diff --git a/.github/.metadata.json b/.github/.metadata.json new file mode 100644 index 000000000..f07606072 --- /dev/null +++ b/.github/.metadata.json @@ -0,0 +1,32 @@ +{ + "templateVersion": "0.1", + "product": { + "name": "Magento2 Functional Testing Framework (MFTF)", + "description": "MFTF is a framework to write and execute UI Functional tests for Magento 2 projects" + }, + "contacts": { + "team": { + "name": "Magento Quality Engineering / Pangolin", + "DL": "GRP-Pangolin", + "slackChannel": "mftf" + } + }, + "ticketTracker": { + "functionalJiraQueue": { + "projectKey": "MQE", + "component": "Framework - MFTF" + }, + "securityJiraQueue": { + "projectKey": "MAGREQ", + "component": "Test Infrastructure" + } + }, + "staticScan": { + "enable": true, + "frequency": "monthly", + "customName": "", + "branchesToScan": [ + "develop" + ] + } +} diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 271b66244..b037250ef 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -24,8 +24,16 @@ If there is no response from the contributor for two weeks, the issue is closed. Often when the MFTF team works on reviewing the suggested changes, we will add a label to the issue to indicate to our internal team certain information, like status or who is working the issue. If you’re ever curious what the different labels mean, see the [table][labels] below for an explanation of each one. -Refer to [Magento Contributor Agreement] for detailed information about the License Agreement. -All contributors are required to submit a click-through form to agree to the terms. +## Code Of Conduct + +This project adheres to the Adobe [Code Of Conduct](../CODE_OF_CONDUCT.md). By participating, you are expected to uphold this code. +Please report unacceptable behavior to [Grp-opensourceoffice@adobe.com](mailto:Grp-opensourceoffice@adobe.com). + +## Contributor License Agreement + +All third-party contributions to this project must be accompanied by a signed Contributor License Agreement (CLA). +This gives Adobe permission to redistribute your contributions as part of the project. +[Sign our CLA](https://opensource.adobe.com/cla.html). You only need to sign it once. ## Contribution requirements @@ -44,7 +52,7 @@ All contributors are required to submit a click-through form to agree to the ter 7. For large features or changes, [open an issue][issue] to discuss first. This may prevent duplicate or unnecessary effort, and it may gain you some additional contributors. 8. To report a bug, [open an issue][issue], and follow [guidelines about bugfix issues][issue reporting]. -9. All automated tests must pass successfully (all builds on [Travis CI] must be green). +9. All automated tests must pass successfully (all builds must be green). ## Fork a repository @@ -171,4 +179,3 @@ Label| Description [Magento Contributor Agreement]: http://www.magento.com/legaldocuments/mca [MFTF repository]: https://github.com/magento/magento2-functional-testing-framework [open new issue]: https://github.com/magento/magento2-functional-testing-framework/issues/new -[Travis CI]: https://travis-ci.com/magento/magento2-functional-testing-framework/pull_requests diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 3ce27ce35..9bcc9ec2b 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -12,5 +12,5 @@ - [ ] Pull request has a meaningful description of its purpose - [ ] All commits are accompanied by meaningful commit messages - [ ] All new or changed code is covered with unit/verification tests (if applicable) - - [ ] All automated tests passed successfully (all builds on Travis CI are green) + - [ ] All automated tests passed successfully (all builds are green) - [ ] Changes to Framework doesn't have backward incompatible changes for tests or have related Pull Request with fixes to tests \ No newline at end of file diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 000000000..f54e1b395 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,156 @@ +# Copyright © Magento, Inc. All rights reserved. +# See COPYING.txt for license details. + +name: CI + +on: + pull_request: + paths-ignore: + - 'docs/**' + +jobs: + unit-tests: + name: Unit Tests + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + php-versions: ['7.3', '7.4', '8.0', '8.1'] + steps: + - uses: actions/checkout@v2 + + - name: Setup PHP + uses: shivammathur/setup-php@master + with: + php-version: ${{ matrix.php-versions }} + extensions: curl, dom, intl, json, openssl + coverage: xdebug + + - name: Cache Composer packages + id: composer-cache + uses: actions/cache@v2 + with: + path: vendor + key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }} + restore-keys: | + ${{ runner.os }}-php- + + - name: Install dependencies + if: steps.composer-cache.outputs.cache-hit != 'true' + run: composer install --prefer-dist --no-progress --no-suggest + + - name: Run tests + run: vendor/bin/phpunit --configuration dev/tests/phpunit.xml --testsuite unit --coverage-clover clover.xml + +# - name: Monitor coverage +# if: github.event_name == 'pull_request' +# uses: slavcodev/coverage-monitor-action@1.2.0 +# with: +# github_token: ${{ secrets.GITHUB_TOKEN }} +# clover_file: "clover.xml" +# threshold_alert: 10 +# threshold_warning: 20 + + verification-tests: + name: Verification Tests + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + php-versions: ['7.3', '7.4', '8.0', '8.1'] + steps: + - uses: actions/checkout@v2 + + - name: Setup PHP + uses: shivammathur/setup-php@master + with: + php-version: ${{ matrix.php-versions }} + extensions: curl, dom, intl, json, openssl + + - name: Cache Composer packages + id: composer-cache + uses: actions/cache@v2 + with: + path: vendor + key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }} + restore-keys: | + ${{ runner.os }}-php- + + - name: Install dependencies + if: steps.composer-cache.outputs.cache-hit != 'true' + run: composer install --prefer-dist --no-progress --no-suggest + + - name: Run tests + run: vendor/bin/phpunit --configuration dev/tests/phpunit.xml --testsuite verification + + static-tests: + name: Static Tests + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + php-versions: ['7.3', '7.4', '8.0', '8.1'] + steps: + - uses: actions/checkout@v2 + + - name: Setup PHP + uses: shivammathur/setup-php@master + with: + php-version: ${{ matrix.php-versions }} + extensions: curl, dom, intl, json, openssl + + - name: Cache Composer packages + id: composer-cache + uses: actions/cache@v2 + with: + path: vendor + key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }} + restore-keys: | + ${{ runner.os }}-php- + + - name: Install dependencies + if: steps.composer-cache.outputs.cache-hit != 'true' + run: composer install --prefer-dist --no-progress --no-suggest + + - name: Run tests + run: bin/static-checks + + functional-tests: + name: Functional Tests + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + php-versions: ['7.3', '7.4', '8.0', '8.1'] + + services: + chrome: + image: selenium/standalone-chrome:3.141.59-zirconium + ports: + - 4444:4444 + steps: + - uses: actions/checkout@v2 + + - name: Setup PHP + uses: shivammathur/setup-php@master + with: + php-version: ${{ matrix.php-versions }} + extensions: curl, dom, intl, json, openssl + + - name: Cache Composer packages + id: composer-cache + uses: actions/cache@v2 + with: + path: vendor + key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }} + restore-keys: | + ${{ runner.os }}-php- + + - name: Install dependencies + if: steps.composer-cache.outputs.cache-hit != 'true' + run: composer install --prefer-dist --no-progress --no-suggest + + - name: Run tests + run: bin/functional + + diff --git a/.gitignore b/.gitignore index d6ad9e81c..1ab55a2ff 100755 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,7 @@ dev/tests/mftf.log dev/tests/docs/* dev/tests/_output dev/tests/functional.suite.yml +/v2/ +dev/.credentials.example +dev/tests/.phpunit.result.cache +dev/tests/verification/TestModule/Test/testFile.xml \ No newline at end of file diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 913c74f48..000000000 --- a/.travis.yml +++ /dev/null @@ -1,15 +0,0 @@ -language: php -php: - - 7.0 - - 7.1 - - 7.2 - - 7.3 -install: composer install --no-interaction --prefer-source -env: - matrix: - - VERIFICATION_TOOL=phpunit-checks - - VERIFICATION_TOOL=static-checks -script: - - bin/$VERIFICATION_TOOL -after_success: - - travis_retry php vendor/bin/coveralls diff --git a/CHANGELOG.md b/CHANGELOG.md index 54189d745..2319ee48c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,481 @@ Magento Functional Testing Framework Changelog ================================================ +3.10.2 +--------- + +### Fixes + +* Fixed admin credentials being output to console in WebAPIAuth +* Fixed links in docs + + + +3.10.1 +--------- + +### Fixes + +* Fixed allure reports not generating for composer builds. +* Fixed all MFTF scheduled build not generating allure report. + +3.10.0 +--------- + +### Enhancements +* Updated symfony/console and symfony/process constraints to support latest Symfony LTS (5.4v) +* Updated Symfony related code to support latest Symfony LTS (5.4v). +* Implement rapid times X clicks on UI element in MFTF +* Log MFTF test dependencies +* Unused entity static check +* Updated docs for new location of password +* Remove any remaining usages of Travis CI from MFTF Repo +* Unit tests for GenerateTestFailedCommandTest and RunTestFailedCommandTest + +### Fixes +* Hashicorp Vault PHP lib being instantiated with wrong params + +3.9.0 +--------- + +### Fixes + +* Fixed invalid UTF-8 chars returned from magentoCLI that breaks allure reporting +* Fixed MFTF tests failure without access to S3 +* Removed sub heading Parameters from allure report +* Removed truncation of parameters on running MFTF run:test + +### Enhancements +* MFTF group summary file +* Static check to detect unused entities +* Ability To Run MFTF JSON Configuration From File +* Test generation error on invalid entities +* Update MFTF to not pass NULL into non-nullable arguments +* Static check for created data outside action group +* Deleted the unused images +* CreateData's to allow uniqueness attribute +* Set proper weight for action for config parallel generation +* Test before/after comments in test/allure +* Throw error message if key value pair is not mapped properly in .credentials file + +3.8.0 +--------- + +### Updates: +* Allow MFTF Helpers to Return Data to MFTF Test +* Improve parallel grouping and fix an issue with unbalanced groups +* Added new action WaitForElementClickable + +3.7.3 +--------- + +### Updates: +* Fix encoding issue when secret key contains plus sign +* Adding pagebuilder file upload spinner to loadingMaskLocators + + +3.7.2 +--------- + +### Bug fix: +* Failed tests weren't logged correctly to `failed` file which caused a failure during run:failed command execution + + +3.7.1 +--------- + +### GitHub Pull Requests: +* [#873](https://github.com/magento/magento2-functional-testing-framework/pull/873) -- Add check for isBuiltin method (for PHP 8 compatibility) by @karyna-tsymbal-atwix + +### Updates +* Moved `hoa/console` to suggest section to avoid issues with PHP8.0 +* Update `vlucas/phpdotenv` to the latest versions +* `` encodes special character which caused test failed +* Add filter for groups, now we can generate tests with specific group annotation +* Seprated a `run:failed` command to `generate:failed` and `run:failed` + * `run:failed` command can execute failed tests without need to regenerate failed tests +* Deleting MagentoPwaWebDriver file and moving it to Pwa_tests repo + + +3.7.0 +--------- + +### GitHub Pull Requests: + +* [#842](https://github.com/magento/magento2-functional-testing-framework/pull/842) -- Eliminated AspectMock from FileStorageTest +* [#843](https://github.com/magento/magento2-functional-testing-framework/pull/843) -- Eliminated AspectMock from ObjectExtensionUtilTest +* [#844](https://github.com/magento/magento2-functional-testing-framework/pull/844) -- Eliminated AspectMock from TestObjectHandlerTest +* [#845](https://github.com/magento/magento2-functional-testing-framework/pull/845) -- Eliminated AspectMock from SuiteObjectHandlerTest +* [#846](https://github.com/magento/magento2-functional-testing-framework/pull/846) -- Eliminated AspectMock from ActionGroupObjectTest +* [#847](https://github.com/magento/magento2-functional-testing-framework/pull/847) -- Removed not used mocked object +* [#848](https://github.com/magento/magento2-functional-testing-framework/pull/848) -- Eliminated AspectMock usage from ActionObjectTest +* [#850](https://github.com/magento/magento2-functional-testing-framework/pull/850) -- Eliminated AspectMock from TestGeneratorTest +* [#852](https://github.com/magento/magento2-functional-testing-framework/pull/852) -- Eliminated AspectMock from ModuleResolverTest +* [#853](https://github.com/magento/magento2-functional-testing-framework/pull/853) -- Eliminated AspectMock from PersistedObjectHandlerTest +* [#855](https://github.com/magento/magento2-functional-testing-framework/pull/855) -- Eliminated AspectMock from OperationDataArrayResolverTest +* [#856](https://github.com/magento/magento2-functional-testing-framework/pull/856) -- Eliminated AspectMock from DataExtensionUtilTest +* [#857](https://github.com/magento/magento2-functional-testing-framework/pull/857) -- Eliminated AspectMock from ParallelGroupSorterTest +* [#859](https://github.com/magento/magento2-functional-testing-framework/pull/859) -- Eliminated AspectMock usage from SuiteGeneratorTest +* [#861](https://github.com/magento/magento2-functional-testing-framework/pull/861) -- Eliminated aspect mock from mock module resolver builder +* [#862](https://github.com/magento/magento2-functional-testing-framework/pull/862) -- Eliminated AspectMock where it was imported but never used +* [#863](https://github.com/magento/magento2-functional-testing-framework/pull/863) -- Eliminated AspectMock from MagentoTestCase +* [#864](https://github.com/magento/magento2-functional-testing-framework/pull/864) -- Eliminated AspectMock usage from TestLoggingUtil +* [#865](https://github.com/magento/magento2-functional-testing-framework/pull/865) -- Eliminated aspect mock from object handler uti +* [#866](https://github.com/magento/magento2-functional-testing-framework/pull/866) -- Added access/secret key config parameters +* [#867](https://github.com/magento/magento2-functional-testing-framework/pull/867) -- Added empty query and fragment testing to the UrlFormatterTest +* [#868](https://github.com/magento/magento2-functional-testing-framework/pull/868) -- PHP 8 support - fix code related to changes in CURL +* [#869](https://github.com/magento/magento2-functional-testing-framework/pull/869) -- The squizlabs/php_codesniffer composer dependency has been updated to version 3.6.0 +* [#870](https://github.com/magento/magento2-functional-testing-framework/pull/870) -- Removing the csharpru/vault-php-guzzle6-transport not needed dependency +* [#871](https://github.com/magento/magento2-functional-testing-framework/pull/871) -- Changed loose comparisons into strict +* [#872](https://github.com/magento/magento2-functional-testing-framework/pull/872) -- Fix broken MFTF tests + + 3.6.1 +--------- + +### Enhancements + +* Maintainability + * Updated allure dependencies to pull package from new repo `allure-framework/allure-php-api`. + +3.6.0 +--------- + +### Enhancements + +* Maintainability + * Updated composer dependencies to be PHP 8 compatible with the except of codeception/aspect-mock. + +### GitHub Pull Requests: + +* [#830](https://github.com/magento/magento2-functional-testing-framework/pull/830) -- Add ability to configure multiple OTPs +* [#832](https://github.com/magento/magento2-functional-testing-framework/pull/832) -- Updated monolog/monolog to ^2.2 +* [#833](https://github.com/magento/magento2-functional-testing-framework/pull/833) -- Removed usage of AspectMock in FilesystemTest +* [#834](https://github.com/magento/magento2-functional-testing-framework/pull/834) -- Removed usage of AspectMock in AnnotationsCheckTest +* [#838](https://github.com/magento/magento2-functional-testing-framework/pull/838) -- Removed usage of AspectMock in DeprecatedEntityUsageCheckTest +* [#841](https://github.com/magento/magento2-functional-testing-framework/pull/841) -- Removed usage of AspectMock in GenerationErrorHandlerTest +* [#854](https://github.com/magento/magento2-functional-testing-framework/pull/854) -- Updated "monolog" to the latest version 2.3.1 + +3.5.1 +--------- + +### GitHub Pull Requests: + +* [#825](https://github.com/magento/magento2-functional-testing-framework/pull/825) -- Update allure-codeception in order to support php8 + +3.5.0 +--------- + +### Enhancements + +* Customizability + * Added new `config:parallel --groups` option in `generate:tests` command to generate and split tests/suites into required number of execution time balanced groups. + +### Fixes + +* Added --no-sandbox chrome option in functional suite configuration. + +### GitHub Pull Requests: + +* [#824](https://github.com/magento/magento2-functional-testing-framework/pull/824) -- Fix typo in introduction.md +* [#816](https://github.com/magento/magento2-functional-testing-framework/pull/816) -- Update mftf.md +* [#812](https://github.com/magento/magento2-functional-testing-framework/pull/812) -- Added examples and modified url links in assertions.md + +3.4.0 +--------- + +### Enhancements + +* Maintainability + * Added support for composer 2. + +3.3.0 +--------- + +### Enhancements + +* Usability + * [#817](https://github.com/magento/magento2-functional-testing-framework/pull/817) -- Add support for admin WebAPI token refresh. + +* Maintainability + * [#814](https://github.com/magento/magento2-functional-testing-framework/pull/814) -- Update dependencies in order to make mftf php8 compatible, fix running phpcpd + * [#815](https://github.com/magento/magento2-functional-testing-framework/pull/815) -- Upgrade csharpru/vault-php to 4.1 + +### Fixes + +* Fixed test generation error in a split suite group (--config=parallel) to allow generation of subsequent groups. +* Fixed an issue where test extends from a skipped parent is not properly skipped. + +3.2.1 +--------- + +### Fixes + +* Fixed issue that causes Magento bin/magento to fail when xdebug 3 is used. [GitHub Issue #808](https://github.com/magento/magento2-functional-testing-framework/issues/808) + +### GitHub Pull Requests: + + * [#806](https://github.com/magento/magento2-functional-testing-framework/pull/806) -- Enable an extending entity to overwrite a requiredEntity binding + * [#809](https://github.com/magento/magento2-functional-testing-framework/pull/809) -- Add MFTF documentation for AWS S3 + +3.2.0 +--------- + +### Enhancements + +* Usability + * Introduced error tolerance during test and suite generation. See the [command page](./docs/commands/mftf.md#error-tolerance-during-generation) for details. + Addressed github issue [#276](https://github.com/magento/magento2-functional-testing-framework/issues/276). + +* Maintainability + * Updated annotation static-check to check all required annotations. + +### Fixes + +* Fixed issue where CUSTOM_MODULE_PATHS env variable does not use all paths. +* Fixed issue where run:test only records the last failed test in `failed` file. + +3.1.1 +--------- + +* Traceability + * Removed `travis.yml` and replaced with `.github/workflows/main.yml` + +### Fixes +Fixed issue with XPath locators for waits in MagentoPwaWebDriver. + +3.1.0 +--------- + +### Enhancements + +* Customizability + * Introduced the new `return` action that allows action groups to return a value. See the [actions page](./docs/test/actions.md#return) for details. + * Introduced new MFTF command that invokes `vendor/bin/codecept run`. See the [mftf page](./docs/commands/mftf.md#codeceptrun) for details. + +* Usability + * Introduced new action `pause`, to invoke codeception interactive pause for debugging during test execution. See the [Interactive Pause](./docs/interactive-pause.md) page for details. + * Introduced a new `.env` configuration option `ENABLE_PAUSE`, to enable the new pause feature. + +* Maintainability + * Added a new static check that checks for the usage of the `pause` action. See the [command page](./docs/commands/mftf.md#static-checks) for details. + +* Modularity + * MFTF now supports the use of actions from multiple modules within suites. + +* Traceability + * A deprecation notice is now added at test execution time for deprecated metadata usage. + +### Fixes + +* Fixed issue with suite precondition failure for `createData` with required entity. + +### GitHub Issues/Pull Requests: + + * [#547](https://github.com/magento/magento2-functional-testing-framework/pull/547) -- Fix invalid behavior of MAGENTO_BACKEND_BASE_URL + * [#742](https://github.com/magento/magento2-functional-testing-framework/pull/742) -- Fix Waits In MagentoPwaWebDriver + * [#750](https://github.com/magento/magento2-functional-testing-framework/pull/750) -- Docs: Outlining the difference between Allure severity levels + * [#683](https://github.com/magento/magento2-functional-testing-framework/pull/683) -- Docs: Renamed sample test name with the correct one + * [#691](https://github.com/magento/magento2-functional-testing-framework/pull/691) -- Docs: Branch name updates + * [#678](https://github.com/magento/magento2-functional-testing-framework/pull/678) -- Docs: Command added to modify the web server rewrites configuration + * [#745](https://github.com/magento/magento2-functional-testing-framework/pull/745) -- Docs: Remove invalid sample test name + +3.0.0 +--------- + +### Enhancements + +* Customizability + * Introduced MFTF helpers `` to create custom actions outside of MFTF.[See custom-helpers page for details](./docs/custom-helpers.md) + * Removed deprecated actions `` and ``. + * `` no longer skips a test. Instead, the test is added to the `skip` group. + +* Maintainability + * Added support for PHP 7.4. + * Added support for PHPUnit 9. + * Dropped support for PHP 7.0, 7.1, 7.2. + * Schema updates for test entities to only allow single entity per file except Data and Metadata. + * Support for sub-folders in test modules. + * Removed support to read test entities from `dev/tests/acceptance/tests/functional/Magento/FunctionalTest`. + * Removed file attribute for `` in suiteSchema. + * Removed action `pauseExecution` and added `pause`. [See actions page for details](./docs/test/actions.md#pause) + * Removed action `formatMoney` and added `formatCurrency`. [See actions page for details](./docs/test/actions.md#formatcurrency) + * Improved assertion actions to support PHPUnit 9 changes. [See assertions page for details](./docs/test/assertions.md) + * Added new actions: `assertEqualsWithDelta`, `assertNotEqualsWithDelta`, `assertEqualsCanonicalizing`, `assertNotEqualsCanonicalizing`, `assertEqualsIgnoringCase`, `assertNotEqualsIgnoringCase`. + * Added new actions: `assertStringContainsString`, `assertStringNotContainsString`, `assertStringContainsStringIgnoringCase`, `assertStringNotContainsStringIgnoringCase` for string haystacks. + * Removed actions: `assertInternalType`, `assertNotInternalType`, `assertArraySubset`. + * Removed delta option from `assertEquals` and `assertNotEquals`. + * Added static check `deprecatedEntityUsage` that checks and reports references to deprecated test entities. + * Added static check `annotations` that checks and reports missing annotations in tests. + * Updated `bin/mftf static-checks` command to allow executing static-checks defined in `staticRuleSet.json` by default. [See command page for details](./docs/commands/mftf.md#static-checks) + * Added support for Two-Factor Authentication (2FA). [See configure-2fa page for details](./docs/configure-2fa.md) + * Added new upgrade script to remove unused arguments from action groups. + * `mftf.log` no longer includes notices and warnings at test execution time. + * Added unhandledPromptBehavior driver capability for Chrome 75+ support. + * Added the Chrome option `--ignore-certificate-errors` to `functional.suite.dist.yml`. + +* Traceability + * Removed `--debug` option `NONE` to disallow ability to turn off schema validation. + * Notices added for test entity naming convention violations. + * Metadata file names changed to `*Meta.xml`. + * Introduced new `.env` configuration `VERBOSE_ARTIFACTS` to toggle saving attachments in Allure. [See configuration page for details](./docs/configuration.md) + * Changed the `bin/mftf static-checks` error file directory from the current working directory to `TESTS_BP/tests/_output/static-results/`. + +* Readability + * Support only nested assertion syntax [See assertions page for details](./docs/test/assertions.md). + * Documented [3.0.0 Backward Incompatible Changes](./docs/backward-incompatible-changes.md). + * Removed blacklist/whitelist terminology in MFTF. + +* Upgrade scripts added to upgrade tests to MFTF major version requirements. See upgrade instructions below. +* Bumped dependencies to support PHP/PHPUnit upgrade. + +### Upgrade Instructions + +* Run `bin/mftf reset --hard` to remove old generated configurations. +* Run `bin/mftf build:project` to generate new configurations. +* Run `bin/mftf upgrade:tests`. [See command page for details](./docs/commands/mftf.md#upgradetests). +* After running the above command, some tests may need manually updates: + * Remove all occurrences of `` and ``. + * Remove all occurrences of `` from any ``s. + * Ensure all `` actions in your tests have a valid schema. +* Lastly, try to generate all tests. Tests should all be generated as a result of the upgrades. + * If not, the most likely issue will be a changed XML schema. Check error messaging and search your codebase for the attributes listed. + +### Fixes + +* Throw exception during generation when leaving out .url for `amOnPage`. +* `request_timeout` and `connection_timeout` added to functional.suite.yml.dist. +* Fixed `ModuleResolver` to resolve test modules moved out of deprecated path. +* Fixed issue of resolving arguments of type `entity` in action groups within a custom helper. +* Fixed reporting issue in output file for `testDependencies` static check. +* Fixed a bug in `actionGroupArguments` static check when action group filename is missing `ActionGroup`. +* Fixed issue of running suites under root `_suite` directory in Standalone MFTF. +* Fixed issue with custom helper usage in suites. +* Fixed issue with decryption of secrets during data entity creation. +* Fixed issue with merging of `array` items in data entity. +* Fixed issue where an extended data entity would not merge array items. Array items should merge properly now. +* Fixed issue where Chrome remains running after MFTF suite finishes. +* Fixed javascript error seen on Chrome 83 for dragAndDrop action. +* Fixed allure issue when `WebDriverCurlException` is encountered in `afterStep`. + +### GitHub Issues/Pull Requests + +* [#567](https://github.com/magento/magento2-functional-testing-framework/pull/567) -- log attachments for failed requests. + +### Demo Video links + +* [MFTF 3.0.0 RC1](https://www.youtube.com/watch?v=z0ZaZCmnw-A&t=2s) +* [MFTF 3.0.0 RC2](https://www.youtube.com/watch?v=BJOQAw6dX5o) +* [MFTF 3.0.0 RC3](https://www.youtube.com/watch?v=scLb7pi8pR0) + +2.6.4 +----- + +### Fixes +* added dependency to packages MFTF used but never specified in composer.json + +2.6.3 +----- + +### New Feature +* `--filter` option was added to `bin/mftf generate:tests` command. For more details please go to https://devdocs.magento.com/mftf/docs/commands/mftf.html#generatetests + +2.6.2 +----- + +### Fixes +* Fixed float conversion error in test generation + +2.6.1 +----- + +* Usability + * Introduced new `.env` configuration `ELASTICSEARCH_VERSION` to support multiple elasticsearch versions +* Maintainability + * Added deprecation notices for upcoming MFTF 3.0.0 +* Replaced facebook webdriver with php-webdriver to support PHP version updates + +2.6.0 +----- + +* Usability + * `magentoCron` action added by community maintainer @lbajsarowicz +* Traceability + * MFTF generated cest files are fully compatible for Codeception `dry-run`. +* Modularity + * `mftf generate:tests` and `mftf run:test` commands now accept suite scoped test names in format `[suitename:testname]...`. +* Maintainability + * Support `deprecated` syntax for the following test entities: + * Test + * Action Group + * Data + * Metadata + * Page + * Section + * Section Element + * See DevDocs for details + * Improved `mftf static-checks` command to allow executing all or specific static checks. + * Added a new static check that checks and reports unused arguments in action groups. +* Customizability + * AWS Secrets Manager has been added as an additional credential storage. + * See DevDocs for details +* Bumped dependencies to latest possible versions + +### Fixes +* Fixed missing before, after, failed steps in cest file when generating tests with `--allow-skipped` option. +* Fixed suites and tests display issue in Allure `Suites` page after `mftf run:group` command. +* `createData` action now shows a meaningful error message at runtime when the entity does not exist. + +### GitHub Issues/Pull requests: +* [#537](https://github.com/magento/magento2-functional-testing-framework/pull/537) -- Refactor of TestGenerator class +* [#538](https://github.com/magento/magento2-functional-testing-framework/pull/538) -- FEATURE: command to execute Cron Jobs + +2.5.4 +----- +[Demo Video](https://www.youtube.com/watch?v=tguvkw1HWKg) +* Traceability + * Introduced new `mftf doctor` command + * Command verifies and troubleshoots some configuration steps required for running tests + * Please see DevDocs for more details + * `<*Data>` actions now contain `API Endpoint` and `Request Header` artifacts. + * Introduced new `.env` configurations `ENABLE_BROWSER_LOG` and `BROWSER_LOG_BLOCKLIST` + * Configuration enables allure artifacts for browser log entries if they are present after the step. + * Blocklist filters out logs from specific sources. +* Customizability + * Introduced `timeout=""` to `magentoCLI` actions. + +### GitHub Issues/Pull requests: +* [#317](https://github.com/magento/magento2-functional-testing-framework/pull/317) -- RetrieveEntityField generation does not consider ActionGroup as part of namespace +* [#433](https://github.com/magento/magento2-functional-testing-framework/pull/433) -- Add possibility to include multiple non primitive types in an array + +### Fixes +* A test now contains attachments for every exception encountered in the test (fix for a test `` exception overriding all test exceptions). +* Fixed hard requirement for `MAGENTO_BASE_URL` to contain a leading `/`. +* `magentoCLI` actions for `config:sensitive:set` no longer obscure CLI output. +* `WAIT_TIMEOUT` in the `.env` now correctly sets `pageload_timeout` configuration. +* Fixed an issue where `run:group` could not consolidate a `group` that had tests in and out of ``s. + +2.5.3 +----- + +### Fixes +* Fixed an issue where `createData` actions would cause an exception when used in `` hooks. + +2.5.2 +----- + +* Traceability + * Allure report enhanced to display file path of tests. +* Maintainability + * Added support to read MFTF test entities from `dev/tests/acceptance/tests/functional///*` + * Removed path deprecation warning from `ModuleResolver`. + * Refactored problem methods to reduce cyclomatic complexity. + +### Fixes +* Fixed issue with builds due to absence of AcceptanceTester class. + +### GitHub Issues/Pull requests: +* [#348](https://github.com/magento/magento2-functional-testing-framework/pull/348) -- executeInSelenium command fixed to prevent escaping double quotes. + 2.5.1 ----- @@ -61,7 +536,7 @@ Magento Functional Testing Framework Changelog * Customizability * Use of `_CREDS` has been extended to `` and `` actions * Traceability - * A Test step is now generated and injected in the allure report when a test is reported as `BROKEN`. + * A Test step is now generated and injected in the allure report when a test is reported as `BROKEN`. ### Fixes * `static-checks` command now properly returns `1` if any static check failed. @@ -163,7 +638,7 @@ Magento Functional Testing Framework Changelog ### Enhancements * Maintainability * Added new `mftf run:failed` commands, which reruns all failed tests from last run configuration. - + ### Fixes * Fixed an issue where mftf would fail to parse test materials for extensions installed under `vendor`. * Fixed a Windows compatibility issue around the use of Magento's `ComponentRegistrar` to aggregate paths. @@ -252,15 +727,15 @@ Magento Functional Testing Framework Changelog 2.3.1 ----- -### Enhancements +### Enhancements * Maintainability * `mftf build:project` now copies over the `command.php` file into the parent Magento installation, if detected. 2.3.0 ----- -### Enhancements +### Enhancements * Traceability - * MFTF now outputs generation run-time information, warnings, and errors to an `mftf.log` file. + * MFTF now outputs generation run-time information, warnings, and errors to an `mftf.log` file. * Overall error messages for various generation errors have been improved. Usage of the `--debug` flag provides file-specific errors for all XML-related errors. * Allure Reports now require a unique `story` and `title` combination, to prevent collisions in Allure Report generation. * The `features` annotation now ignores user input and defaults to the module the test lives under (for clear Allure organization). @@ -311,7 +786,7 @@ Magento Functional Testing Framework Changelog 2.2.0 ----- -### Enhancements +### Enhancements * Traceability * Javascript errors are now logged and reported in test output. * Test failures are no longer overwritten by failures in an `` hook. @@ -388,7 +863,7 @@ Magento Functional Testing Framework Changelog ----- ### Enhancements * Traceability - * Severity in `` tags now properly reflect Magento severity values. + * Severity in `` tags now properly reflect Magento severity values. * Modularity * Added ability to pass in simple values to actionGroups via addition of `type` attribute in actionGroup `` declaration. * For examples, see devdocs article on actionGroups. @@ -424,7 +899,7 @@ Magento Functional Testing Framework Changelog * Added the ability to refer to `custom_attribute` data in persisted entities via `key` instead of index. * ex. `url_key` in category entity: `$category.custom_attributes[3][value]$` and `$category.custom_attributes[url_key]$` are both valid. * Maintainability - * Added check for duplicate `stepKey` attributes at test generation. This check is scoped to `` tags within a single `` tag in a single file. + * Added check for duplicate `stepKey` attributes at test generation. This check is scoped to `` tags within a single `` tag in a single file. ### Fixes * Fixed inability to use `` with `` in test hooks. diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 000000000..549b492a0 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,74 @@ +# Adobe Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, gender identity and expression, level of experience, +nationality, personal appearance, race, religion, or sexual identity and +orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language. +* Being respectful of differing viewpoints and experiences. +* Gracefully accepting constructive criticism. +* Focusing on what is best for the community. +* Showing empathy towards other community members. + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or +advances. +* Trolling, insulting/derogatory comments, and personal or political attacks. +* Public or private harassment. +* Publishing others' private information, such as a physical or electronic + address, without explicit permission. +* Other conduct which could reasonably be considered inappropriate in a + professional setting. + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at Grp-opensourceoffice@adobe.com. All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at [https://contributor-covenant.org/version/1/4][version]. + +[homepage]: https://contributor-covenant.org +[version]: https://contributor-covenant.org/version/1/4/ \ No newline at end of file diff --git a/README.md b/README.md index b95a7afc4..02a2b0cd5 100755 --- a/README.md +++ b/README.md @@ -1,7 +1,5 @@ # Magento Functional Testing Framework (MFTF) -[![Build Status](https://travis-ci.org/magento/magento2-functional-testing-framework.svg?branch=develop)](https://travis-ci.org/magento/magento2-functional-testing-framework) [![Coverage Status](https://coveralls.io/repos/github/magento/magento2-functional-testing-framework/badge.svg?branch=develop)](https://coveralls.io/github/magento/magento2-functional-testing-framework) - ---- ## Installation @@ -16,7 +14,7 @@ Learn more about contributing in our [Contribution Guidelines][]. If you want to participate in the documentation work, see [DevDocs Contributing][]. -### Labels applied by the MFTF team +### Labels Applied by the MFTF Team Refer to the tables with descriptions of each label below. These labels are applied by the MFTF development team to community contributed issues and pull requests, to communicate status, impact, or which team is working on it. @@ -53,12 +51,10 @@ These labels are applied by the MFTF development team to community contributed i | **bugfix** | The issue or pull request relates to bug fixing. | | **enhancement** | The issue or pull request that raises the MFTF to a higher degree (for example new features, optimization, refactoring, etc). | -## Reporting security issues +## Reporting Security Issues -To report security vulnerabilities and other security issues in the Magento software or web sites, send an email with the report at [security@magento.com][]. -Do not report security issues using GitHub. -Be sure to encrypt your e-mail with our [encryption key][] if it includes sensitive information. -Learn more about reporting security issues [here][]. +To report security vulnerabilities or learn more about reporting security issues in Magento software or web sites visit the [Magento Bug Bounty Program](https://hackerone.com/magento) on hackerone. +Please create a hackerone account [there](https://hackerone.com/magento) to submit and follow-up on your issue. Stay up-to-date on the latest security news and patches for Magento by signing up for [Security Alert Notifications][]. @@ -66,15 +62,12 @@ Stay up-to-date on the latest security news and patches for Magento by signing u Each Magento source file included in this distribution is licensed under AGPL 3.0. -See the license [here][] or contact [license@magentocommerce.com][] for a copy. +See the [license][] or contact [license@magentocommerce.com][] for a copy. [Getting Started]: docs/getting-started.md -[Contribution Guidelines]: .github/CONTRIBUTING.html +[Contribution Guidelines]: .github/CONTRIBUTING.md [DevDocs Contributing]: https://github.com/magento/devdocs/blob/master/.github/CONTRIBUTING.md -[security@magento.com]: mailto:security@magento.com -[encryption key]: https://info2.magento.com/rs/magentoenterprise/images/security_at_magento.asc -[here]: https://magento.com/security/reporting-magento-security-issue [Security Alert Notifications]: https://magento.com/security/sign-up -[here]: LICENSE_AGPL3.txt -[license@magentocommerce.com]: mailto:license@magentocommerce.com \ No newline at end of file +[license]: LICENSE_AGPL3.txt +[license@magentocommerce.com]: mailto:license@magentocommerce.com diff --git a/_config.yml b/_config.yml new file mode 100644 index 000000000..fc24e7a62 --- /dev/null +++ b/_config.yml @@ -0,0 +1 @@ +theme: jekyll-theme-hacker \ No newline at end of file diff --git a/bin/blacklist.txt b/bin/blocklist.txt similarity index 96% rename from bin/blacklist.txt rename to bin/blocklist.txt index 39b3a6700..6d9f6f4e2 100644 --- a/bin/blacklist.txt +++ b/bin/blocklist.txt @@ -3,7 +3,7 @@ # # # THIS FILE CANNOT CONTAIN BLANK LINES # ################################################################### -bin/blacklist.txt +bin/blocklist.txt dev/tests/static/Magento/Sniffs/Commenting/FunctionCommentSniff.php dev/tests/static/Magento/Sniffs/Commenting/VariableCommentSniff.php dev/tests/verification/_generated diff --git a/bin/copyright-check b/bin/copyright-check index 79fe8df06..dc9976d77 100755 --- a/bin/copyright-check +++ b/bin/copyright-check @@ -5,13 +5,13 @@ FILE_EXTENSIONS='.php\|.xml\|.xsd' -BLACKLIST='bin/blacklist.txt' +BLOCKLIST='bin/blocklist.txt' RESULT='' # Iterate through the list of tracked files # that have the expected extensions # that are not ignored -for i in `git ls-tree --full-tree -r --name-only HEAD | grep $FILE_EXTENSIONS | grep -v -f $BLACKLIST` +for i in `git ls-tree --full-tree -r --name-only HEAD | grep $FILE_EXTENSIONS | grep -v -f $BLOCKLIST` do if echo `cat $i` | grep -q -v "Copyright © Magento, Inc. All rights reserved."; then # Copyright is missing diff --git a/bin/copyright-check.bat b/bin/copyright-check.bat index 9f2e23bfb..0a7612f77 100644 --- a/bin/copyright-check.bat +++ b/bin/copyright-check.bat @@ -3,7 +3,7 @@ @echo off SETLOCAL EnableDelayedExpansion -SET BLACKLIST_FILE=bin/blacklist.txt +SET BLOCKLIST_FILE=bin/blocklist.txt SET i=0 FOR /F %%x IN ('git ls-tree --full-tree -r --name-only HEAD') DO ( @@ -12,14 +12,14 @@ FOR /F %%x IN ('git ls-tree --full-tree -r --name-only HEAD') DO ( if "%%~xx"==".xml" set GOOD_EXT=1 if "%%~xx"==".xsd" set GOOD_EXT=1 IF DEFINED GOOD_EXT ( - SET BLACKLISTED= - FOR /F "tokens=* skip=5" %%f IN (%BLACKLIST_FILE%) DO ( + SET BLOCKLISTED= + FOR /F "tokens=* skip=5" %%f IN (%BLOCKLIST_FILE%) DO ( SET LINE=%%x IF NOT "!LINE!"=="!LINE:%%f=!" ( - SET BLACKLISTED=1 + SET BLOCKLISTED=1 ) ) - IF NOT DEFINED BLACKLISTED ( + IF NOT DEFINED BLOCKLISTED ( FIND "Copyright © Magento, Inc. All rights reserved." %%x >nul IF ERRORLEVEL 1 ( SET /A i+=1 diff --git a/bin/functional b/bin/functional new file mode 100755 index 000000000..63bb41197 --- /dev/null +++ b/bin/functional @@ -0,0 +1,12 @@ +# Copyright © Magento, Inc. All rights reserved. +# See COPYING.txt for license details. + +set -e + +echo "===============================" +echo " EXECUTE Functional Tests " +echo "===============================" +bin/mftf build:project +bin/mftf run:test DeprecatedDevDocsTest -f +bin/mftf run:test DevDocsTest -f +bin/mftf run:test FormatCurrencyTest -f \ No newline at end of file diff --git a/bin/mftf b/bin/mftf index b978d99ee..7a9ca1cf2 100755 --- a/bin/mftf +++ b/bin/mftf @@ -27,9 +27,11 @@ try { try { + $version = json_decode(file_get_contents(FW_BP . DIRECTORY_SEPARATOR . 'composer.json'), true); + $version = $version['version']; $application = new Symfony\Component\Console\Application(); $application->setName('Magento Functional Testing Framework CLI'); - $application->setVersion('2.5.1'); + $application->setVersion($version); /** @var \Magento\FunctionalTestingFramework\Console\CommandListInterface $commandList */ $commandList = new \Magento\FunctionalTestingFramework\Console\CommandList; foreach ($commandList->getCommands() as $command) { diff --git a/composer.json b/composer.json index f980005c1..0161b4883 100755 --- a/composer.json +++ b/composer.json @@ -2,55 +2,65 @@ "name": "magento/magento2-functional-testing-framework", "description": "Magento2 Functional Testing Framework", "type": "library", - "version": "2.5.1", + "version": "3.10.2", "license": "AGPL-3.0", "keywords": ["magento", "automation", "functional", "testing"], "config": { "sort-packages": true }, "require": { - "php": "7.0.2||7.0.4||~7.0.6||~7.1.0||~7.2.0||~7.3.0", + "php": ">7.3", "ext-curl": "*", - "allure-framework/allure-codeception": "~1.3.0", - "codeception/codeception": "~2.3.4 || ~2.4.0 ", - "composer/composer": "^1.6", - "consolidation/robo": "^1.0.0", - "csharpru/vault-php": "~3.5.3", - "csharpru/vault-php-guzzle6-transport": "^2.0", - "flow/jsonpath": ">0.2", - "fzaninotto/faker": "^1.6", - "monolog/monolog": "^1.0", + "ext-dom": "*", + "ext-iconv": "*", + "ext-intl": "*", + "ext-json": "*", + "ext-openssl": "*", + "allure-framework/allure-codeception": "^1.4", + "aws/aws-sdk-php": "^3.132", + "codeception/codeception": "^4.1", + "codeception/module-asserts": "^1.1", + "codeception/module-sequence": "^1.0", + "codeception/module-webdriver": "^1.0", + "composer/composer": "^1.9 || ^2.0, !=2.2.16", + "csharpru/vault-php": "^4.2.1", + "guzzlehttp/guzzle": "^7.3.0", + "laminas/laminas-diactoros": "^2.8", + "monolog/monolog": "^2.3", "mustache/mustache": "~2.5", - "symfony/process": "^2.8 || ^3.1 || ^4.0", - "vlucas/phpdotenv": "^2.4" + "nikic/php-parser": "^4.4", + "php-webdriver/webdriver": "^1.9.0", + "spomky-labs/otphp": "^10.0", + "symfony/console": "^4.4||^5.4", + "symfony/dotenv": "^5.3", + "symfony/finder": "^5.0", + "symfony/http-foundation": "^5.0", + "symfony/mime": "^5.0", + "symfony/process": "^4.4||^5.4", + "weew/helpers-array": "^1.3" }, "require-dev": { - "squizlabs/php_codesniffer": "~3.2", - "sebastian/phpcpd": "~3.0 || ~4.0", "brainmaestro/composer-git-hooks": "^2.3.1", - "doctrine/cache": "<1.7.0", - "codeception/aspect-mock": "^3.0", - "goaop/framework": "2.2.0", "codacy/coverage": "^1.4", - "phpmd/phpmd": "^2.6.0", - "phpunit/phpunit": "~6.5.0 || ~7.0.0", - "rregeer/phpunit-coverage-check": "^0.1.4", - "php-coveralls/php-coveralls": "^1.0", - "symfony/stopwatch": "~3.4.6" + "php-coveralls/php-coveralls": "^1.0||^2.2", + "phpmd/phpmd": "^2.8.0", + "phpunit/phpunit": "^9.0", + "sebastian/phpcpd": "~6.0.0", + "squizlabs/php_codesniffer": "~3.6.0" }, "suggest": { - "epfremme/swagger-php": "^2.0" + "hoa/console": "Enables action and interactive console functionality" }, "autoload": { "files": ["src/Magento/FunctionalTestingFramework/_bootstrap.php"], "psr-4": { "Magento\\FunctionalTestingFramework\\": "src/Magento/FunctionalTestingFramework", - "MFTF\\": "dev/tests/functional/MFTF" + "MFTF\\": "dev/tests/functional/tests/MFTF" } }, "autoload-dev": { "psr-4": { - "tests\\unit\\": "dev/tests/unit" + "tests\\": "dev/tests" } }, "scripts": { diff --git a/composer.lock b/composer.lock index 275ae9778..1f79506a8 100644 --- a/composer.lock +++ b/composer.lock @@ -4,28 +4,33 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "beb8473a3c21b83da864289149fc03b4", + "content-hash": "075f7ef8a19879334ba1e0443f4e9679", "packages": [ { "name": "allure-framework/allure-codeception", - "version": "1.3.0", + "version": "1.5.2", "source": { "type": "git", "url": "https://github.com/allure-framework/allure-codeception.git", - "reference": "9d31d781b3622b028f1f6210bc76ba88438bd518" + "reference": "a6156aef942a4e4de0add34a73d066a9458cefc6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/allure-framework/allure-codeception/zipball/9d31d781b3622b028f1f6210bc76ba88438bd518", - "reference": "9d31d781b3622b028f1f6210bc76ba88438bd518", + "url": "https://api.github.com/repos/allure-framework/allure-codeception/zipball/a6156aef942a4e4de0add34a73d066a9458cefc6", + "reference": "a6156aef942a4e4de0add34a73d066a9458cefc6", "shasum": "" }, "require": { - "allure-framework/allure-php-api": "~1.1.0", - "codeception/codeception": "~2.1", - "php": ">=5.4.0", - "symfony/filesystem": ">=2.6", - "symfony/finder": ">=2.6" + "allure-framework/allure-php-api": "^1.3", + "codeception/codeception": "^2.5 | ^3 | ^4", + "ext-json": "*", + "php": ">=7.1.3", + "symfony/filesystem": "^2.7 | ^3 | ^4 | ^5", + "symfony/finder": "^2.7 | ^3 | ^4 | ^5" + }, + "require-dev": { + "ext-dom": "*", + "phpunit/phpunit": "^7.2 | ^8 | ^9" }, "type": "library", "autoload": { @@ -40,11 +45,11 @@ "authors": [ { "name": "Ivan Krutov", - "email": "vania-pooh@yandex-team.ru", + "email": "vania-pooh@aerokube.com", "role": "Developer" } ], - "description": "A Codeception adapter for Allure report.", + "description": "Allure Codeception integration", "homepage": "http://allure.qatools.ru/", "keywords": [ "allure", @@ -55,29 +60,35 @@ "steps", "testing" ], - "time": "2018-12-18T19:47:23+00:00" + "support": { + "email": "allure@qameta.io", + "issues": "https://github.com/allure-framework/allure-codeception/issues", + "source": "https://github.com/allure-framework/allure-codeception" + }, + "time": "2021-06-04T13:24:36+00:00" }, { "name": "allure-framework/allure-php-api", - "version": "1.1.4", + "version": "1.3.1", "source": { "type": "git", - "url": "https://github.com/allure-framework/allure-php-adapter-api.git", - "reference": "a462a0da121681577033e13c123b6cc4e89cdc64" + "url": "https://github.com/allure-framework/allure-php-api.git", + "reference": "f64b69afeff472c564a4e2379efb2b69c430ec5a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/allure-framework/allure-php-adapter-api/zipball/a462a0da121681577033e13c123b6cc4e89cdc64", - "reference": "a462a0da121681577033e13c123b6cc4e89cdc64", + "url": "https://api.github.com/repos/allure-framework/allure-php-api/zipball/f64b69afeff472c564a4e2379efb2b69c430ec5a", + "reference": "f64b69afeff472c564a4e2379efb2b69c430ec5a", "shasum": "" }, "require": { - "jms/serializer": ">=0.16.0", - "moontoast/math": ">=1.1.0", - "php": ">=5.4.0", - "phpunit/phpunit": ">=4.0.0", - "ramsey/uuid": ">=3.0.0", - "symfony/http-foundation": ">=2.0" + "jms/serializer": "^1 | ^2 | ^3", + "php": ">=7.1.3", + "ramsey/uuid": "^3 | ^4", + "symfony/mime": "^4.3 | ^5" + }, + "require-dev": { + "phpunit/phpunit": "^7 | ^8 | ^9" }, "type": "library", "autoload": { @@ -107,29 +118,242 @@ "php", "report" ], - "time": "2016-12-07T12:15:46+00:00" + "support": { + "email": "allure@yandex-team.ru", + "issues": "https://github.com/allure-framework/allure-php-api/issues", + "source": "https://github.com/allure-framework/allure-php-api" + }, + "time": "2021-03-26T14:32:27+00:00" + }, + { + "name": "aws/aws-crt-php", + "version": "v1.0.2", + "source": { + "type": "git", + "url": "https://github.com/awslabs/aws-crt-php.git", + "reference": "3942776a8c99209908ee0b287746263725685732" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/awslabs/aws-crt-php/zipball/3942776a8c99209908ee0b287746263725685732", + "reference": "3942776a8c99209908ee0b287746263725685732", + "shasum": "" + }, + "require": { + "php": ">=5.5" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35|^5.4.3" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "AWS SDK Common Runtime Team", + "email": "aws-sdk-common-runtime@amazon.com" + } + ], + "description": "AWS Common Runtime for PHP", + "homepage": "http://aws.amazon.com/sdkforphp", + "keywords": [ + "amazon", + "aws", + "crt", + "sdk" + ], + "support": { + "issues": "https://github.com/awslabs/aws-crt-php/issues", + "source": "https://github.com/awslabs/aws-crt-php/tree/v1.0.2" + }, + "time": "2021-09-03T22:57:30+00:00" + }, + { + "name": "aws/aws-sdk-php", + "version": "3.198.7", + "source": { + "type": "git", + "url": "https://github.com/aws/aws-sdk-php.git", + "reference": "40197a954c9f49557a1b0d49e2a9bd6f7bf6adfc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/40197a954c9f49557a1b0d49e2a9bd6f7bf6adfc", + "reference": "40197a954c9f49557a1b0d49e2a9bd6f7bf6adfc", + "shasum": "" + }, + "require": { + "aws/aws-crt-php": "^1.0.2", + "ext-json": "*", + "ext-pcre": "*", + "ext-simplexml": "*", + "guzzlehttp/guzzle": "^5.3.3|^6.2.1|^7.0", + "guzzlehttp/promises": "^1.4.0", + "guzzlehttp/psr7": "^1.7.0", + "mtdowling/jmespath.php": "^2.6", + "php": ">=5.5" + }, + "require-dev": { + "andrewsville/php-token-reflection": "^1.4", + "aws/aws-php-sns-message-validator": "~1.0", + "behat/behat": "~3.0", + "doctrine/cache": "~1.4", + "ext-dom": "*", + "ext-openssl": "*", + "ext-pcntl": "*", + "ext-sockets": "*", + "nette/neon": "^2.3", + "paragonie/random_compat": ">= 2", + "phpunit/phpunit": "^4.8.35|^5.4.3", + "psr/cache": "^1.0", + "psr/simple-cache": "^1.0", + "sebastian/comparator": "^1.2.3" + }, + "suggest": { + "aws/aws-php-sns-message-validator": "To validate incoming SNS notifications", + "doctrine/cache": "To use the DoctrineCacheAdapter", + "ext-curl": "To send requests using cURL", + "ext-openssl": "Allows working with CloudFront private distributions and verifying received SNS messages", + "ext-sockets": "To use client-side monitoring" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "Aws\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Amazon Web Services", + "homepage": "http://aws.amazon.com" + } + ], + "description": "AWS SDK for PHP - Use Amazon Web Services in your PHP project", + "homepage": "http://aws.amazon.com/sdkforphp", + "keywords": [ + "amazon", + "aws", + "cloud", + "dynamodb", + "ec2", + "glacier", + "s3", + "sdk" + ], + "support": { + "forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80", + "issues": "https://github.com/aws/aws-sdk-php/issues", + "source": "https://github.com/aws/aws-sdk-php/tree/3.198.7" + }, + "time": "2021-10-18T18:17:12+00:00" + }, + { + "name": "beberlei/assert", + "version": "v3.3.1", + "source": { + "type": "git", + "url": "https://github.com/beberlei/assert.git", + "reference": "5e721d7e937ca3ba2cdec1e1adf195f9e5188372" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/beberlei/assert/zipball/5e721d7e937ca3ba2cdec1e1adf195f9e5188372", + "reference": "5e721d7e937ca3ba2cdec1e1adf195f9e5188372", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-json": "*", + "ext-mbstring": "*", + "ext-simplexml": "*", + "php": "^7.0 || ^8.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "*", + "phpstan/phpstan": "*", + "phpunit/phpunit": ">=6.0.0", + "yoast/phpunit-polyfills": "^0.1.0" + }, + "suggest": { + "ext-intl": "Needed to allow Assertion::count(), Assertion::isCountable(), Assertion::minCount(), and Assertion::maxCount() to operate on ResourceBundles" + }, + "type": "library", + "autoload": { + "files": [ + "lib/Assert/functions.php" + ], + "psr-4": { + "Assert\\": "lib/Assert" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-2-Clause" + ], + "authors": [ + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de", + "role": "Lead Developer" + }, + { + "name": "Richard Quadling", + "email": "rquadling@gmail.com", + "role": "Collaborator" + } + ], + "description": "Thin assertion library for input validation in business models.", + "keywords": [ + "assert", + "assertion", + "validation" + ], + "support": { + "issues": "https://github.com/beberlei/assert/issues", + "source": "https://github.com/beberlei/assert/tree/v3.3.1" + }, + "time": "2021-04-18T20:11:03+00:00" }, { "name": "behat/gherkin", - "version": "v4.4.5", + "version": "v4.9.0", "source": { "type": "git", "url": "https://github.com/Behat/Gherkin.git", - "reference": "5c14cff4f955b17d20d088dec1bde61c0539ec74" + "reference": "0bc8d1e30e96183e4f36db9dc79caead300beff4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Behat/Gherkin/zipball/5c14cff4f955b17d20d088dec1bde61c0539ec74", - "reference": "5c14cff4f955b17d20d088dec1bde61c0539ec74", + "url": "https://api.github.com/repos/Behat/Gherkin/zipball/0bc8d1e30e96183e4f36db9dc79caead300beff4", + "reference": "0bc8d1e30e96183e4f36db9dc79caead300beff4", "shasum": "" }, "require": { - "php": ">=5.3.1" + "php": "~7.2|~8.0" }, "require-dev": { - "phpunit/phpunit": "~4.5|~5", - "symfony/phpunit-bridge": "~2.7|~3", - "symfony/yaml": "~2.3|~3" + "cucumber/cucumber": "dev-gherkin-22.0.0", + "phpunit/phpunit": "~8|~9", + "symfony/yaml": "~3|~4|~5" }, "suggest": { "symfony/yaml": "If you want to parse features, represented in YAML files" @@ -137,7 +361,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.4-dev" + "dev-master": "4.x-dev" } }, "autoload": { @@ -156,7 +380,7 @@ "homepage": "http://everzet.com" } ], - "description": "Gherkin DSL parser for PHP 5.3", + "description": "Gherkin DSL parser for PHP", "homepage": "http://behat.org/", "keywords": [ "BDD", @@ -166,157 +390,119 @@ "gherkin", "parser" ], - "time": "2016-10-30T11:50:56+00:00" + "support": { + "issues": "https://github.com/Behat/Gherkin/issues", + "source": "https://github.com/Behat/Gherkin/tree/v4.9.0" + }, + "time": "2021-10-12T13:05:09+00:00" }, { - "name": "cache/cache", - "version": "0.4.0", + "name": "brick/math", + "version": "0.9.3", "source": { "type": "git", - "url": "https://github.com/php-cache/cache.git", - "reference": "902b2e5b54ea57e3a801437748652228c4c58604" + "url": "https://github.com/brick/math.git", + "reference": "ca57d18f028f84f777b2168cd1911b0dee2343ae" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-cache/cache/zipball/902b2e5b54ea57e3a801437748652228c4c58604", - "reference": "902b2e5b54ea57e3a801437748652228c4c58604", + "url": "https://api.github.com/repos/brick/math/zipball/ca57d18f028f84f777b2168cd1911b0dee2343ae", + "reference": "ca57d18f028f84f777b2168cd1911b0dee2343ae", "shasum": "" }, "require": { - "doctrine/cache": "^1.3", - "league/flysystem": "^1.0", - "php": "^5.6 || ^7.0", - "psr/cache": "^1.0", - "psr/log": "^1.0", - "psr/simple-cache": "^1.0" - }, - "conflict": { - "cache/adapter-common": "*", - "cache/apc-adapter": "*", - "cache/apcu-adapter": "*", - "cache/array-adapter": "*", - "cache/chain-adapter": "*", - "cache/doctrine-adapter": "*", - "cache/filesystem-adapter": "*", - "cache/hierarchical-cache": "*", - "cache/illuminate-adapter": "*", - "cache/memcache-adapter": "*", - "cache/memcached-adapter": "*", - "cache/mongodb-adapter": "*", - "cache/predis-adapter": "*", - "cache/psr-6-doctrine-bridge": "*", - "cache/redis-adapter": "*", - "cache/session-handler": "*", - "cache/taggable-cache": "*", - "cache/void-adapter": "*" + "ext-json": "*", + "php": "^7.1 || ^8.0" }, "require-dev": { - "cache/integration-tests": "^0.16", - "defuse/php-encryption": "^2.0", - "illuminate/cache": "^5.4", - "mockery/mockery": "^0.9", - "phpunit/phpunit": "^4.0 || ^5.1", - "predis/predis": "^1.0", - "symfony/cache": "dev-master" - }, - "suggest": { - "ext-apc": "APC extension is required to use the APC Adapter", - "ext-apcu": "APCu extension is required to use the APCu Adapter", - "ext-memcache": "Memcache extension is required to use the Memcache Adapter", - "ext-memcached": "Memcached extension is required to use the Memcached Adapter", - "ext-mongodb": "Mongodb extension required to use the Mongodb adapter", - "ext-redis": "Redis extension is required to use the Redis adapter", - "mongodb/mongodb": "Mongodb lib required to use the Mongodb adapter" + "php-coveralls/php-coveralls": "^2.2", + "phpunit/phpunit": "^7.5.15 || ^8.5 || ^9.0", + "vimeo/psalm": "4.9.2" }, "type": "library", "autoload": { "psr-4": { - "Cache\\": "src/" - }, - "exclude-from-classmap": [ - "**/Tests/" - ] + "Brick\\Math\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], - "authors": [ + "description": "Arbitrary-precision arithmetic library", + "keywords": [ + "Arbitrary-precision", + "BigInteger", + "BigRational", + "arithmetic", + "bigdecimal", + "bignum", + "brick", + "math" + ], + "support": { + "issues": "https://github.com/brick/math/issues", + "source": "https://github.com/brick/math/tree/0.9.3" + }, + "funding": [ { - "name": "Aaron Scherer", - "email": "aequasi@gmail.com", - "homepage": "https://github.com/aequasi" + "url": "https://github.com/BenMorel", + "type": "github" }, { - "name": "Tobias Nyholm", - "email": "tobias.nyholm@gmail.com", - "homepage": "https://github.com/nyholm" + "url": "https://tidelift.com/funding/github/packagist/brick/math", + "type": "tidelift" } ], - "description": "Library of all the php-cache adapters", - "homepage": "http://www.php-cache.com/en/latest/", - "keywords": [ - "cache", - "psr6" - ], - "time": "2017-03-28T16:08:48+00:00" + "time": "2021-08-15T20:50:18+00:00" }, { "name": "codeception/codeception", - "version": "2.3.9", + "version": "4.1.22", "source": { "type": "git", "url": "https://github.com/Codeception/Codeception.git", - "reference": "104f46fa0bde339f1bcc3a375aac21eb36e65a1e" + "reference": "9777ec3690ceedc4bce2ed13af7af4ca4ee3088f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Codeception/Codeception/zipball/104f46fa0bde339f1bcc3a375aac21eb36e65a1e", - "reference": "104f46fa0bde339f1bcc3a375aac21eb36e65a1e", + "url": "https://api.github.com/repos/Codeception/Codeception/zipball/9777ec3690ceedc4bce2ed13af7af4ca4ee3088f", + "reference": "9777ec3690ceedc4bce2ed13af7af4ca4ee3088f", "shasum": "" }, "require": { - "behat/gherkin": "~4.4.0", - "codeception/stub": "^1.0", + "behat/gherkin": "^4.4.0", + "codeception/lib-asserts": "^1.0", + "codeception/phpunit-wrapper": ">6.0.15 <6.1.0 | ^6.6.1 | ^7.7.1 | ^8.1.1 | ^9.0", + "codeception/stub": "^2.0 | ^3.0", + "ext-curl": "*", "ext-json": "*", "ext-mbstring": "*", - "facebook/webdriver": ">=1.1.3 <2.0", - "guzzlehttp/guzzle": ">=4.1.4 <7.0", - "guzzlehttp/psr7": "~1.0", - "php": ">=5.4.0 <8.0", - "phpunit/php-code-coverage": ">=2.2.4 <6.0", - "phpunit/phpunit": ">=4.8.28 <5.0.0 || >=5.6.3 <7.0", - "sebastian/comparator": ">1.1 <3.0", - "sebastian/diff": ">=1.4 <3.0", - "symfony/browser-kit": ">=2.7 <5.0", - "symfony/console": ">=2.7 <5.0", - "symfony/css-selector": ">=2.7 <5.0", - "symfony/dom-crawler": ">=2.7 <5.0", - "symfony/event-dispatcher": ">=2.7 <5.0", - "symfony/finder": ">=2.7 <5.0", - "symfony/yaml": ">=2.7 <5.0" + "guzzlehttp/psr7": "^1.4 | ^2.0", + "php": ">=5.6.0 <9.0", + "symfony/console": ">=2.7 <6.0", + "symfony/css-selector": ">=2.7 <6.0", + "symfony/event-dispatcher": ">=2.7 <6.0", + "symfony/finder": ">=2.7 <6.0", + "symfony/yaml": ">=2.7 <6.0" }, "require-dev": { + "codeception/module-asserts": "1.*@dev", + "codeception/module-cli": "1.*@dev", + "codeception/module-db": "1.*@dev", + "codeception/module-filesystem": "1.*@dev", + "codeception/module-phpbrowser": "1.*@dev", "codeception/specify": "~0.3", - "facebook/graph-sdk": "~5.3", - "flow/jsonpath": "~0.2", + "codeception/util-universalframework": "*@dev", "monolog/monolog": "~1.8", - "pda/pheanstalk": "~3.0", - "php-amqplib/php-amqplib": "~2.4", - "predis/predis": "^1.0", "squizlabs/php_codesniffer": "~2.0", - "symfony/process": ">=2.7 <5.0", - "vlucas/phpdotenv": "^2.4.0" + "symfony/process": ">=2.7 <6.0", + "vlucas/phpdotenv": "^2.0 | ^3.0 | ^4.0 | ^5.0" }, "suggest": { - "aws/aws-sdk-php": "For using AWS Auth in REST module and Queue module", - "codeception/phpbuiltinserver": "Start and stop PHP built-in web server for your tests", "codeception/specify": "BDD-style code blocks", "codeception/verify": "BDD-style assertions", - "flow/jsonpath": "For using JSONPath in REST module", - "league/factory-muffin": "For DataFactory module", - "league/factory-muffin-faker": "For Faker support in DataFactory module", - "phpseclib/phpseclib": "for SFTP option in FTP Module", + "hoa/console": "For interactive console functionality", "stecman/symfony-console-completion": "For BASH autocompletion", "symfony/phpunit-bridge": "For phpunit-bridge support" }, @@ -329,7 +515,7 @@ }, "autoload": { "psr-4": { - "Codeception\\": "src\\Codeception", + "Codeception\\": "src/Codeception", "Codeception\\Extension\\": "ext" } }, @@ -353,75 +539,99 @@ "functional testing", "unit testing" ], - "time": "2018-02-26T23:29:41+00:00" + "support": { + "issues": "https://github.com/Codeception/Codeception/issues", + "source": "https://github.com/Codeception/Codeception/tree/4.1.22" + }, + "funding": [ + { + "url": "https://opencollective.com/codeception", + "type": "open_collective" + } + ], + "time": "2021-08-06T17:15:34+00:00" }, { - "name": "codeception/stub", - "version": "1.0.4", + "name": "codeception/lib-asserts", + "version": "1.13.2", "source": { "type": "git", - "url": "https://github.com/Codeception/Stub.git", - "reference": "681b62348837a5ef07d10d8a226f5bc358cc8805" + "url": "https://github.com/Codeception/lib-asserts.git", + "reference": "184231d5eab66bc69afd6b9429344d80c67a33b6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Codeception/Stub/zipball/681b62348837a5ef07d10d8a226f5bc358cc8805", - "reference": "681b62348837a5ef07d10d8a226f5bc358cc8805", + "url": "https://api.github.com/repos/Codeception/lib-asserts/zipball/184231d5eab66bc69afd6b9429344d80c67a33b6", + "reference": "184231d5eab66bc69afd6b9429344d80c67a33b6", "shasum": "" }, "require": { - "phpunit/phpunit-mock-objects": ">2.3 <7.0" - }, - "require-dev": { - "phpunit/phpunit": ">=4.8 <8.0" + "codeception/phpunit-wrapper": ">6.0.15 <6.1.0 | ^6.6.1 | ^7.7.1 | ^8.0.3 | ^9.0", + "ext-dom": "*", + "php": ">=5.6.0 <9.0" }, "type": "library", "autoload": { - "psr-4": { - "Codeception\\": "src/" - } + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], - "description": "Flexible Stub wrapper for PHPUnit's Mock Builder", - "time": "2018-05-17T09:31:08+00:00" + "authors": [ + { + "name": "Michael Bodnarchuk", + "email": "davert@mail.ua", + "homepage": "http://codegyre.com" + }, + { + "name": "Gintautas Miselis" + }, + { + "name": "Gustavo Nieves", + "homepage": "https://medium.com/@ganieves" + } + ], + "description": "Assertion methods used by Codeception core and Asserts module", + "homepage": "https://codeception.com/", + "keywords": [ + "codeception" + ], + "support": { + "issues": "https://github.com/Codeception/lib-asserts/issues", + "source": "https://github.com/Codeception/lib-asserts/tree/1.13.2" + }, + "time": "2020-10-21T16:26:20+00:00" }, { - "name": "composer/ca-bundle", - "version": "1.2.4", + "name": "codeception/module-asserts", + "version": "1.3.1", "source": { "type": "git", - "url": "https://github.com/composer/ca-bundle.git", - "reference": "10bb96592168a0f8e8f6dcde3532d9fa50b0b527" + "url": "https://github.com/Codeception/module-asserts.git", + "reference": "59374f2fef0cabb9e8ddb53277e85cdca74328de" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/ca-bundle/zipball/10bb96592168a0f8e8f6dcde3532d9fa50b0b527", - "reference": "10bb96592168a0f8e8f6dcde3532d9fa50b0b527", + "url": "https://api.github.com/repos/Codeception/module-asserts/zipball/59374f2fef0cabb9e8ddb53277e85cdca74328de", + "reference": "59374f2fef0cabb9e8ddb53277e85cdca74328de", "shasum": "" }, "require": { - "ext-openssl": "*", - "ext-pcre": "*", - "php": "^5.3.2 || ^7.0 || ^8.0" + "codeception/codeception": "*@dev", + "codeception/lib-asserts": "^1.13.1", + "php": ">=5.6.0 <9.0" }, - "require-dev": { - "phpunit/phpunit": "^4.8.35 || ^5.7 || 6.5 - 8", - "psr/log": "^1.0", - "symfony/process": "^2.5 || ^3.0 || ^4.0" + "conflict": { + "codeception/codeception": "<4.0" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.x-dev" - } - }, "autoload": { - "psr-4": { - "Composer\\CaBundle\\": "src" - } + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -429,75 +639,52 @@ ], "authors": [ { - "name": "Jordi Boggiano", - "email": "j.boggiano@seld.be", - "homepage": "http://seld.be" + "name": "Michael Bodnarchuk" + }, + { + "name": "Gintautas Miselis" + }, + { + "name": "Gustavo Nieves", + "homepage": "https://medium.com/@ganieves" } ], - "description": "Lets you find a path to the system CA bundle, and includes a fallback to the Mozilla CA bundle.", + "description": "Codeception module containing various assertions", + "homepage": "https://codeception.com/", "keywords": [ - "cabundle", - "cacert", - "certificate", - "ssl", - "tls" + "assertions", + "asserts", + "codeception" ], - "time": "2019-08-30T08:44:50+00:00" + "support": { + "issues": "https://github.com/Codeception/module-asserts/issues", + "source": "https://github.com/Codeception/module-asserts/tree/1.3.1" + }, + "time": "2020-10-21T16:48:15+00:00" }, { - "name": "composer/composer", - "version": "1.9.0", + "name": "codeception/module-sequence", + "version": "1.0.1", "source": { "type": "git", - "url": "https://github.com/composer/composer.git", - "reference": "314aa57fdcfc942065996f59fb73a8b3f74f3fa5" + "url": "https://github.com/Codeception/module-sequence.git", + "reference": "b75be26681ae90824cde8f8df785981f293667e1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/composer/zipball/314aa57fdcfc942065996f59fb73a8b3f74f3fa5", - "reference": "314aa57fdcfc942065996f59fb73a8b3f74f3fa5", + "url": "https://api.github.com/repos/Codeception/module-sequence/zipball/b75be26681ae90824cde8f8df785981f293667e1", + "reference": "b75be26681ae90824cde8f8df785981f293667e1", "shasum": "" }, "require": { - "composer/ca-bundle": "^1.0", - "composer/semver": "^1.0", - "composer/spdx-licenses": "^1.2", - "composer/xdebug-handler": "^1.1", - "justinrainbow/json-schema": "^3.0 || ^4.0 || ^5.0", - "php": "^5.3.2 || ^7.0", - "psr/log": "^1.0", - "seld/jsonlint": "^1.4", - "seld/phar-utils": "^1.0", - "symfony/console": "^2.7 || ^3.0 || ^4.0", - "symfony/filesystem": "^2.7 || ^3.0 || ^4.0", - "symfony/finder": "^2.7 || ^3.0 || ^4.0", - "symfony/process": "^2.7 || ^3.0 || ^4.0" - }, - "conflict": { - "symfony/console": "2.8.38" - }, - "require-dev": { - "phpunit/phpunit": "^4.8.35 || ^5.7", - "phpunit/phpunit-mock-objects": "^2.3 || ^3.0" - }, - "suggest": { - "ext-openssl": "Enabling the openssl extension allows you to access https URLs for repositories and packages", - "ext-zip": "Enabling the zip extension allows you to unzip archives", - "ext-zlib": "Allow gzip compression of HTTP requests" + "codeception/codeception": "^4.0", + "php": ">=5.6.0 <9.0" }, - "bin": [ - "bin/composer" - ], "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.9-dev" - } - }, "autoload": { - "psr-4": { - "Composer\\": "src/Composer" - } + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -505,56 +692,47 @@ ], "authors": [ { - "name": "Nils Adermann", - "email": "naderman@naderman.de", - "homepage": "http://www.naderman.de" - }, - { - "name": "Jordi Boggiano", - "email": "j.boggiano@seld.be", - "homepage": "http://seld.be" + "name": "Michael Bodnarchuk" } ], - "description": "Composer helps you declare, manage and install dependencies of PHP projects. It ensures you have the right stack everywhere.", - "homepage": "https://getcomposer.org/", + "description": "Sequence module for Codeception", + "homepage": "http://codeception.com/", "keywords": [ - "autoload", - "dependency", - "package" + "codeception" ], - "time": "2019-08-02T18:55:33+00:00" + "support": { + "issues": "https://github.com/Codeception/module-sequence/issues", + "source": "https://github.com/Codeception/module-sequence/tree/1.0.1" + }, + "time": "2020-10-31T18:36:26+00:00" }, { - "name": "composer/semver", - "version": "1.5.0", + "name": "codeception/module-webdriver", + "version": "1.4.0", "source": { "type": "git", - "url": "https://github.com/composer/semver.git", - "reference": "46d9139568ccb8d9e7cdd4539cab7347568a5e2e" + "url": "https://github.com/Codeception/module-webdriver.git", + "reference": "baa18b7bf70aa024012f967b5ce5021e1faa9151" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/semver/zipball/46d9139568ccb8d9e7cdd4539cab7347568a5e2e", - "reference": "46d9139568ccb8d9e7cdd4539cab7347568a5e2e", + "url": "https://api.github.com/repos/Codeception/module-webdriver/zipball/baa18b7bf70aa024012f967b5ce5021e1faa9151", + "reference": "baa18b7bf70aa024012f967b5ce5021e1faa9151", "shasum": "" }, "require": { - "php": "^5.3.2 || ^7.0" + "codeception/codeception": "^4.0", + "php": ">=5.6.0 <9.0", + "php-webdriver/webdriver": "^1.8.0" }, - "require-dev": { - "phpunit/phpunit": "^4.5 || ^5.0.5", - "phpunit/phpunit-mock-objects": "2.3.0 || ^3.0" + "suggest": { + "codeception/phpbuiltinserver": "Start and stop PHP built-in web server for your tests" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.x-dev" - } - }, "autoload": { - "psr-4": { - "Composer\\Semver\\": "src" - } + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -562,59 +740,55 @@ ], "authors": [ { - "name": "Nils Adermann", - "email": "naderman@naderman.de", - "homepage": "http://www.naderman.de" + "name": "Michael Bodnarchuk" }, { - "name": "Jordi Boggiano", - "email": "j.boggiano@seld.be", - "homepage": "http://seld.be" + "name": "Gintautas Miselis" }, { - "name": "Rob Bast", - "email": "rob.bast@gmail.com", - "homepage": "http://robbast.nl" + "name": "Zaahid Bateson" } ], - "description": "Semver library that offers utilities, version constraint parsing and validation.", + "description": "WebDriver module for Codeception", + "homepage": "http://codeception.com/", "keywords": [ - "semantic", - "semver", - "validation", - "versioning" + "acceptance-testing", + "browser-testing", + "codeception" ], - "time": "2019-03-19T17:25:45+00:00" + "support": { + "issues": "https://github.com/Codeception/module-webdriver/issues", + "source": "https://github.com/Codeception/module-webdriver/tree/1.4.0" + }, + "time": "2021-09-02T12:01:02+00:00" }, { - "name": "composer/spdx-licenses", - "version": "1.5.2", + "name": "codeception/phpunit-wrapper", + "version": "9.0.6", "source": { "type": "git", - "url": "https://github.com/composer/spdx-licenses.git", - "reference": "7ac1e6aec371357df067f8a688c3d6974df68fa5" + "url": "https://github.com/Codeception/phpunit-wrapper.git", + "reference": "b0c06abb3181eedca690170f7ed0fd26a70bfacc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/spdx-licenses/zipball/7ac1e6aec371357df067f8a688c3d6974df68fa5", - "reference": "7ac1e6aec371357df067f8a688c3d6974df68fa5", + "url": "https://api.github.com/repos/Codeception/phpunit-wrapper/zipball/b0c06abb3181eedca690170f7ed0fd26a70bfacc", + "reference": "b0c06abb3181eedca690170f7ed0fd26a70bfacc", "shasum": "" }, "require": { - "php": "^5.3.2 || ^7.0 || ^8.0" + "php": ">=7.2", + "phpunit/phpunit": "^9.0" }, "require-dev": { - "phpunit/phpunit": "^4.8.35 || ^5.7 || 6.5 - 7" + "codeception/specify": "*", + "consolidation/robo": "^3.0.0-alpha3", + "vlucas/phpdotenv": "^3.0" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.x-dev" - } - }, "autoload": { "psr-4": { - "Composer\\Spdx\\": "src" + "Codeception\\PHPUnit\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -623,110 +797,88 @@ ], "authors": [ { - "name": "Nils Adermann", - "email": "naderman@naderman.de", - "homepage": "http://www.naderman.de" - }, - { - "name": "Jordi Boggiano", - "email": "j.boggiano@seld.be", - "homepage": "http://seld.be" + "name": "Davert", + "email": "davert.php@resend.cc" }, { - "name": "Rob Bast", - "email": "rob.bast@gmail.com", - "homepage": "http://robbast.nl" + "name": "Naktibalda" } ], - "description": "SPDX licenses list and validation library.", - "keywords": [ - "license", - "spdx", - "validator" - ], - "time": "2019-07-29T10:31:59+00:00" + "description": "PHPUnit classes used by Codeception", + "support": { + "issues": "https://github.com/Codeception/phpunit-wrapper/issues", + "source": "https://github.com/Codeception/phpunit-wrapper/tree/9.0.6" + }, + "time": "2020-12-28T13:59:47+00:00" }, { - "name": "composer/xdebug-handler", - "version": "1.3.3", + "name": "codeception/stub", + "version": "3.7.0", "source": { "type": "git", - "url": "https://github.com/composer/xdebug-handler.git", - "reference": "46867cbf8ca9fb8d60c506895449eb799db1184f" + "url": "https://github.com/Codeception/Stub.git", + "reference": "468dd5fe659f131fc997f5196aad87512f9b1304" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/46867cbf8ca9fb8d60c506895449eb799db1184f", - "reference": "46867cbf8ca9fb8d60c506895449eb799db1184f", + "url": "https://api.github.com/repos/Codeception/Stub/zipball/468dd5fe659f131fc997f5196aad87512f9b1304", + "reference": "468dd5fe659f131fc997f5196aad87512f9b1304", "shasum": "" }, "require": { - "php": "^5.3.2 || ^7.0", - "psr/log": "^1.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5" + "phpunit/phpunit": "^8.4 | ^9.0" }, "type": "library", "autoload": { "psr-4": { - "Composer\\XdebugHandler\\": "src" + "Codeception\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], - "authors": [ - { - "name": "John Stevenson", - "email": "john-stevenson@blueyonder.co.uk" - } - ], - "description": "Restarts a process without xdebug.", - "keywords": [ - "Xdebug", - "performance" - ], - "time": "2019-05-27T17:52:04+00:00" + "description": "Flexible Stub wrapper for PHPUnit's Mock Builder", + "support": { + "issues": "https://github.com/Codeception/Stub/issues", + "source": "https://github.com/Codeception/Stub/tree/3.7.0" + }, + "time": "2020-07-03T15:54:43+00:00" }, { - "name": "consolidation/annotated-command", - "version": "2.9.1", + "name": "composer/ca-bundle", + "version": "1.2.11", "source": { "type": "git", - "url": "https://github.com/consolidation/annotated-command.git", - "reference": "4bdbb8fa149e1cc1511bd77b0bc4729fd66bccac" + "url": "https://github.com/composer/ca-bundle.git", + "reference": "0b072d51c5a9c6f3412f7ea3ab043d6603cb2582" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/consolidation/annotated-command/zipball/4bdbb8fa149e1cc1511bd77b0bc4729fd66bccac", - "reference": "4bdbb8fa149e1cc1511bd77b0bc4729fd66bccac", + "url": "https://api.github.com/repos/composer/ca-bundle/zipball/0b072d51c5a9c6f3412f7ea3ab043d6603cb2582", + "reference": "0b072d51c5a9c6f3412f7ea3ab043d6603cb2582", "shasum": "" }, "require": { - "consolidation/output-formatters": "^3.1.12", - "php": ">=5.4.0", - "psr/log": "^1", - "symfony/console": "^2.8|^3|^4", - "symfony/event-dispatcher": "^2.5|^3|^4", - "symfony/finder": "^2.5|^3|^4" + "ext-openssl": "*", + "ext-pcre": "*", + "php": "^5.3.2 || ^7.0 || ^8.0" }, "require-dev": { - "g1a/composer-test-scenarios": "^2", - "phpunit/phpunit": "^6", - "satooshi/php-coveralls": "^2", - "squizlabs/php_codesniffer": "^2.7" + "phpstan/phpstan": "^0.12.55", + "psr/log": "^1.0", + "symfony/phpunit-bridge": "^4.2 || ^5", + "symfony/process": "^2.5 || ^3.0 || ^4.0 || ^5.0 || ^6.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.x-dev" + "dev-main": "1.x-dev" } }, "autoload": { "psr-4": { - "Consolidation\\AnnotatedCommand\\": "src" + "Composer\\CaBundle\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -735,52 +887,92 @@ ], "authors": [ { - "name": "Greg Anderson", - "email": "greg.1.anderson@greenknowe.org" + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "Lets you find a path to the system CA bundle, and includes a fallback to the Mozilla CA bundle.", + "keywords": [ + "cabundle", + "cacert", + "certificate", + "ssl", + "tls" + ], + "support": { + "irc": "irc://irc.freenode.org/composer", + "issues": "https://github.com/composer/ca-bundle/issues", + "source": "https://github.com/composer/ca-bundle/tree/1.2.11" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" } ], - "description": "Initialize Symfony Console commands from annotated command class methods.", - "time": "2018-09-19T17:47:18+00:00" + "time": "2021-09-25T20:32:43+00:00" }, { - "name": "consolidation/config", - "version": "1.1.1", + "name": "composer/composer", + "version": "2.1.9", "source": { "type": "git", - "url": "https://github.com/consolidation/config.git", - "reference": "925231dfff32f05b787e1fddb265e789b939cf4c" + "url": "https://github.com/composer/composer.git", + "reference": "e558c88f28d102d497adec4852802c0dc14c7077" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/consolidation/config/zipball/925231dfff32f05b787e1fddb265e789b939cf4c", - "reference": "925231dfff32f05b787e1fddb265e789b939cf4c", + "url": "https://api.github.com/repos/composer/composer/zipball/e558c88f28d102d497adec4852802c0dc14c7077", + "reference": "e558c88f28d102d497adec4852802c0dc14c7077", "shasum": "" }, "require": { - "dflydev/dot-access-data": "^1.1.0", - "grasmash/expander": "^1", - "php": ">=5.4.0" + "composer/ca-bundle": "^1.0", + "composer/metadata-minifier": "^1.0", + "composer/semver": "^3.0", + "composer/spdx-licenses": "^1.2", + "composer/xdebug-handler": "^2.0", + "justinrainbow/json-schema": "^5.2.11", + "php": "^5.3.2 || ^7.0 || ^8.0", + "psr/log": "^1.0", + "react/promise": "^1.2 || ^2.7", + "seld/jsonlint": "^1.4", + "seld/phar-utils": "^1.0", + "symfony/console": "^2.8.52 || ^3.4.35 || ^4.4 || ^5.0 || ^6.0", + "symfony/filesystem": "^2.8.52 || ^3.4.35 || ^4.4 || ^5.0 || ^6.0", + "symfony/finder": "^2.8.52 || ^3.4.35 || ^4.4 || ^5.0 || ^6.0", + "symfony/process": "^2.8.52 || ^3.4.35 || ^4.4 || ^5.0 || ^6.0" }, "require-dev": { - "g1a/composer-test-scenarios": "^1", - "phpunit/phpunit": "^5", - "satooshi/php-coveralls": "^1.0", - "squizlabs/php_codesniffer": "2.*", - "symfony/console": "^2.5|^3|^4", - "symfony/yaml": "^2.8.11|^3|^4" + "phpspec/prophecy": "^1.10", + "symfony/phpunit-bridge": "^4.2 || ^5.0 || ^6.0" }, "suggest": { - "symfony/yaml": "Required to use Consolidation\\Config\\Loader\\YamlConfigLoader" + "ext-openssl": "Enabling the openssl extension allows you to access https URLs for repositories and packages", + "ext-zip": "Enabling the zip extension allows you to unzip archives", + "ext-zlib": "Allow gzip compression of HTTP requests" }, + "bin": [ + "bin/composer" + ], "type": "library", "extra": { "branch-alias": { - "dev-master": "1.x-dev" + "dev-master": "2.1-dev" } }, "autoload": { "psr-4": { - "Consolidation\\Config\\": "src" + "Composer\\": "src/Composer" } }, "notification-url": "https://packagist.org/downloads/", @@ -789,47 +981,75 @@ ], "authors": [ { - "name": "Greg Anderson", - "email": "greg.1.anderson@greenknowe.org" + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "https://www.naderman.de" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "https://seld.be" + } + ], + "description": "Composer helps you declare, manage and install dependencies of PHP projects. It ensures you have the right stack everywhere.", + "homepage": "https://getcomposer.org/", + "keywords": [ + "autoload", + "dependency", + "package" + ], + "support": { + "irc": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/composer/issues", + "source": "https://github.com/composer/composer/tree/2.1.9" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" } ], - "description": "Provide configuration services for a commandline tool.", - "time": "2018-10-24T17:55:35+00:00" + "time": "2021-10-05T07:47:38+00:00" }, { - "name": "consolidation/log", - "version": "1.0.6", + "name": "composer/metadata-minifier", + "version": "1.0.0", "source": { "type": "git", - "url": "https://github.com/consolidation/log.git", - "reference": "dfd8189a771fe047bf3cd669111b2de5f1c79395" + "url": "https://github.com/composer/metadata-minifier.git", + "reference": "c549d23829536f0d0e984aaabbf02af91f443207" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/consolidation/log/zipball/dfd8189a771fe047bf3cd669111b2de5f1c79395", - "reference": "dfd8189a771fe047bf3cd669111b2de5f1c79395", + "url": "https://api.github.com/repos/composer/metadata-minifier/zipball/c549d23829536f0d0e984aaabbf02af91f443207", + "reference": "c549d23829536f0d0e984aaabbf02af91f443207", "shasum": "" }, "require": { - "php": ">=5.5.0", - "psr/log": "~1.0", - "symfony/console": "^2.8|^3|^4" + "php": "^5.3.2 || ^7.0 || ^8.0" }, "require-dev": { - "g1a/composer-test-scenarios": "^1", - "phpunit/phpunit": "4.*", - "satooshi/php-coveralls": "^2", - "squizlabs/php_codesniffer": "2.*" + "composer/composer": "^2", + "phpstan/phpstan": "^0.12.55", + "symfony/phpunit-bridge": "^4.2 || ^5" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.x-dev" + "dev-main": "1.x-dev" } }, "autoload": { "psr-4": { - "Consolidation\\Log\\": "src" + "Composer\\MetadataMinifier\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -838,54 +1058,66 @@ ], "authors": [ { - "name": "Greg Anderson", - "email": "greg.1.anderson@greenknowe.org" + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "Small utility library that handles metadata minification and expansion.", + "keywords": [ + "composer", + "compression" + ], + "support": { + "issues": "https://github.com/composer/metadata-minifier/issues", + "source": "https://github.com/composer/metadata-minifier/tree/1.0.0" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" } ], - "description": "Improved Psr-3 / Psr\\Log logger based on Symfony Console components.", - "time": "2018-05-25T18:14:39+00:00" + "time": "2021-04-07T13:37:33+00:00" }, { - "name": "consolidation/output-formatters", - "version": "3.4.0", + "name": "composer/semver", + "version": "3.2.5", "source": { "type": "git", - "url": "https://github.com/consolidation/output-formatters.git", - "reference": "a942680232094c4a5b21c0b7e54c20cce623ae19" + "url": "https://github.com/composer/semver.git", + "reference": "31f3ea725711245195f62e54ffa402d8ef2fdba9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/consolidation/output-formatters/zipball/a942680232094c4a5b21c0b7e54c20cce623ae19", - "reference": "a942680232094c4a5b21c0b7e54c20cce623ae19", + "url": "https://api.github.com/repos/composer/semver/zipball/31f3ea725711245195f62e54ffa402d8ef2fdba9", + "reference": "31f3ea725711245195f62e54ffa402d8ef2fdba9", "shasum": "" }, "require": { - "dflydev/dot-access-data": "^1.1.0", - "php": ">=5.4.0", - "symfony/console": "^2.8|^3|^4", - "symfony/finder": "^2.5|^3|^4" + "php": "^5.3.2 || ^7.0 || ^8.0" }, "require-dev": { - "g1a/composer-test-scenarios": "^2", - "phpunit/phpunit": "^5.7.27", - "satooshi/php-coveralls": "^2", - "squizlabs/php_codesniffer": "^2.7", - "symfony/console": "3.2.3", - "symfony/var-dumper": "^2.8|^3|^4", - "victorjonsson/markdowndocs": "^1.3" - }, - "suggest": { - "symfony/var-dumper": "For using the var_dump formatter" + "phpstan/phpstan": "^0.12.54", + "symfony/phpunit-bridge": "^4.2 || ^5" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.x-dev" + "dev-main": "3.x-dev" } }, "autoload": { "psr-4": { - "Consolidation\\OutputFormatters\\": "src" + "Composer\\Semver\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -894,125 +1126,78 @@ ], "authors": [ { - "name": "Greg Anderson", - "email": "greg.1.anderson@greenknowe.org" + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "http://www.naderman.de" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + }, + { + "name": "Rob Bast", + "email": "rob.bast@gmail.com", + "homepage": "http://robbast.nl" } ], - "description": "Format text by applying transformations provided by plug-in formatters.", - "time": "2018-10-19T22:35:38+00:00" - }, - { - "name": "consolidation/robo", - "version": "1.3.1", - "source": { - "type": "git", - "url": "https://github.com/consolidation/Robo.git", - "reference": "31f2d2562c4e1dcde70f2659eefd59aa9c7f5b2d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/consolidation/Robo/zipball/31f2d2562c4e1dcde70f2659eefd59aa9c7f5b2d", - "reference": "31f2d2562c4e1dcde70f2659eefd59aa9c7f5b2d", - "shasum": "" - }, - "require": { - "consolidation/annotated-command": "^2.8.2", - "consolidation/config": "^1.0.10", - "consolidation/log": "~1", - "consolidation/output-formatters": "^3.1.13", - "consolidation/self-update": "^1", - "g1a/composer-test-scenarios": "^2", - "grasmash/yaml-expander": "^1.3", - "league/container": "^2.2", - "php": ">=5.5.0", - "symfony/console": "^2.8|^3|^4", - "symfony/event-dispatcher": "^2.5|^3|^4", - "symfony/filesystem": "^2.5|^3|^4", - "symfony/finder": "^2.5|^3|^4", - "symfony/process": "^2.5|^3|^4" - }, - "replace": { - "codegyre/robo": "< 1.0" - }, - "require-dev": { - "codeception/aspect-mock": "^1|^2.1.1", - "codeception/base": "^2.3.7", - "codeception/verify": "^0.3.2", - "goaop/framework": "~2.1.2", - "goaop/parser-reflection": "^1.1.0", - "natxet/cssmin": "3.0.4", - "nikic/php-parser": "^3.1.5", - "patchwork/jsqueeze": "~2", - "pear/archive_tar": "^1.4.2", - "phpunit/php-code-coverage": "~2|~4", - "satooshi/php-coveralls": "^2", - "squizlabs/php_codesniffer": "^2.8" - }, - "suggest": { - "henrikbjorn/lurker": "For monitoring filesystem changes in taskWatch", - "natxet/CssMin": "For minifying CSS files in taskMinify", - "patchwork/jsqueeze": "For minifying JS files in taskMinify", - "pear/archive_tar": "Allows tar archives to be created and extracted in taskPack and taskExtract, respectively." - }, - "bin": [ - "robo" + "description": "Semver library that offers utilities, version constraint parsing and validation.", + "keywords": [ + "semantic", + "semver", + "validation", + "versioning" ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.x-dev", - "dev-state": "1.x-dev" - } - }, - "autoload": { - "psr-4": { - "Robo\\": "src" - } + "support": { + "irc": "irc://irc.freenode.org/composer", + "issues": "https://github.com/composer/semver/issues", + "source": "https://github.com/composer/semver/tree/3.2.5" }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ + "funding": [ { - "name": "Davert", - "email": "davert.php@resend.cc" + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" } ], - "description": "Modern task runner", - "time": "2018-08-17T18:44:18+00:00" + "time": "2021-05-24T12:41:47+00:00" }, { - "name": "consolidation/self-update", - "version": "1.1.5", + "name": "composer/spdx-licenses", + "version": "1.5.5", "source": { "type": "git", - "url": "https://github.com/consolidation/self-update.git", - "reference": "a1c273b14ce334789825a09d06d4c87c0a02ad54" + "url": "https://github.com/composer/spdx-licenses.git", + "reference": "de30328a7af8680efdc03e396aad24befd513200" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/consolidation/self-update/zipball/a1c273b14ce334789825a09d06d4c87c0a02ad54", - "reference": "a1c273b14ce334789825a09d06d4c87c0a02ad54", + "url": "https://api.github.com/repos/composer/spdx-licenses/zipball/de30328a7af8680efdc03e396aad24befd513200", + "reference": "de30328a7af8680efdc03e396aad24befd513200", "shasum": "" }, "require": { - "php": ">=5.5.0", - "symfony/console": "^2.8|^3|^4", - "symfony/filesystem": "^2.5|^3|^4" + "php": "^5.3.2 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7 || 6.5 - 7" }, - "bin": [ - "scripts/release" - ], "type": "library", "extra": { "branch-alias": { - "dev-master": "1.x-dev" + "dev-main": "1.x-dev" } }, "autoload": { "psr-4": { - "SelfUpdate\\": "src" + "Composer\\Spdx\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -1021,76 +1206,146 @@ ], "authors": [ { - "name": "Greg Anderson", - "email": "greg.1.anderson@greenknowe.org" + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "http://www.naderman.de" }, { - "name": "Alexander Menk", - "email": "menk@mestrona.net" + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + }, + { + "name": "Rob Bast", + "email": "rob.bast@gmail.com", + "homepage": "http://robbast.nl" + } + ], + "description": "SPDX licenses list and validation library.", + "keywords": [ + "license", + "spdx", + "validator" + ], + "support": { + "irc": "irc://irc.freenode.org/composer", + "issues": "https://github.com/composer/spdx-licenses/issues", + "source": "https://github.com/composer/spdx-licenses/tree/1.5.5" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" } ], - "description": "Provides a self:update command for Symfony Console applications.", - "time": "2018-10-28T01:52:03+00:00" + "time": "2020-12-03T16:04:16+00:00" }, { - "name": "container-interop/container-interop", - "version": "1.2.0", + "name": "composer/xdebug-handler", + "version": "2.0.2", "source": { "type": "git", - "url": "https://github.com/container-interop/container-interop.git", - "reference": "79cbf1341c22ec75643d841642dd5d6acd83bdb8" + "url": "https://github.com/composer/xdebug-handler.git", + "reference": "84674dd3a7575ba617f5a76d7e9e29a7d3891339" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/container-interop/container-interop/zipball/79cbf1341c22ec75643d841642dd5d6acd83bdb8", - "reference": "79cbf1341c22ec75643d841642dd5d6acd83bdb8", + "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/84674dd3a7575ba617f5a76d7e9e29a7d3891339", + "reference": "84674dd3a7575ba617f5a76d7e9e29a7d3891339", "shasum": "" }, "require": { - "psr/container": "^1.0" + "php": "^5.3.2 || ^7.0 || ^8.0", + "psr/log": "^1 || ^2 || ^3" + }, + "require-dev": { + "phpstan/phpstan": "^0.12.55", + "symfony/phpunit-bridge": "^4.2 || ^5" }, "type": "library", "autoload": { "psr-4": { - "Interop\\Container\\": "src/Interop/Container/" + "Composer\\XdebugHandler\\": "src" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], - "description": "Promoting the interoperability of container objects (DIC, SL, etc.)", - "homepage": "https://github.com/container-interop/container-interop", - "time": "2017-02-14T19:40:03+00:00" + "authors": [ + { + "name": "John Stevenson", + "email": "john-stevenson@blueyonder.co.uk" + } + ], + "description": "Restarts a process without Xdebug.", + "keywords": [ + "Xdebug", + "performance" + ], + "support": { + "irc": "irc://irc.freenode.org/composer", + "issues": "https://github.com/composer/xdebug-handler/issues", + "source": "https://github.com/composer/xdebug-handler/tree/2.0.2" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2021-07-31T17:03:58+00:00" }, { "name": "csharpru/vault-php", - "version": "3.5.3", + "version": "4.2.1", "source": { "type": "git", "url": "https://github.com/CSharpRU/vault-php.git", - "reference": "04be9776310fe7d1afb97795645f95c21e6b4fcf" + "reference": "89b393ecf65f61a44d3a1872547f65085982b481" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/CSharpRU/vault-php/zipball/04be9776310fe7d1afb97795645f95c21e6b4fcf", - "reference": "04be9776310fe7d1afb97795645f95c21e6b4fcf", + "url": "https://api.github.com/repos/CSharpRU/vault-php/zipball/89b393ecf65f61a44d3a1872547f65085982b481", + "reference": "89b393ecf65f61a44d3a1872547f65085982b481", "shasum": "" }, "require": { - "cache/cache": "^0.4.0", - "doctrine/inflector": "~1.1.0", - "guzzlehttp/promises": "^1.3", - "guzzlehttp/psr7": "^1.4", + "ext-json": "*", + "php": "^7.2 || ^8.0", "psr/cache": "^1.0", + "psr/http-client": "^1.0", + "psr/http-factory": "^1.0", "psr/log": "^1.0", "weew/helpers-array": "^1.3" }, "require-dev": { - "codacy/coverage": "^1.1", - "codeception/codeception": "^2.2", - "csharpru/vault-php-guzzle6-transport": "~2.0", - "php-vcr/php-vcr": "^1.3" + "alextartan/guzzle-psr18-adapter": "^1.2 || ^2.0", + "cache/array-adapter": "^1.0", + "codeception/codeception": "^4.1", + "codeception/module-asserts": "^1.3", + "laminas/laminas-diactoros": "^2.3", + "php-vcr/php-vcr": "^1.5", + "symfony/event-dispatcher": "<5.0" + }, + "suggest": { + "cache/array-adapter": "For usage with CachedClient class" }, "type": "library", "autoload": { @@ -1109,31 +1364,48 @@ } ], "description": "Best Vault client for PHP that you can find", - "time": "2018-04-28T04:52:17+00:00" + "keywords": [ + "hashicorp", + "secrets", + "vault" + ], + "support": { + "issues": "https://github.com/CSharpRU/vault-php/issues", + "source": "https://github.com/CSharpRU/vault-php/tree/4.2.1" + }, + "time": "2021-05-21T06:39:35+00:00" }, { - "name": "csharpru/vault-php-guzzle6-transport", - "version": "2.0.4", + "name": "doctrine/annotations", + "version": "1.13.2", "source": { "type": "git", - "url": "https://github.com/CSharpRU/vault-php-guzzle6-transport.git", - "reference": "33c392120ac9f253b62b034e0e8ffbbdb3513bd8" + "url": "https://github.com/doctrine/annotations.git", + "reference": "5b668aef16090008790395c02c893b1ba13f7e08" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/CSharpRU/vault-php-guzzle6-transport/zipball/33c392120ac9f253b62b034e0e8ffbbdb3513bd8", - "reference": "33c392120ac9f253b62b034e0e8ffbbdb3513bd8", + "url": "https://api.github.com/repos/doctrine/annotations/zipball/5b668aef16090008790395c02c893b1ba13f7e08", + "reference": "5b668aef16090008790395c02c893b1ba13f7e08", "shasum": "" }, "require": { - "guzzlehttp/guzzle": "~6.2", - "guzzlehttp/promises": "^1.3", - "guzzlehttp/psr7": "^1.4" + "doctrine/lexer": "1.*", + "ext-tokenizer": "*", + "php": "^7.1 || ^8.0", + "psr/cache": "^1 || ^2 || ^3" + }, + "require-dev": { + "doctrine/cache": "^1.11 || ^2.0", + "doctrine/coding-standard": "^6.0 || ^8.1", + "phpstan/phpstan": "^0.12.20", + "phpunit/phpunit": "^7.5 || ^8.0 || ^9.1.5", + "symfony/cache": "^4.4 || ^5.2" }, "type": "library", "autoload": { "psr-4": { - "VaultTransports\\": "src/" + "Doctrine\\Common\\Annotations\\": "lib/Doctrine/Common/Annotations" } }, "notification-url": "https://packagist.org/downloads/", @@ -1142,39 +1414,69 @@ ], "authors": [ { - "name": "Yaroslav Lukyanov", - "email": "c_sharp@mail.ru" + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" } ], - "description": "Guzzle6 transport for Vault PHP client", - "time": "2019-03-10T06:17:37+00:00" + "description": "Docblock Annotations Parser", + "homepage": "https://www.doctrine-project.org/projects/annotations.html", + "keywords": [ + "annotations", + "docblock", + "parser" + ], + "support": { + "issues": "https://github.com/doctrine/annotations/issues", + "source": "https://github.com/doctrine/annotations/tree/1.13.2" + }, + "time": "2021-08-05T19:00:23+00:00" }, { - "name": "dflydev/dot-access-data", - "version": "v1.1.0", + "name": "doctrine/instantiator", + "version": "1.4.0", "source": { "type": "git", - "url": "https://github.com/dflydev/dflydev-dot-access-data.git", - "reference": "3fbd874921ab2c041e899d044585a2ab9795df8a" + "url": "https://github.com/doctrine/instantiator.git", + "reference": "d56bf6102915de5702778fe20f2de3b2fe570b5b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/dflydev/dflydev-dot-access-data/zipball/3fbd874921ab2c041e899d044585a2ab9795df8a", - "reference": "3fbd874921ab2c041e899d044585a2ab9795df8a", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/d56bf6102915de5702778fe20f2de3b2fe570b5b", + "reference": "d56bf6102915de5702778fe20f2de3b2fe570b5b", "shasum": "" }, "require": { - "php": ">=5.3.2" + "php": "^7.1 || ^8.0" }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - } + "require-dev": { + "doctrine/coding-standard": "^8.0", + "ext-pdo": "*", + "ext-phar": "*", + "phpbench/phpbench": "^0.13 || 1.0.0-alpha2", + "phpstan/phpstan": "^0.12", + "phpstan/phpstan-phpunit": "^0.12", + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0" }, + "type": "library", "autoload": { - "psr-0": { - "Dflydev\\DotAccessData": "src" + "psr-4": { + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" } }, "notification-url": "https://packagist.org/downloads/", @@ -1183,62 +1485,68 @@ ], "authors": [ { - "name": "Dragonfly Development Inc.", - "email": "info@dflydev.com", - "homepage": "http://dflydev.com" + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "https://ocramius.github.io/" + } + ], + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "homepage": "https://www.doctrine-project.org/projects/instantiator.html", + "keywords": [ + "constructor", + "instantiate" + ], + "support": { + "issues": "https://github.com/doctrine/instantiator/issues", + "source": "https://github.com/doctrine/instantiator/tree/1.4.0" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" }, { - "name": "Beau Simensen", - "email": "beau@dflydev.com", - "homepage": "http://beausimensen.com" + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" }, { - "name": "Carlos Frutos", - "email": "carlos@kiwing.it", - "homepage": "https://github.com/cfrutos" + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator", + "type": "tidelift" } ], - "description": "Given a deep data structure, access data by dot notation.", - "homepage": "https://github.com/dflydev/dflydev-dot-access-data", - "keywords": [ - "access", - "data", - "dot", - "notation" - ], - "time": "2017-01-20T21:14:22+00:00" + "time": "2020-11-10T18:47:58+00:00" }, { - "name": "doctrine/annotations", - "version": "v1.4.0", + "name": "doctrine/lexer", + "version": "1.2.1", "source": { "type": "git", - "url": "https://github.com/doctrine/annotations.git", - "reference": "54cacc9b81758b14e3ce750f205a393d52339e97" + "url": "https://github.com/doctrine/lexer.git", + "reference": "e864bbf5904cb8f5bb334f99209b48018522f042" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/annotations/zipball/54cacc9b81758b14e3ce750f205a393d52339e97", - "reference": "54cacc9b81758b14e3ce750f205a393d52339e97", + "url": "https://api.github.com/repos/doctrine/lexer/zipball/e864bbf5904cb8f5bb334f99209b48018522f042", + "reference": "e864bbf5904cb8f5bb334f99209b48018522f042", "shasum": "" }, "require": { - "doctrine/lexer": "1.*", - "php": "^5.6 || ^7.0" + "php": "^7.2 || ^8.0" }, "require-dev": { - "doctrine/cache": "1.*", - "phpunit/phpunit": "^5.7" + "doctrine/coding-standard": "^6.0", + "phpstan/phpstan": "^0.11.8", + "phpunit/phpunit": "^8.2" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.4.x-dev" + "dev-master": "1.2.x-dev" } }, "autoload": { "psr-4": { - "Doctrine\\Common\\Annotations\\": "lib/Doctrine/Common/Annotations" + "Doctrine\\Common\\Lexer\\": "lib/Doctrine/Common/Lexer" } }, "notification-url": "https://packagist.org/downloads/", @@ -1246,70 +1554,97 @@ "MIT" ], "authors": [ - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, { "name": "Guilherme Blanco", "email": "guilhermeblanco@gmail.com" }, { - "name": "Jonathan Wage", - "email": "jonwage@gmail.com" + "name": "Roman Borschel", + "email": "roman@code-factory.org" }, { "name": "Johannes Schmitt", "email": "schmittjoh@gmail.com" } ], - "description": "Docblock Annotations Parser", - "homepage": "http://www.doctrine-project.org", + "description": "PHP Doctrine Lexer parser library that can be used in Top-Down, Recursive Descent Parsers.", + "homepage": "https://www.doctrine-project.org/projects/lexer.html", "keywords": [ "annotations", "docblock", - "parser" + "lexer", + "parser", + "php" + ], + "support": { + "issues": "https://github.com/doctrine/lexer/issues", + "source": "https://github.com/doctrine/lexer/tree/1.2.1" + }, + "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%2Flexer", + "type": "tidelift" + } ], - "time": "2017-02-24T16:22:25+00:00" + "time": "2020-05-25T17:44:05+00:00" }, { - "name": "doctrine/cache", - "version": "v1.6.2", + "name": "guzzlehttp/guzzle", + "version": "7.4.0", "source": { "type": "git", - "url": "https://github.com/doctrine/cache.git", - "reference": "eb152c5100571c7a45470ff2a35095ab3f3b900b" + "url": "https://github.com/guzzle/guzzle.git", + "reference": "868b3571a039f0ebc11ac8f344f4080babe2cb94" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/cache/zipball/eb152c5100571c7a45470ff2a35095ab3f3b900b", - "reference": "eb152c5100571c7a45470ff2a35095ab3f3b900b", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/868b3571a039f0ebc11ac8f344f4080babe2cb94", + "reference": "868b3571a039f0ebc11ac8f344f4080babe2cb94", "shasum": "" }, "require": { - "php": "~5.5|~7.0" + "ext-json": "*", + "guzzlehttp/promises": "^1.5", + "guzzlehttp/psr7": "^1.8.3 || ^2.1", + "php": "^7.2.5 || ^8.0", + "psr/http-client": "^1.0", + "symfony/deprecation-contracts": "^2.2" }, - "conflict": { - "doctrine/common": ">2.2,<2.4" + "provide": { + "psr/http-client-implementation": "1.0" }, "require-dev": { - "phpunit/phpunit": "~4.8|~5.0", - "predis/predis": "~1.0", - "satooshi/php-coveralls": "~0.6" + "bamarni/composer-bin-plugin": "^1.4.1", + "ext-curl": "*", + "php-http/client-integration-tests": "^3.0", + "phpunit/phpunit": "^8.5.5 || ^9.3.5", + "psr/log": "^1.1 || ^2.0 || ^3.0" + }, + "suggest": { + "ext-curl": "Required for CURL handler support", + "ext-intl": "Required for Internationalized Domain Name (IDN) support", + "psr/log": "Required for using the Log middleware" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.6.x-dev" + "dev-master": "7.4-dev" } }, "autoload": { + "files": [ + "src/functions_include.php" + ], "psr-4": { - "Doctrine\\Common\\Cache\\": "lib/Doctrine/Common/Cache" + "GuzzleHttp\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -1318,63 +1653,105 @@ ], "authors": [ { - "name": "Roman Borschel", - "email": "roman@code-factory.org" + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" }, { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" }, { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" + "name": "Jeremy Lindblom", + "email": "jeremeamia@gmail.com", + "homepage": "https://github.com/jeremeamia" }, { - "name": "Jonathan Wage", - "email": "jonwage@gmail.com" + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" }, { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" } ], - "description": "Caching library offering an object-oriented API for many cache backends", - "homepage": "http://www.doctrine-project.org", + "description": "Guzzle is a PHP HTTP client library", "keywords": [ - "cache", - "caching" + "client", + "curl", + "framework", + "http", + "http client", + "psr-18", + "psr-7", + "rest", + "web service" + ], + "support": { + "issues": "https://github.com/guzzle/guzzle/issues", + "source": "https://github.com/guzzle/guzzle/tree/7.4.0" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/guzzle", + "type": "tidelift" + } ], - "time": "2017-07-22T12:49:21+00:00" + "time": "2021-10-18T09:52:00+00:00" }, { - "name": "doctrine/inflector", - "version": "v1.1.0", + "name": "guzzlehttp/promises", + "version": "1.5.0", "source": { "type": "git", - "url": "https://github.com/doctrine/inflector.git", - "reference": "90b2128806bfde671b6952ab8bea493942c1fdae" + "url": "https://github.com/guzzle/promises.git", + "reference": "136a635e2b4a49b9d79e9c8fee267ffb257fdba0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/inflector/zipball/90b2128806bfde671b6952ab8bea493942c1fdae", - "reference": "90b2128806bfde671b6952ab8bea493942c1fdae", + "url": "https://api.github.com/repos/guzzle/promises/zipball/136a635e2b4a49b9d79e9c8fee267ffb257fdba0", + "reference": "136a635e2b4a49b9d79e9c8fee267ffb257fdba0", "shasum": "" }, "require": { - "php": ">=5.3.2" + "php": ">=5.5" }, "require-dev": { - "phpunit/phpunit": "4.*" + "symfony/phpunit-bridge": "^4.4 || ^5.1" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.1.x-dev" + "dev-master": "1.5-dev" } }, "autoload": { - "psr-0": { - "Doctrine\\Common\\Inflector\\": "lib/" + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "GuzzleHttp\\Promise\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -1383,69 +1760,91 @@ ], "authors": [ { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" }, { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" }, { - "name": "Jonathan Wage", - "email": "jonwage@gmail.com" + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" }, { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" } ], - "description": "Common String Manipulations with regard to casing and singular/plural rules.", - "homepage": "http://www.doctrine-project.org", + "description": "Guzzle promises library", "keywords": [ - "inflection", - "pluralize", - "singularize", - "string" + "promise" + ], + "support": { + "issues": "https://github.com/guzzle/promises/issues", + "source": "https://github.com/guzzle/promises/tree/1.5.0" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/promises", + "type": "tidelift" + } ], - "time": "2015-11-06T14:35:42+00:00" + "time": "2021-10-07T13:05:22+00:00" }, { - "name": "doctrine/instantiator", - "version": "1.0.5", + "name": "guzzlehttp/psr7", + "version": "1.8.3", "source": { "type": "git", - "url": "https://github.com/doctrine/instantiator.git", - "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d" + "url": "https://github.com/guzzle/psr7.git", + "reference": "1afdd860a2566ed3c2b0b4a3de6e23434a79ec85" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/8e884e78f9f0eb1329e445619e04456e64d8051d", - "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/1afdd860a2566ed3c2b0b4a3de6e23434a79ec85", + "reference": "1afdd860a2566ed3c2b0b4a3de6e23434a79ec85", "shasum": "" }, "require": { - "php": ">=5.3,<8.0-DEV" + "php": ">=5.4.0", + "psr/http-message": "~1.0", + "ralouphie/getallheaders": "^2.0.5 || ^3.0.0" + }, + "provide": { + "psr/http-message-implementation": "1.0" }, "require-dev": { - "athletic/athletic": "~0.1.8", - "ext-pdo": "*", - "ext-phar": "*", - "phpunit/phpunit": "~4.0", - "squizlabs/php_codesniffer": "~2.0" + "ext-zlib": "*", + "phpunit/phpunit": "~4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.10" + }, + "suggest": { + "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "1.7-dev" } }, "autoload": { + "files": [ + "src/functions_include.php" + ], "psr-4": { - "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + "GuzzleHttp\\Psr7\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -1454,48 +1853,102 @@ ], "authors": [ { - "name": "Marco Pivetta", - "email": "ocramius@gmail.com", - "homepage": "http://ocramius.github.com/" + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" } ], - "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", - "homepage": "https://github.com/doctrine/instantiator", + "description": "PSR-7 message implementation that also provides common utility methods", "keywords": [ - "constructor", - "instantiate" + "http", + "message", + "psr-7", + "request", + "response", + "stream", + "uri", + "url" + ], + "support": { + "issues": "https://github.com/guzzle/psr7/issues", + "source": "https://github.com/guzzle/psr7/tree/1.8.3" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7", + "type": "tidelift" + } ], - "time": "2015-06-14T21:17:01+00:00" + "time": "2021-10-05T13:56:00+00:00" }, { - "name": "doctrine/lexer", - "version": "1.0.2", + "name": "jms/metadata", + "version": "2.5.1", "source": { "type": "git", - "url": "https://github.com/doctrine/lexer.git", - "reference": "1febd6c3ef84253d7c815bed85fc622ad207a9f8" + "url": "https://github.com/schmittjoh/metadata.git", + "reference": "a995e6cef6d6f56a6226e1616a519630e2ef0aeb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/lexer/zipball/1febd6c3ef84253d7c815bed85fc622ad207a9f8", - "reference": "1febd6c3ef84253d7c815bed85fc622ad207a9f8", + "url": "https://api.github.com/repos/schmittjoh/metadata/zipball/a995e6cef6d6f56a6226e1616a519630e2ef0aeb", + "reference": "a995e6cef6d6f56a6226e1616a519630e2ef0aeb", "shasum": "" }, "require": { - "php": ">=5.3.2" + "php": "^7.2|^8.0" }, "require-dev": { - "phpunit/phpunit": "^4.5" + "doctrine/cache": "^1.0", + "doctrine/coding-standard": "^8.0", + "mikey179/vfsstream": "^1.6.7", + "phpunit/phpunit": "^8.5|^9.0", + "psr/container": "^1.0", + "symfony/cache": "^3.1|^4.0|^5.0", + "symfony/dependency-injection": "^3.1|^4.0|^5.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "2.x-dev" } }, "autoload": { "psr-4": { - "Doctrine\\Common\\Lexer\\": "lib/Doctrine/Common/Lexer" + "Metadata\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -1504,161 +1957,154 @@ ], "authors": [ { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" + "name": "Johannes M. Schmitt", + "email": "schmittjoh@gmail.com" }, { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" + "name": "Asmir Mustafic", + "email": "goetas@gmail.com" } ], - "description": "PHP Doctrine Lexer parser library that can be used in Top-Down, Recursive Descent Parsers.", - "homepage": "https://www.doctrine-project.org/projects/lexer.html", + "description": "Class/method/property metadata management in PHP", "keywords": [ "annotations", - "docblock", - "lexer", - "parser", - "php" + "metadata", + "xml", + "yaml" ], - "time": "2019-06-08T11:03:04+00:00" + "support": { + "issues": "https://github.com/schmittjoh/metadata/issues", + "source": "https://github.com/schmittjoh/metadata/tree/2.5.1" + }, + "time": "2021-08-04T19:32:08+00:00" }, { - "name": "facebook/webdriver", - "version": "1.6.0", + "name": "jms/serializer", + "version": "3.15.0", "source": { "type": "git", - "url": "https://github.com/facebook/php-webdriver.git", - "reference": "bd8c740097eb9f2fc3735250fc1912bc811a954e" + "url": "https://github.com/schmittjoh/serializer.git", + "reference": "9d6d9b81889904603383722ca0cd7f7999baeebc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/facebook/php-webdriver/zipball/bd8c740097eb9f2fc3735250fc1912bc811a954e", - "reference": "bd8c740097eb9f2fc3735250fc1912bc811a954e", + "url": "https://api.github.com/repos/schmittjoh/serializer/zipball/9d6d9b81889904603383722ca0cd7f7999baeebc", + "reference": "9d6d9b81889904603383722ca0cd7f7999baeebc", "shasum": "" }, "require": { - "ext-curl": "*", - "ext-json": "*", - "ext-mbstring": "*", - "ext-zip": "*", - "php": "^5.6 || ~7.0", - "symfony/process": "^2.8 || ^3.1 || ^4.0" + "doctrine/annotations": "^1.10.4", + "doctrine/instantiator": "^1.0.3", + "doctrine/lexer": "^1.1", + "jms/metadata": "^2.0", + "php": "^7.2||^8.0", + "phpstan/phpdoc-parser": "^0.4 || ^0.5 || ^1.0" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^2.0", - "jakub-onderka/php-parallel-lint": "^0.9.2", - "php-coveralls/php-coveralls": "^2.0", - "php-mock/php-mock-phpunit": "^1.1", - "phpunit/phpunit": "^5.7", - "sebastian/environment": "^1.3.4 || ^2.0 || ^3.0", - "squizlabs/php_codesniffer": "^2.6", - "symfony/var-dumper": "^3.3 || ^4.0" + "doctrine/coding-standard": "^8.1", + "doctrine/orm": "~2.1", + "doctrine/persistence": "^1.3.3|^2.0|^3.0", + "doctrine/phpcr-odm": "^1.3|^2.0", + "ext-pdo_sqlite": "*", + "jackalope/jackalope-doctrine-dbal": "^1.1.5", + "ocramius/proxy-manager": "^1.0|^2.0", + "phpstan/phpstan": "^0.12.65", + "phpunit/phpunit": "^8.0||^9.0", + "psr/container": "^1.0", + "symfony/dependency-injection": "^3.0|^4.0|^5.0|^6.0", + "symfony/expression-language": "^3.0|^4.0|^5.0|^6.0", + "symfony/filesystem": "^3.0|^4.0|^5.0|^6.0", + "symfony/form": "^3.0|^4.0|^5.0|^6.0", + "symfony/translation": "^3.0|^4.0|^5.0|^6.0", + "symfony/validator": "^3.1.9|^4.0|^5.0|^6.0", + "symfony/yaml": "^3.3|^4.0|^5.0|^6.0", + "twig/twig": "~1.34|~2.4|^3.0" }, "suggest": { - "ext-SimpleXML": "For Firefox profile creation" + "doctrine/collections": "Required if you like to use doctrine collection types as ArrayCollection.", + "symfony/cache": "Required if you like to use cache functionality.", + "symfony/yaml": "Required if you'd like to use the YAML metadata format." }, "type": "library", "extra": { "branch-alias": { - "dev-community": "1.5-dev" + "dev-master": "3.x-dev" } }, "autoload": { "psr-4": { - "Facebook\\WebDriver\\": "lib/" + "JMS\\Serializer\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "Apache-2.0" + "MIT" ], - "description": "A PHP client for Selenium WebDriver", - "homepage": "https://github.com/facebook/php-webdriver", + "authors": [ + { + "name": "Johannes M. Schmitt", + "email": "schmittjoh@gmail.com" + }, + { + "name": "Asmir Mustafic", + "email": "goetas@gmail.com" + } + ], + "description": "Library for (de-)serializing data of any complexity; supports XML, JSON, and YAML.", + "homepage": "http://jmsyst.com/libs/serializer", "keywords": [ - "facebook", - "php", - "selenium", - "webdriver" + "deserialization", + "jaxb", + "json", + "serialization", + "xml" ], - "time": "2018-05-16T17:37:13+00:00" - }, - { - "name": "flow/jsonpath", - "version": "0.4.0", - "source": { - "type": "git", - "url": "https://github.com/FlowCommunications/JSONPath.git", - "reference": "f0222818d5c938e4ab668ab2e2c079bd51a27112" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/FlowCommunications/JSONPath/zipball/f0222818d5c938e4ab668ab2e2c079bd51a27112", - "reference": "f0222818d5c938e4ab668ab2e2c079bd51a27112", - "shasum": "" - }, - "require": { - "php": ">=5.4.0" - }, - "require-dev": { - "peekmo/jsonpath": "dev-master", - "phpunit/phpunit": "^4.0" - }, - "type": "library", - "autoload": { - "psr-0": { - "Flow\\JSONPath": "src/", - "Flow\\JSONPath\\Test": "tests/" - } + "support": { + "issues": "https://github.com/schmittjoh/serializer/issues", + "source": "https://github.com/schmittjoh/serializer/tree/3.15.0" }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ + "funding": [ { - "name": "Stephen Frank", - "email": "stephen@flowsa.com" + "url": "https://github.com/goetas", + "type": "github" } ], - "description": "JSONPath implementation for parsing, searching and flattening arrays", - "time": "2018-03-04T16:39:47+00:00" + "time": "2021-10-14T20:02:48+00:00" }, { - "name": "fzaninotto/faker", - "version": "v1.8.0", + "name": "justinrainbow/json-schema", + "version": "5.2.11", "source": { "type": "git", - "url": "https://github.com/fzaninotto/Faker.git", - "reference": "f72816b43e74063c8b10357394b6bba8cb1c10de" + "url": "https://github.com/justinrainbow/json-schema.git", + "reference": "2ab6744b7296ded80f8cc4f9509abbff393399aa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/fzaninotto/Faker/zipball/f72816b43e74063c8b10357394b6bba8cb1c10de", - "reference": "f72816b43e74063c8b10357394b6bba8cb1c10de", + "url": "https://api.github.com/repos/justinrainbow/json-schema/zipball/2ab6744b7296ded80f8cc4f9509abbff393399aa", + "reference": "2ab6744b7296ded80f8cc4f9509abbff393399aa", "shasum": "" }, "require": { - "php": "^5.3.3 || ^7.0" + "php": ">=5.3.3" }, "require-dev": { - "ext-intl": "*", - "phpunit/phpunit": "^4.8.35 || ^5.7", - "squizlabs/php_codesniffer": "^1.5" + "friendsofphp/php-cs-fixer": "~2.2.20||~2.15.1", + "json-schema/json-schema-test-suite": "1.2.0", + "phpunit/phpunit": "^4.8.35" }, + "bin": [ + "bin/validate-json" + ], "type": "library", "extra": { "branch-alias": { - "dev-master": "1.8-dev" + "dev-master": "5.0.x-dev" } }, "autoload": { "psr-4": { - "Faker\\": "src/Faker/" + "JsonSchema\\": "src/JsonSchema/" } }, "notification-url": "https://packagist.org/downloads/", @@ -1667,131 +2113,196 @@ ], "authors": [ { - "name": "François Zaninotto" + "name": "Bruno Prieto Reis", + "email": "bruno.p.reis@gmail.com" + }, + { + "name": "Justin Rainbow", + "email": "justin.rainbow@gmail.com" + }, + { + "name": "Igor Wiedler", + "email": "igor@wiedler.ch" + }, + { + "name": "Robert Schönthal", + "email": "seroscho@googlemail.com" } ], - "description": "Faker is a PHP library that generates fake data for you.", + "description": "A library to validate a json schema.", + "homepage": "https://github.com/justinrainbow/json-schema", "keywords": [ - "data", - "faker", - "fixtures" + "json", + "schema" ], - "time": "2018-07-12T10:23:15+00:00" - }, - { - "name": "g1a/composer-test-scenarios", - "version": "2.2.0", - "source": { - "type": "git", - "url": "https://github.com/g1a/composer-test-scenarios.git", - "reference": "a166fd15191aceab89f30c097e694b7cf3db4880" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/g1a/composer-test-scenarios/zipball/a166fd15191aceab89f30c097e694b7cf3db4880", - "reference": "a166fd15191aceab89f30c097e694b7cf3db4880", - "shasum": "" + "support": { + "issues": "https://github.com/justinrainbow/json-schema/issues", + "source": "https://github.com/justinrainbow/json-schema/tree/5.2.11" }, - "bin": [ - "scripts/create-scenario", - "scripts/dependency-licenses", - "scripts/install-scenario" - ], - "type": "library", - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Greg Anderson", - "email": "greg.1.anderson@greenknowe.org" - } - ], - "description": "Useful scripts for testing multiple sets of Composer dependencies.", - "time": "2018-08-08T23:37:23+00:00" + "time": "2021-07-22T09:24:00+00:00" }, { - "name": "grasmash/expander", - "version": "1.0.0", + "name": "laminas/laminas-diactoros", + "version": "2.8.0", "source": { "type": "git", - "url": "https://github.com/grasmash/expander.git", - "reference": "95d6037344a4be1dd5f8e0b0b2571a28c397578f" + "url": "https://github.com/laminas/laminas-diactoros.git", + "reference": "0c26ef1d95b6d7e6e3943a243ba3dc0797227199" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/grasmash/expander/zipball/95d6037344a4be1dd5f8e0b0b2571a28c397578f", - "reference": "95d6037344a4be1dd5f8e0b0b2571a28c397578f", + "url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/0c26ef1d95b6d7e6e3943a243ba3dc0797227199", + "reference": "0c26ef1d95b6d7e6e3943a243ba3dc0797227199", "shasum": "" }, "require": { - "dflydev/dot-access-data": "^1.1.0", - "php": ">=5.4" + "php": "^7.3 || ~8.0.0 || ~8.1.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0" + }, + "conflict": { + "phpspec/prophecy": "<1.9.0", + "zendframework/zend-diactoros": "*" + }, + "provide": { + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" }, "require-dev": { - "greg-1-anderson/composer-test-scenarios": "^1", - "phpunit/phpunit": "^4|^5.5.4", - "satooshi/php-coveralls": "^1.0.2|dev-master", - "squizlabs/php_codesniffer": "^2.7" + "ext-curl": "*", + "ext-dom": "*", + "ext-gd": "*", + "ext-libxml": "*", + "http-interop/http-factory-tests": "^0.8.0", + "laminas/laminas-coding-standard": "~1.0.0", + "php-http/psr7-integration-tests": "^1.1", + "phpspec/prophecy-phpunit": "^2.0", + "phpunit/phpunit": "^9.1", + "psalm/plugin-phpunit": "^0.14.0", + "vimeo/psalm": "^4.3" }, "type": "library", "extra": { - "branch-alias": { - "dev-master": "1.x-dev" + "laminas": { + "config-provider": "Laminas\\Diactoros\\ConfigProvider", + "module": "Laminas\\Diactoros" } }, "autoload": { + "files": [ + "src/functions/create_uploaded_file.php", + "src/functions/marshal_headers_from_sapi.php", + "src/functions/marshal_method_from_sapi.php", + "src/functions/marshal_protocol_version_from_sapi.php", + "src/functions/marshal_uri_from_sapi.php", + "src/functions/normalize_server.php", + "src/functions/normalize_uploaded_files.php", + "src/functions/parse_cookie_header.php", + "src/functions/create_uploaded_file.legacy.php", + "src/functions/marshal_headers_from_sapi.legacy.php", + "src/functions/marshal_method_from_sapi.legacy.php", + "src/functions/marshal_protocol_version_from_sapi.legacy.php", + "src/functions/marshal_uri_from_sapi.legacy.php", + "src/functions/normalize_server.legacy.php", + "src/functions/normalize_uploaded_files.legacy.php", + "src/functions/parse_cookie_header.legacy.php" + ], "psr-4": { - "Grasmash\\Expander\\": "src/" + "Laminas\\Diactoros\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], - "authors": [ + "description": "PSR HTTP Message implementations", + "homepage": "https://laminas.dev", + "keywords": [ + "http", + "laminas", + "psr", + "psr-17", + "psr-7" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-diactoros/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-diactoros/issues", + "rss": "https://github.com/laminas/laminas-diactoros/releases.atom", + "source": "https://github.com/laminas/laminas-diactoros" + }, + "funding": [ { - "name": "Matthew Grasmick" + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" } ], - "description": "Expands internal property references in PHP arrays file.", - "time": "2017-12-21T22:14:55+00:00" + "time": "2021-09-22T03:54:36+00:00" }, { - "name": "grasmash/yaml-expander", - "version": "1.4.0", + "name": "monolog/monolog", + "version": "2.3.5", "source": { "type": "git", - "url": "https://github.com/grasmash/yaml-expander.git", - "reference": "3f0f6001ae707a24f4d9733958d77d92bf9693b1" + "url": "https://github.com/Seldaek/monolog.git", + "reference": "fd4380d6fc37626e2f799f29d91195040137eba9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/grasmash/yaml-expander/zipball/3f0f6001ae707a24f4d9733958d77d92bf9693b1", - "reference": "3f0f6001ae707a24f4d9733958d77d92bf9693b1", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/fd4380d6fc37626e2f799f29d91195040137eba9", + "reference": "fd4380d6fc37626e2f799f29d91195040137eba9", "shasum": "" }, "require": { - "dflydev/dot-access-data": "^1.1.0", - "php": ">=5.4", - "symfony/yaml": "^2.8.11|^3|^4" + "php": ">=7.2", + "psr/log": "^1.0.1 || ^2.0 || ^3.0" + }, + "provide": { + "psr/log-implementation": "1.0.0 || 2.0.0 || 3.0.0" }, "require-dev": { - "greg-1-anderson/composer-test-scenarios": "^1", - "phpunit/phpunit": "^4.8|^5.5.4", - "satooshi/php-coveralls": "^1.0.2|dev-master", - "squizlabs/php_codesniffer": "^2.7" + "aws/aws-sdk-php": "^2.4.9 || ^3.0", + "doctrine/couchdb": "~1.0@dev", + "elasticsearch/elasticsearch": "^7", + "graylog2/gelf-php": "^1.4.2", + "mongodb/mongodb": "^1.8", + "php-amqplib/php-amqplib": "~2.4 || ^3", + "php-console/php-console": "^3.1.3", + "phpspec/prophecy": "^1.6.1", + "phpstan/phpstan": "^0.12.91", + "phpunit/phpunit": "^8.5", + "predis/predis": "^1.1", + "rollbar/rollbar": "^1.3", + "ruflin/elastica": ">=0.90@dev", + "swiftmailer/swiftmailer": "^5.3|^6.0" + }, + "suggest": { + "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB", + "doctrine/couchdb": "Allow sending log messages to a CouchDB server", + "elasticsearch/elasticsearch": "Allow sending log messages to an Elasticsearch server via official client", + "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", + "ext-curl": "Required to send log messages using the IFTTTHandler, the LogglyHandler, the SendGridHandler, the SlackWebhookHandler or the TelegramBotHandler", + "ext-mbstring": "Allow to work properly with unicode symbols", + "ext-mongodb": "Allow sending log messages to a MongoDB server (via driver)", + "ext-openssl": "Required to send log messages using SSL", + "ext-sockets": "Allow sending log messages to a Syslog server (via UDP driver)", + "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server", + "mongodb/mongodb": "Allow sending log messages to a MongoDB server (via library)", + "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib", + "php-console/php-console": "Allow sending log messages to Google Chrome", + "rollbar/rollbar": "Allow sending log messages to Rollbar", + "ruflin/elastica": "Allow sending log messages to an Elastic Search server" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.x-dev" + "dev-main": "2.x-dev" } }, "autoload": { "psr-4": { - "Grasmash\\YamlExpander\\": "src/" + "Monolog\\": "src/Monolog" } }, "notification-url": "https://packagist.org/downloads/", @@ -1800,51 +2311,71 @@ ], "authors": [ { - "name": "Matthew Grasmick" + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "https://seld.be" + } + ], + "description": "Sends your logs to files, sockets, inboxes, databases and various web services", + "homepage": "https://github.com/Seldaek/monolog", + "keywords": [ + "log", + "logging", + "psr-3" + ], + "support": { + "issues": "https://github.com/Seldaek/monolog/issues", + "source": "https://github.com/Seldaek/monolog/tree/2.3.5" + }, + "funding": [ + { + "url": "https://github.com/Seldaek", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/monolog/monolog", + "type": "tidelift" } ], - "description": "Expands internal property references in a yaml file.", - "time": "2017-12-16T16:06:03+00:00" + "time": "2021-10-01T21:08:31+00:00" }, { - "name": "guzzlehttp/guzzle", - "version": "6.3.3", + "name": "mtdowling/jmespath.php", + "version": "2.6.1", "source": { "type": "git", - "url": "https://github.com/guzzle/guzzle.git", - "reference": "407b0cb880ace85c9b63c5f9551db498cb2d50ba" + "url": "https://github.com/jmespath/jmespath.php.git", + "reference": "9b87907a81b87bc76d19a7fb2d61e61486ee9edb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/407b0cb880ace85c9b63c5f9551db498cb2d50ba", - "reference": "407b0cb880ace85c9b63c5f9551db498cb2d50ba", + "url": "https://api.github.com/repos/jmespath/jmespath.php/zipball/9b87907a81b87bc76d19a7fb2d61e61486ee9edb", + "reference": "9b87907a81b87bc76d19a7fb2d61e61486ee9edb", "shasum": "" }, "require": { - "guzzlehttp/promises": "^1.0", - "guzzlehttp/psr7": "^1.4", - "php": ">=5.5" + "php": "^5.4 || ^7.0 || ^8.0", + "symfony/polyfill-mbstring": "^1.17" }, "require-dev": { - "ext-curl": "*", - "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.4 || ^7.0", - "psr/log": "^1.0" - }, - "suggest": { - "psr/log": "Required for using the Log middleware" + "composer/xdebug-handler": "^1.4 || ^2.0", + "phpunit/phpunit": "^4.8.36 || ^7.5.15" }, + "bin": [ + "bin/jp.php" + ], "type": "library", "extra": { "branch-alias": { - "dev-master": "6.3-dev" + "dev-master": "2.6-dev" } }, "autoload": { "files": [ - "src/functions_include.php" + "src/JmesPath.php" ], "psr-4": { - "GuzzleHttp\\": "src/" + "JmesPath\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -1858,165 +2389,42 @@ "homepage": "https://github.com/mtdowling" } ], - "description": "Guzzle is a PHP HTTP client library", - "homepage": "http://guzzlephp.org/", + "description": "Declaratively specify how to extract elements from a JSON document", "keywords": [ - "client", - "curl", - "framework", - "http", - "http client", - "rest", - "web service" + "json", + "jsonpath" ], - "time": "2018-04-22T15:46:56+00:00" - }, - { - "name": "guzzlehttp/promises", - "version": "v1.3.1", - "source": { - "type": "git", - "url": "https://github.com/guzzle/promises.git", - "reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/guzzle/promises/zipball/a59da6cf61d80060647ff4d3eb2c03a2bc694646", - "reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646", - "shasum": "" - }, - "require": { - "php": ">=5.5.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.4-dev" - } + "support": { + "issues": "https://github.com/jmespath/jmespath.php/issues", + "source": "https://github.com/jmespath/jmespath.php/tree/2.6.1" }, - "autoload": { - "psr-4": { - "GuzzleHttp\\Promise\\": "src/" - }, - "files": [ - "src/functions_include.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Michael Dowling", - "email": "mtdowling@gmail.com", - "homepage": "https://github.com/mtdowling" - } - ], - "description": "Guzzle promises library", - "keywords": [ - "promise" - ], - "time": "2016-12-20T10:07:11+00:00" + "time": "2021-06-14T00:11:39+00:00" }, { - "name": "guzzlehttp/psr7", - "version": "1.4.2", - "source": { - "type": "git", - "url": "https://github.com/guzzle/psr7.git", - "reference": "f5b8a8512e2b58b0071a7280e39f14f72e05d87c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/f5b8a8512e2b58b0071a7280e39f14f72e05d87c", - "reference": "f5b8a8512e2b58b0071a7280e39f14f72e05d87c", - "shasum": "" - }, - "require": { - "php": ">=5.4.0", - "psr/http-message": "~1.0" - }, - "provide": { - "psr/http-message-implementation": "1.0" - }, - "require-dev": { - "phpunit/phpunit": "~4.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.4-dev" - } - }, - "autoload": { - "psr-4": { - "GuzzleHttp\\Psr7\\": "src/" - }, - "files": [ - "src/functions_include.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Michael Dowling", - "email": "mtdowling@gmail.com", - "homepage": "https://github.com/mtdowling" - }, - { - "name": "Tobias Schultze", - "homepage": "https://github.com/Tobion" - } - ], - "description": "PSR-7 message implementation that also provides common utility methods", - "keywords": [ - "http", - "message", - "request", - "response", - "stream", - "uri", - "url" - ], - "time": "2017-03-20T17:10:46+00:00" - }, - { - "name": "jms/metadata", - "version": "1.7.0", + "name": "mustache/mustache", + "version": "v2.13.0", "source": { "type": "git", - "url": "https://github.com/schmittjoh/metadata.git", - "reference": "e5854ab1aa643623dc64adde718a8eec32b957a8" + "url": "https://github.com/bobthecow/mustache.php.git", + "reference": "e95c5a008c23d3151d59ea72484d4f72049ab7f4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/schmittjoh/metadata/zipball/e5854ab1aa643623dc64adde718a8eec32b957a8", - "reference": "e5854ab1aa643623dc64adde718a8eec32b957a8", + "url": "https://api.github.com/repos/bobthecow/mustache.php/zipball/e95c5a008c23d3151d59ea72484d4f72049ab7f4", + "reference": "e95c5a008c23d3151d59ea72484d4f72049ab7f4", "shasum": "" }, "require": { - "php": ">=5.3.0" + "php": ">=5.2.4" }, "require-dev": { - "doctrine/cache": "~1.0", - "symfony/cache": "~3.1" + "friendsofphp/php-cs-fixer": "~1.11", + "phpunit/phpunit": "~3.7|~4.0|~5.0" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.5.x-dev" - } - }, "autoload": { "psr-0": { - "Metadata\\": "src/" + "Mustache": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -2025,600 +2433,164 @@ ], "authors": [ { - "name": "Asmir Mustafic", - "email": "goetas@gmail.com" - }, - { - "name": "Johannes M. Schmitt", - "email": "schmittjoh@gmail.com" + "name": "Justin Hileman", + "email": "justin@justinhileman.info", + "homepage": "http://justinhileman.com" } ], - "description": "Class/method/property metadata management in PHP", + "description": "A Mustache implementation in PHP.", + "homepage": "https://github.com/bobthecow/mustache.php", "keywords": [ - "annotations", - "metadata", - "xml", - "yaml" + "mustache", + "templating" ], - "time": "2018-10-26T12:40:10+00:00" - }, - { - "name": "jms/parser-lib", - "version": "1.0.0", - "source": { - "type": "git", - "url": "https://github.com/schmittjoh/parser-lib.git", - "reference": "c509473bc1b4866415627af0e1c6cc8ac97fa51d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/schmittjoh/parser-lib/zipball/c509473bc1b4866415627af0e1c6cc8ac97fa51d", - "reference": "c509473bc1b4866415627af0e1c6cc8ac97fa51d", - "shasum": "" - }, - "require": { - "phpoption/phpoption": ">=0.9,<2.0-dev" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - } + "support": { + "issues": "https://github.com/bobthecow/mustache.php/issues", + "source": "https://github.com/bobthecow/mustache.php/tree/master" }, - "autoload": { - "psr-0": { - "JMS\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "Apache2" - ], - "description": "A library for easily creating recursive-descent parsers.", - "time": "2012-11-18T18:08:43+00:00" + "time": "2019-11-23T21:40:31+00:00" }, { - "name": "jms/serializer", - "version": "1.14.0", - "source": { - "type": "git", - "url": "https://github.com/schmittjoh/serializer.git", - "reference": "ee96d57024af9a7716d56fcbe3aa94b3d030f3ca" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/schmittjoh/serializer/zipball/ee96d57024af9a7716d56fcbe3aa94b3d030f3ca", - "reference": "ee96d57024af9a7716d56fcbe3aa94b3d030f3ca", - "shasum": "" - }, - "require": { - "doctrine/annotations": "^1.0", - "doctrine/instantiator": "^1.0.3", - "jms/metadata": "^1.3", - "jms/parser-lib": "1.*", - "php": "^5.5|^7.0", - "phpcollection/phpcollection": "~0.1", - "phpoption/phpoption": "^1.1" - }, - "conflict": { - "twig/twig": "<1.12" - }, - "require-dev": { - "doctrine/orm": "~2.1", - "doctrine/phpcr-odm": "^1.3|^2.0", - "ext-pdo_sqlite": "*", - "jackalope/jackalope-doctrine-dbal": "^1.1.5", - "phpunit/phpunit": "^4.8|^5.0", - "propel/propel1": "~1.7", - "psr/container": "^1.0", - "symfony/dependency-injection": "^2.7|^3.3|^4.0", - "symfony/expression-language": "^2.6|^3.0", - "symfony/filesystem": "^2.1", - "symfony/form": "~2.1|^3.0", - "symfony/translation": "^2.1|^3.0", - "symfony/validator": "^2.2|^3.0", - "symfony/yaml": "^2.1|^3.0", - "twig/twig": "~1.12|~2.0" - }, - "suggest": { - "doctrine/cache": "Required if you like to use cache functionality.", - "doctrine/collections": "Required if you like to use doctrine collection types as ArrayCollection.", - "symfony/yaml": "Required if you'd like to serialize data to YAML format." - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-1.x": "1.14-dev" - } - }, - "autoload": { - "psr-0": { - "JMS\\Serializer": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Asmir Mustafic", - "email": "goetas@gmail.com" - }, - { - "name": "Johannes M. Schmitt", - "email": "schmittjoh@gmail.com" - } - ], - "description": "Library for (de-)serializing data of any complexity; supports XML, JSON, and YAML.", - "homepage": "http://jmsyst.com/libs/serializer", - "keywords": [ - "deserialization", - "jaxb", - "json", - "serialization", - "xml" - ], - "time": "2019-04-17T08:12:16+00:00" - }, - { - "name": "justinrainbow/json-schema", - "version": "5.2.8", - "source": { - "type": "git", - "url": "https://github.com/justinrainbow/json-schema.git", - "reference": "dcb6e1006bb5fd1e392b4daa68932880f37550d4" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/justinrainbow/json-schema/zipball/dcb6e1006bb5fd1e392b4daa68932880f37550d4", - "reference": "dcb6e1006bb5fd1e392b4daa68932880f37550d4", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "~2.2.20", - "json-schema/json-schema-test-suite": "1.2.0", - "phpunit/phpunit": "^4.8.35" - }, - "bin": [ - "bin/validate-json" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "JsonSchema\\": "src/JsonSchema/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Bruno Prieto Reis", - "email": "bruno.p.reis@gmail.com" - }, - { - "name": "Justin Rainbow", - "email": "justin.rainbow@gmail.com" - }, - { - "name": "Igor Wiedler", - "email": "igor@wiedler.ch" - }, - { - "name": "Robert Schönthal", - "email": "seroscho@googlemail.com" - } - ], - "description": "A library to validate a json schema.", - "homepage": "https://github.com/justinrainbow/json-schema", - "keywords": [ - "json", - "schema" - ], - "time": "2019-01-14T23:55:14+00:00" - }, - { - "name": "league/container", - "version": "2.4.1", + "name": "myclabs/deep-copy", + "version": "1.10.2", "source": { "type": "git", - "url": "https://github.com/thephpleague/container.git", - "reference": "43f35abd03a12977a60ffd7095efd6a7808488c0" + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "776f831124e9c62e1a2c601ecc52e776d8bb7220" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/container/zipball/43f35abd03a12977a60ffd7095efd6a7808488c0", - "reference": "43f35abd03a12977a60ffd7095efd6a7808488c0", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/776f831124e9c62e1a2c601ecc52e776d8bb7220", + "reference": "776f831124e9c62e1a2c601ecc52e776d8bb7220", "shasum": "" }, "require": { - "container-interop/container-interop": "^1.2", - "php": "^5.4.0 || ^7.0" - }, - "provide": { - "container-interop/container-interop-implementation": "^1.2", - "psr/container-implementation": "^1.0" + "php": "^7.1 || ^8.0" }, "replace": { - "orno/di": "~2.0" + "myclabs/deep-copy": "self.version" }, "require-dev": { - "phpunit/phpunit": "4.*" + "doctrine/collections": "^1.0", + "doctrine/common": "^2.6", + "phpunit/phpunit": "^7.1" }, "type": "library", - "extra": { - "branch-alias": { - "dev-2.x": "2.x-dev", - "dev-1.x": "1.x-dev" - } - }, "autoload": { + "files": [ + "src/DeepCopy/deep_copy.php" + ], "psr-4": { - "League\\Container\\": "src" + "DeepCopy\\": "src/DeepCopy/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], - "authors": [ - { - "name": "Phil Bennett", - "email": "philipobenito@gmail.com", - "homepage": "http://www.philipobenito.com", - "role": "Developer" - } - ], - "description": "A fast and intuitive dependency injection container.", - "homepage": "https://github.com/thephpleague/container", + "description": "Create deep copies (clones) of your objects", "keywords": [ - "container", - "dependency", - "di", - "injection", - "league", - "provider", - "service" + "clone", + "copy", + "duplicate", + "object", + "object graph" ], - "time": "2017-05-10T09:20:27+00:00" - }, - { - "name": "league/flysystem", - "version": "1.0.53", - "source": { - "type": "git", - "url": "https://github.com/thephpleague/flysystem.git", - "reference": "08e12b7628f035600634a5e76d95b5eb66cea674" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/08e12b7628f035600634a5e76d95b5eb66cea674", - "reference": "08e12b7628f035600634a5e76d95b5eb66cea674", - "shasum": "" - }, - "require": { - "ext-fileinfo": "*", - "php": ">=5.5.9" + "support": { + "issues": "https://github.com/myclabs/DeepCopy/issues", + "source": "https://github.com/myclabs/DeepCopy/tree/1.10.2" }, - "conflict": { - "league/flysystem-sftp": "<1.0.6" - }, - "require-dev": { - "phpspec/phpspec": "^3.4", - "phpunit/phpunit": "^5.7.10" - }, - "suggest": { - "ext-fileinfo": "Required for MimeType", - "ext-ftp": "Allows you to use FTP server storage", - "ext-openssl": "Allows you to use FTPS server storage", - "league/flysystem-aws-s3-v2": "Allows you to use S3 storage with AWS SDK v2", - "league/flysystem-aws-s3-v3": "Allows you to use S3 storage with AWS SDK v3", - "league/flysystem-azure": "Allows you to use Windows Azure Blob storage", - "league/flysystem-cached-adapter": "Flysystem adapter decorator for metadata caching", - "league/flysystem-eventable-filesystem": "Allows you to use EventableFilesystem", - "league/flysystem-rackspace": "Allows you to use Rackspace Cloud Files", - "league/flysystem-sftp": "Allows you to use SFTP server storage via phpseclib", - "league/flysystem-webdav": "Allows you to use WebDAV storage", - "league/flysystem-ziparchive": "Allows you to use ZipArchive adapter", - "spatie/flysystem-dropbox": "Allows you to use Dropbox storage", - "srmklive/flysystem-dropbox-v2": "Allows you to use Dropbox storage for PHP 5 applications" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.1-dev" - } - }, - "autoload": { - "psr-4": { - "League\\Flysystem\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ + "funding": [ { - "name": "Frank de Jonge", - "email": "info@frenky.net" + "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", + "type": "tidelift" } ], - "description": "Filesystem abstraction: Many filesystems, one API.", - "keywords": [ - "Cloud Files", - "WebDAV", - "abstraction", - "aws", - "cloud", - "copy.com", - "dropbox", - "file systems", - "files", - "filesystem", - "filesystems", - "ftp", - "rackspace", - "remote", - "s3", - "sftp", - "storage" - ], - "time": "2019-06-18T20:09:29+00:00" + "time": "2020-11-13T09:40:50+00:00" }, { - "name": "monolog/monolog", - "version": "1.24.0", + "name": "nikic/php-parser", + "version": "v4.13.0", "source": { "type": "git", - "url": "https://github.com/Seldaek/monolog.git", - "reference": "bfc9ebb28f97e7a24c45bdc3f0ff482e47bb0266" + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "50953a2691a922aa1769461637869a0a2faa3f53" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/monolog/zipball/bfc9ebb28f97e7a24c45bdc3f0ff482e47bb0266", - "reference": "bfc9ebb28f97e7a24c45bdc3f0ff482e47bb0266", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/50953a2691a922aa1769461637869a0a2faa3f53", + "reference": "50953a2691a922aa1769461637869a0a2faa3f53", "shasum": "" }, "require": { - "php": ">=5.3.0", - "psr/log": "~1.0" - }, - "provide": { - "psr/log-implementation": "1.0.0" + "ext-tokenizer": "*", + "php": ">=7.0" }, "require-dev": { - "aws/aws-sdk-php": "^2.4.9 || ^3.0", - "doctrine/couchdb": "~1.0@dev", - "graylog2/gelf-php": "~1.0", - "jakub-onderka/php-parallel-lint": "0.9", - "php-amqplib/php-amqplib": "~2.4", - "php-console/php-console": "^3.1.3", - "phpunit/phpunit": "~4.5", - "phpunit/phpunit-mock-objects": "2.3.0", - "ruflin/elastica": ">=0.90 <3.0", - "sentry/sentry": "^0.13", - "swiftmailer/swiftmailer": "^5.3|^6.0" - }, - "suggest": { - "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB", - "doctrine/couchdb": "Allow sending log messages to a CouchDB server", - "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", - "ext-mongo": "Allow sending log messages to a MongoDB server", - "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server", - "mongodb/mongodb": "Allow sending log messages to a MongoDB server via PHP Driver", - "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib", - "php-console/php-console": "Allow sending log messages to Google Chrome", - "rollbar/rollbar": "Allow sending log messages to Rollbar", - "ruflin/elastica": "Allow sending log messages to an Elastic Search server", - "sentry/sentry": "Allow sending log messages to a Sentry server" + "ircmaxell/php-yacc": "^0.0.7", + "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0" }, + "bin": [ + "bin/php-parse" + ], "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0.x-dev" + "dev-master": "4.9-dev" } }, "autoload": { "psr-4": { - "Monolog\\": "src/Monolog" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jordi Boggiano", - "email": "j.boggiano@seld.be", - "homepage": "http://seld.be" - } - ], - "description": "Sends your logs to files, sockets, inboxes, databases and various web services", - "homepage": "http://github.com/Seldaek/monolog", - "keywords": [ - "log", - "logging", - "psr-3" - ], - "time": "2018-11-05T09:00:11+00:00" - }, - { - "name": "moontoast/math", - "version": "1.1.2", - "source": { - "type": "git", - "url": "https://github.com/ramsey/moontoast-math.git", - "reference": "c2792a25df5cad4ff3d760dd37078fc5b6fccc79" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/ramsey/moontoast-math/zipball/c2792a25df5cad4ff3d760dd37078fc5b6fccc79", - "reference": "c2792a25df5cad4ff3d760dd37078fc5b6fccc79", - "shasum": "" - }, - "require": { - "ext-bcmath": "*", - "php": ">=5.3.3" - }, - "require-dev": { - "jakub-onderka/php-parallel-lint": "^0.9.0", - "phpunit/phpunit": "^4.7|>=5.0 <5.4", - "satooshi/php-coveralls": "^0.6.1", - "squizlabs/php_codesniffer": "^2.3" - }, - "type": "library", - "autoload": { - "psr-4": { - "Moontoast\\Math\\": "src/Moontoast/Math/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "Apache-2.0" - ], - "authors": [ - { - "name": "Ben Ramsey", - "email": "ben@benramsey.com", - "homepage": "https://benramsey.com" - } - ], - "description": "A mathematics library, providing functionality for large numbers", - "homepage": "https://github.com/ramsey/moontoast-math", - "keywords": [ - "bcmath", - "math" - ], - "time": "2017-02-16T16:54:46+00:00" - }, - { - "name": "mustache/mustache", - "version": "v2.12.0", - "source": { - "type": "git", - "url": "https://github.com/bobthecow/mustache.php.git", - "reference": "fe8fe72e9d580591854de404cc59a1b83ca4d19e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/bobthecow/mustache.php/zipball/fe8fe72e9d580591854de404cc59a1b83ca4d19e", - "reference": "fe8fe72e9d580591854de404cc59a1b83ca4d19e", - "shasum": "" - }, - "require": { - "php": ">=5.2.4" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "~1.11", - "phpunit/phpunit": "~3.7|~4.0|~5.0" - }, - "type": "library", - "autoload": { - "psr-0": { - "Mustache": "src/" + "PhpParser\\": "lib/PhpParser" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Justin Hileman", - "email": "justin@justinhileman.info", - "homepage": "http://justinhileman.com" + "name": "Nikita Popov" } ], - "description": "A Mustache implementation in PHP.", - "homepage": "https://github.com/bobthecow/mustache.php", - "keywords": [ - "mustache", - "templating" - ], - "time": "2017-07-11T12:54:05+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": { - "psr-4": { - "DeepCopy\\": "src/DeepCopy/" - }, - "files": [ - "src/DeepCopy/deep_copy.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Create deep copies (clones) of your objects", + "description": "A PHP parser written in PHP", "keywords": [ - "clone", - "copy", - "duplicate", - "object", - "object graph" + "parser", + "php" ], - "time": "2017-10-19T19:58:43+00:00" + "support": { + "issues": "https://github.com/nikic/PHP-Parser/issues", + "source": "https://github.com/nikic/PHP-Parser/tree/v4.13.0" + }, + "time": "2021-09-20T12:20:58+00:00" }, { - "name": "paragonie/random_compat", - "version": "v9.99.99", + "name": "paragonie/constant_time_encoding", + "version": "v2.4.0", "source": { "type": "git", - "url": "https://github.com/paragonie/random_compat.git", - "reference": "84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95" + "url": "https://github.com/paragonie/constant_time_encoding.git", + "reference": "f34c2b11eb9d2c9318e13540a1dbc2a3afbd939c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/paragonie/random_compat/zipball/84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95", - "reference": "84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95", + "url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/f34c2b11eb9d2c9318e13540a1dbc2a3afbd939c", + "reference": "f34c2b11eb9d2c9318e13540a1dbc2a3afbd939c", "shasum": "" }, "require": { - "php": "^7" + "php": "^7|^8" }, "require-dev": { - "phpunit/phpunit": "4.*|5.*", - "vimeo/psalm": "^1" - }, - "suggest": { - "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes." + "phpunit/phpunit": "^6|^7|^8|^9", + "vimeo/psalm": "^1|^2|^3|^4" }, "type": "library", + "autoload": { + "psr-4": { + "ParagonIE\\ConstantTime\\": "src/" + } + }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" @@ -2627,42 +2599,63 @@ { "name": "Paragon Initiative Enterprises", "email": "security@paragonie.com", - "homepage": "https://paragonie.com" + "homepage": "https://paragonie.com", + "role": "Maintainer" + }, + { + "name": "Steve 'Sc00bz' Thomas", + "email": "steve@tobtu.com", + "homepage": "https://www.tobtu.com", + "role": "Original Developer" } ], - "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7", + "description": "Constant-time Implementations of RFC 4648 Encoding (Base-64, Base-32, Base-16)", "keywords": [ - "csprng", - "polyfill", - "pseudorandom", - "random" - ], - "time": "2018-07-02T15:55:56+00:00" + "base16", + "base32", + "base32_decode", + "base32_encode", + "base64", + "base64_decode", + "base64_encode", + "bin2hex", + "encoding", + "hex", + "hex2bin", + "rfc4648" + ], + "support": { + "email": "info@paragonie.com", + "issues": "https://github.com/paragonie/constant_time_encoding/issues", + "source": "https://github.com/paragonie/constant_time_encoding" + }, + "time": "2020-12-06T15:14:20+00:00" }, { "name": "phar-io/manifest", - "version": "1.0.1", + "version": "2.0.3", "source": { "type": "git", "url": "https://github.com/phar-io/manifest.git", - "reference": "2df402786ab5368a0169091f61a7c1e0eb6852d0" + "reference": "97803eca37d319dfa7826cc2437fc020857acb53" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phar-io/manifest/zipball/2df402786ab5368a0169091f61a7c1e0eb6852d0", - "reference": "2df402786ab5368a0169091f61a7c1e0eb6852d0", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/97803eca37d319dfa7826cc2437fc020857acb53", + "reference": "97803eca37d319dfa7826cc2437fc020857acb53", "shasum": "" }, "require": { "ext-dom": "*", "ext-phar": "*", - "phar-io/version": "^1.0.1", - "php": "^5.6 || ^7.0" + "ext-xmlwriter": "*", + "phar-io/version": "^3.0.1", + "php": "^7.2 || ^8.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "2.0.x-dev" } }, "autoload": { @@ -2692,24 +2685,28 @@ } ], "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", - "time": "2017-03-05T18:14:27+00:00" + "support": { + "issues": "https://github.com/phar-io/manifest/issues", + "source": "https://github.com/phar-io/manifest/tree/2.0.3" + }, + "time": "2021-07-20T11:28:43+00:00" }, { "name": "phar-io/version", - "version": "1.0.1", + "version": "3.1.0", "source": { "type": "git", "url": "https://github.com/phar-io/version.git", - "reference": "a70c0ced4be299a63d32fa96d9281d03e94041df" + "reference": "bae7c545bef187884426f042434e561ab1ddb182" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phar-io/version/zipball/a70c0ced4be299a63d32fa96d9281d03e94041df", - "reference": "a70c0ced4be299a63d32fa96d9281d03e94041df", + "url": "https://api.github.com/repos/phar-io/version/zipball/bae7c545bef187884426f042434e561ab1ddb182", + "reference": "bae7c545bef187884426f042434e561ab1ddb182", "shasum": "" }, "require": { - "php": "^5.6 || ^7.0" + "php": "^7.2 || ^8.0" }, "type": "library", "autoload": { @@ -2739,87 +2736,103 @@ } ], "description": "Library for handling version information and constraints", - "time": "2017-03-05T17:38:23+00:00" + "support": { + "issues": "https://github.com/phar-io/version/issues", + "source": "https://github.com/phar-io/version/tree/3.1.0" + }, + "time": "2021-02-23T14:00:09+00:00" }, { - "name": "phpcollection/phpcollection", - "version": "0.5.0", + "name": "php-webdriver/webdriver", + "version": "1.12.0", "source": { "type": "git", - "url": "https://github.com/schmittjoh/php-collection.git", - "reference": "f2bcff45c0da7c27991bbc1f90f47c4b7fb434a6" + "url": "https://github.com/php-webdriver/php-webdriver.git", + "reference": "99d4856ed7dffcdf6a52eccd6551e83d8d557ceb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/schmittjoh/php-collection/zipball/f2bcff45c0da7c27991bbc1f90f47c4b7fb434a6", - "reference": "f2bcff45c0da7c27991bbc1f90f47c4b7fb434a6", + "url": "https://api.github.com/repos/php-webdriver/php-webdriver/zipball/99d4856ed7dffcdf6a52eccd6551e83d8d557ceb", + "reference": "99d4856ed7dffcdf6a52eccd6551e83d8d557ceb", "shasum": "" }, "require": { - "phpoption/phpoption": "1.*" + "ext-curl": "*", + "ext-json": "*", + "ext-zip": "*", + "php": "^5.6 || ~7.0 || ^8.0", + "symfony/polyfill-mbstring": "^1.12", + "symfony/process": "^2.8 || ^3.1 || ^4.0 || ^5.0 || ^6.0" }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "0.4-dev" - } + "replace": { + "facebook/webdriver": "*" + }, + "require-dev": { + "ondram/ci-detector": "^2.1 || ^3.5 || ^4.0", + "php-coveralls/php-coveralls": "^2.4", + "php-mock/php-mock-phpunit": "^1.1 || ^2.0", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpunit/phpunit": "^5.7 || ^7 || ^8 || ^9", + "squizlabs/php_codesniffer": "^3.5", + "symfony/var-dumper": "^3.3 || ^4.0 || ^5.0 || ^6.0" + }, + "suggest": { + "ext-SimpleXML": "For Firefox profile creation" }, + "type": "library", "autoload": { - "psr-0": { - "PhpCollection": "src/" + "files": [ + "lib/Exception/TimeoutException.php" + ], + "psr-4": { + "Facebook\\WebDriver\\": "lib/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "Apache2" - ], - "authors": [ - { - "name": "Johannes M. Schmitt", - "email": "schmittjoh@gmail.com" - } + "MIT" ], - "description": "General-Purpose Collection Library for PHP", + "description": "A PHP client for Selenium WebDriver. Previously facebook/webdriver.", + "homepage": "https://github.com/php-webdriver/php-webdriver", "keywords": [ - "collection", - "list", - "map", - "sequence", - "set" + "Chromedriver", + "geckodriver", + "php", + "selenium", + "webdriver" ], - "time": "2015-05-17T12:39:23+00:00" + "support": { + "issues": "https://github.com/php-webdriver/php-webdriver/issues", + "source": "https://github.com/php-webdriver/php-webdriver/tree/1.12.0" + }, + "time": "2021-10-14T09:30:02+00:00" }, { "name": "phpdocumentor/reflection-common", - "version": "1.0.1", + "version": "2.2.0", "source": { "type": "git", "url": "https://github.com/phpDocumentor/ReflectionCommon.git", - "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6" + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6", - "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b", "shasum": "" }, "require": { - "php": ">=5.5" - }, - "require-dev": { - "phpunit/phpunit": "^4.6" + "php": "^7.2 || ^8.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-2.x": "2.x-dev" } }, "autoload": { "psr-4": { - "phpDocumentor\\Reflection\\": [ - "src" - ] + "phpDocumentor\\Reflection\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -2841,44 +2854,45 @@ "reflection", "static analysis" ], - "time": "2017-09-11T18:02:19+00:00" + "support": { + "issues": "https://github.com/phpDocumentor/ReflectionCommon/issues", + "source": "https://github.com/phpDocumentor/ReflectionCommon/tree/2.x" + }, + "time": "2020-06-27T09:03:43+00:00" }, { "name": "phpdocumentor/reflection-docblock", - "version": "4.3.0", + "version": "5.2.2", "source": { "type": "git", "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "94fd0001232e47129dd3504189fa1c7225010d08" + "reference": "069a785b2141f5bcf49f3e353548dc1cce6df556" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/94fd0001232e47129dd3504189fa1c7225010d08", - "reference": "94fd0001232e47129dd3504189fa1c7225010d08", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/069a785b2141f5bcf49f3e353548dc1cce6df556", + "reference": "069a785b2141f5bcf49f3e353548dc1cce6df556", "shasum": "" }, "require": { - "php": "^7.0", - "phpdocumentor/reflection-common": "^1.0.0", - "phpdocumentor/type-resolver": "^0.4.0", - "webmozart/assert": "^1.0" + "ext-filter": "*", + "php": "^7.2 || ^8.0", + "phpdocumentor/reflection-common": "^2.2", + "phpdocumentor/type-resolver": "^1.3", + "webmozart/assert": "^1.9.1" }, "require-dev": { - "doctrine/instantiator": "~1.0.5", - "mockery/mockery": "^1.0", - "phpunit/phpunit": "^6.4" + "mockery/mockery": "~1.3.2" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.x-dev" + "dev-master": "5.x-dev" } }, "autoload": { "psr-4": { - "phpDocumentor\\Reflection\\": [ - "src/" - ] + "phpDocumentor\\Reflection\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -2889,44 +2903,50 @@ { "name": "Mike van Riel", "email": "me@mikevanriel.com" + }, + { + "name": "Jaap van Otterdijk", + "email": "account@ijaap.nl" } ], "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", - "time": "2017-11-30T07:14:17+00:00" + "support": { + "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", + "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/master" + }, + "time": "2020-09-03T19:13:55+00:00" }, { "name": "phpdocumentor/type-resolver", - "version": "0.4.0", + "version": "1.5.1", "source": { "type": "git", "url": "https://github.com/phpDocumentor/TypeResolver.git", - "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7" + "reference": "a12f7e301eb7258bb68acd89d4aefa05c2906cae" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/9c977708995954784726e25d0cd1dddf4e65b0f7", - "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/a12f7e301eb7258bb68acd89d4aefa05c2906cae", + "reference": "a12f7e301eb7258bb68acd89d4aefa05c2906cae", "shasum": "" }, "require": { - "php": "^5.5 || ^7.0", - "phpdocumentor/reflection-common": "^1.0" + "php": "^7.2 || ^8.0", + "phpdocumentor/reflection-common": "^2.0" }, "require-dev": { - "mockery/mockery": "^0.9.4", - "phpunit/phpunit": "^5.2||^4.8.24" + "ext-tokenizer": "*", + "psalm/phar": "^4.8" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-1.x": "1.x-dev" } }, "autoload": { "psr-4": { - "phpDocumentor\\Reflection\\": [ - "src/" - ] + "phpDocumentor\\Reflection\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -2939,157 +2959,169 @@ "email": "me@mikevanriel.com" } ], - "time": "2017-07-14T14:27:02+00:00" + "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", + "support": { + "issues": "https://github.com/phpDocumentor/TypeResolver/issues", + "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.5.1" + }, + "time": "2021-10-02T14:08:47+00:00" }, { - "name": "phpoption/phpoption", - "version": "1.5.0", + "name": "phpspec/prophecy", + "version": "1.14.0", "source": { "type": "git", - "url": "https://github.com/schmittjoh/php-option.git", - "reference": "94e644f7d2051a5f0fcf77d81605f152eecff0ed" + "url": "https://github.com/phpspec/prophecy.git", + "reference": "d86dfc2e2a3cd366cee475e52c6bb3bbc371aa0e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/94e644f7d2051a5f0fcf77d81605f152eecff0ed", - "reference": "94e644f7d2051a5f0fcf77d81605f152eecff0ed", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/d86dfc2e2a3cd366cee475e52c6bb3bbc371aa0e", + "reference": "d86dfc2e2a3cd366cee475e52c6bb3bbc371aa0e", "shasum": "" }, "require": { - "php": ">=5.3.0" + "doctrine/instantiator": "^1.2", + "php": "^7.2 || ~8.0, <8.2", + "phpdocumentor/reflection-docblock": "^5.2", + "sebastian/comparator": "^3.0 || ^4.0", + "sebastian/recursion-context": "^3.0 || ^4.0" }, "require-dev": { - "phpunit/phpunit": "4.7.*" + "phpspec/phpspec": "^6.0 || ^7.0", + "phpunit/phpunit": "^8.0 || ^9.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.3-dev" + "dev-master": "1.x-dev" } }, "autoload": { - "psr-0": { - "PhpOption\\": "src/" + "psr-4": { + "Prophecy\\": "src/Prophecy" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "Apache2" + "MIT" ], "authors": [ { - "name": "Johannes M. Schmitt", - "email": "schmittjoh@gmail.com" + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + }, + { + "name": "Marcello Duarte", + "email": "marcello.duarte@gmail.com" } ], - "description": "Option Type for PHP", + "description": "Highly opinionated mocking framework for PHP 5.3+", + "homepage": "https://github.com/phpspec/prophecy", "keywords": [ - "language", - "option", - "php", - "type" + "Double", + "Dummy", + "fake", + "mock", + "spy", + "stub" ], - "time": "2015-07-25T16:39:46+00:00" + "support": { + "issues": "https://github.com/phpspec/prophecy/issues", + "source": "https://github.com/phpspec/prophecy/tree/1.14.0" + }, + "time": "2021-09-10T09:02:12+00:00" }, { - "name": "phpspec/prophecy", - "version": "1.8.0", + "name": "phpstan/phpdoc-parser", + "version": "1.2.0", "source": { "type": "git", - "url": "https://github.com/phpspec/prophecy.git", - "reference": "4ba436b55987b4bf311cb7c6ba82aa528aac0a06" + "url": "https://github.com/phpstan/phpdoc-parser.git", + "reference": "dbc093d7af60eff5cd575d2ed761b15ed40bd08e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/4ba436b55987b4bf311cb7c6ba82aa528aac0a06", - "reference": "4ba436b55987b4bf311cb7c6ba82aa528aac0a06", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/dbc093d7af60eff5cd575d2ed761b15ed40bd08e", + "reference": "dbc093d7af60eff5cd575d2ed761b15ed40bd08e", "shasum": "" }, "require": { - "doctrine/instantiator": "^1.0.2", - "php": "^5.3|^7.0", - "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0", - "sebastian/comparator": "^1.1|^2.0|^3.0", - "sebastian/recursion-context": "^1.0|^2.0|^3.0" + "php": "^7.1 || ^8.0" }, "require-dev": { - "phpspec/phpspec": "^2.5|^3.2", - "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5 || ^7.1" + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^1.0", + "phpstan/phpstan-strict-rules": "^1.0", + "phpunit/phpunit": "^9.5", + "symfony/process": "^5.2" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.8.x-dev" + "dev-master": "1.0-dev" } }, "autoload": { - "psr-0": { - "Prophecy\\": "src/" + "psr-4": { + "PHPStan\\PhpDocParser\\": [ + "src/" + ] } }, "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" - ], - "time": "2018-08-05T17:53:17+00:00" + "description": "PHPDoc parser with support for nullable, intersection and generic types", + "support": { + "issues": "https://github.com/phpstan/phpdoc-parser/issues", + "source": "https://github.com/phpstan/phpdoc-parser/tree/1.2.0" + }, + "time": "2021-09-16T20:46:02+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "5.3.2", + "version": "9.2.7", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "c89677919c5dd6d3b3852f230a663118762218ac" + "reference": "d4c798ed8d51506800b441f7a13ecb0f76f12218" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/c89677919c5dd6d3b3852f230a663118762218ac", - "reference": "c89677919c5dd6d3b3852f230a663118762218ac", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/d4c798ed8d51506800b441f7a13ecb0f76f12218", + "reference": "d4c798ed8d51506800b441f7a13ecb0f76f12218", "shasum": "" }, "require": { "ext-dom": "*", + "ext-libxml": "*", "ext-xmlwriter": "*", - "php": "^7.0", - "phpunit/php-file-iterator": "^1.4.2", - "phpunit/php-text-template": "^1.2.1", - "phpunit/php-token-stream": "^2.0.1", - "sebastian/code-unit-reverse-lookup": "^1.0.1", - "sebastian/environment": "^3.0", - "sebastian/version": "^2.0.1", - "theseer/tokenizer": "^1.1" + "nikic/php-parser": "^4.12.0", + "php": ">=7.3", + "phpunit/php-file-iterator": "^3.0.3", + "phpunit/php-text-template": "^2.0.2", + "sebastian/code-unit-reverse-lookup": "^2.0.2", + "sebastian/complexity": "^2.0", + "sebastian/environment": "^5.1.2", + "sebastian/lines-of-code": "^1.0.3", + "sebastian/version": "^3.0.1", + "theseer/tokenizer": "^1.2.0" }, "require-dev": { - "phpunit/phpunit": "^6.0" + "phpunit/phpunit": "^9.3" }, "suggest": { - "ext-xdebug": "^2.5.5" + "ext-pcov": "*", + "ext-xdebug": "*" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "5.3.x-dev" + "dev-master": "9.2-dev" } }, "autoload": { @@ -3115,29 +3147,42 @@ "testing", "xunit" ], - "time": "2018-04-06T15:36:58+00:00" + "support": { + "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.7" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2021-09-17T05:39:03+00:00" }, { "name": "phpunit/php-file-iterator", - "version": "1.4.5", + "version": "3.0.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4" + "reference": "aa4be8575f26070b100fccb67faabb28f21f66f8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/730b01bc3e867237eaac355e06a36b85dd93a8b4", - "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/aa4be8575f26070b100fccb67faabb28f21f66f8", + "reference": "aa4be8575f26070b100fccb67faabb28f21f66f8", "shasum": "" }, "require": { - "php": ">=5.3.3" + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.4.x-dev" + "dev-master": "3.0-dev" } }, "autoload": { @@ -3152,7 +3197,7 @@ "authors": [ { "name": "Sebastian Bergmann", - "email": "sb@sebastian-bergmann.de", + "email": "sebastian@phpunit.de", "role": "lead" } ], @@ -3162,26 +3207,48 @@ "filesystem", "iterator" ], - "time": "2017-11-27T13:52:08+00:00" + "support": { + "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/3.0.5" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T05:57:25+00:00" }, { - "name": "phpunit/php-text-template", - "version": "1.2.1", + "name": "phpunit/php-invoker", + "version": "3.1.1", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/php-text-template.git", - "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686" + "url": "https://github.com/sebastianbergmann/php-invoker.git", + "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686", - "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/5a10147d0aaf65b58940a0b72f71c9ac0423cc67", + "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67", "shasum": "" }, "require": { - "php": ">=5.3.3" + "php": ">=7.3" + }, + "require-dev": { + "ext-pcntl": "*", + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-pcntl": "*" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1-dev" + } + }, "autoload": { "classmap": [ "src/" @@ -3198,37 +3265,47 @@ "role": "lead" } ], - "description": "Simple template engine.", - "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "description": "Invoke callables with a timeout", + "homepage": "https://github.com/sebastianbergmann/php-invoker/", "keywords": [ - "template" + "process" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-invoker/issues", + "source": "https://github.com/sebastianbergmann/php-invoker/tree/3.1.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } ], - "time": "2015-06-21T13:50:34+00:00" + "time": "2020-09-28T05:58:55+00:00" }, { - "name": "phpunit/php-timer", - "version": "1.0.9", + "name": "phpunit/php-text-template", + "version": "2.0.4", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/php-timer.git", - "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f" + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", - "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", + "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", "shasum": "" }, "require": { - "php": "^5.3.3 || ^7.0" + "php": ">=7.3" }, "require-dev": { - "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" + "phpunit/phpunit": "^9.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-master": "2.0-dev" } }, "autoload": { @@ -3243,42 +3320,51 @@ "authors": [ { "name": "Sebastian Bergmann", - "email": "sb@sebastian-bergmann.de", + "email": "sebastian@phpunit.de", "role": "lead" } ], - "description": "Utility class for timing", - "homepage": "https://github.com/sebastianbergmann/php-timer/", + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", "keywords": [ - "timer" + "template" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-text-template/issues", + "source": "https://github.com/sebastianbergmann/php-text-template/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } ], - "time": "2017-02-26T11:10:40+00:00" + "time": "2020-10-26T05:33:50+00:00" }, { - "name": "phpunit/php-token-stream", - "version": "2.0.2", + "name": "phpunit/php-timer", + "version": "5.0.3", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/php-token-stream.git", - "reference": "791198a2c6254db10131eecfe8c06670700904db" + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/791198a2c6254db10131eecfe8c06670700904db", - "reference": "791198a2c6254db10131eecfe8c06670700904db", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", + "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", "shasum": "" }, "require": { - "ext-tokenizer": "*", - "php": "^7.0" + "php": ">=7.3" }, "require-dev": { - "phpunit/phpunit": "^6.2.4" + "phpunit/phpunit": "^9.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-master": "5.0-dev" } }, "autoload": { @@ -3293,65 +3379,78 @@ "authors": [ { "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" + "email": "sebastian@phpunit.de", + "role": "lead" } ], - "description": "Wrapper around PHP's tokenizer extension.", - "homepage": "https://github.com/sebastianbergmann/php-token-stream/", + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", "keywords": [ - "tokenizer" + "timer" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-timer/issues", + "source": "https://github.com/sebastianbergmann/php-timer/tree/5.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } ], - "time": "2017-11-27T05:48:46+00:00" + "time": "2020-10-26T13:16:10+00:00" }, { "name": "phpunit/phpunit", - "version": "6.5.13", + "version": "9.5.10", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "0973426fb012359b2f18d3bd1e90ef1172839693" + "reference": "c814a05837f2edb0d1471d6e3f4ab3501ca3899a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/0973426fb012359b2f18d3bd1e90ef1172839693", - "reference": "0973426fb012359b2f18d3bd1e90ef1172839693", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/c814a05837f2edb0d1471d6e3f4ab3501ca3899a", + "reference": "c814a05837f2edb0d1471d6e3f4ab3501ca3899a", "shasum": "" }, "require": { + "doctrine/instantiator": "^1.3.1", "ext-dom": "*", "ext-json": "*", "ext-libxml": "*", "ext-mbstring": "*", "ext-xml": "*", - "myclabs/deep-copy": "^1.6.1", - "phar-io/manifest": "^1.0.1", - "phar-io/version": "^1.0", - "php": "^7.0", - "phpspec/prophecy": "^1.7", - "phpunit/php-code-coverage": "^5.3", - "phpunit/php-file-iterator": "^1.4.3", - "phpunit/php-text-template": "^1.2.1", - "phpunit/php-timer": "^1.0.9", - "phpunit/phpunit-mock-objects": "^5.0.9", - "sebastian/comparator": "^2.1", - "sebastian/diff": "^2.0", - "sebastian/environment": "^3.1", - "sebastian/exporter": "^3.1", - "sebastian/global-state": "^2.0", - "sebastian/object-enumerator": "^3.0.3", - "sebastian/resource-operations": "^1.0", - "sebastian/version": "^2.0.1" - }, - "conflict": { - "phpdocumentor/reflection-docblock": "3.0.2", - "phpunit/dbunit": "<3.0" + "ext-xmlwriter": "*", + "myclabs/deep-copy": "^1.10.1", + "phar-io/manifest": "^2.0.3", + "phar-io/version": "^3.0.2", + "php": ">=7.3", + "phpspec/prophecy": "^1.12.1", + "phpunit/php-code-coverage": "^9.2.7", + "phpunit/php-file-iterator": "^3.0.5", + "phpunit/php-invoker": "^3.1.1", + "phpunit/php-text-template": "^2.0.3", + "phpunit/php-timer": "^5.0.2", + "sebastian/cli-parser": "^1.0.1", + "sebastian/code-unit": "^1.0.6", + "sebastian/comparator": "^4.0.5", + "sebastian/diff": "^4.0.3", + "sebastian/environment": "^5.1.3", + "sebastian/exporter": "^4.0.3", + "sebastian/global-state": "^5.0.1", + "sebastian/object-enumerator": "^4.0.3", + "sebastian/resource-operations": "^3.0.3", + "sebastian/type": "^2.3.4", + "sebastian/version": "^3.0.2" }, "require-dev": { - "ext-pdo": "*" + "ext-pdo": "*", + "phpspec/prophecy-phpunit": "^2.0.1" }, "suggest": { - "ext-xdebug": "*", - "phpunit/php-invoker": "^1.1" + "ext-soap": "*", + "ext-xdebug": "*" }, "bin": [ "phpunit" @@ -3359,10 +3458,13 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "6.5.x-dev" + "dev-master": "9.5-dev" } }, "autoload": { + "files": [ + "src/Framework/Assert/Functions.php" + ], "classmap": [ "src/" ] @@ -3381,88 +3483,140 @@ "description": "The PHP Unit Testing framework.", "homepage": "https://phpunit.de/", "keywords": [ - "phpunit", - "testing", - "xunit" + "phpunit", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/phpunit/issues", + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.10" + }, + "funding": [ + { + "url": "https://phpunit.de/donate.html", + "type": "custom" + }, + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2021-09-25T07:38:51+00:00" + }, + { + "name": "psr/cache", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/cache.git", + "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/cache/zipball/d11b50ad223250cf17b86e38383413f5a6764bf8", + "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Cache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for caching libraries", + "keywords": [ + "cache", + "psr", + "psr-6" ], - "time": "2018-09-08T15:10:43+00:00" + "support": { + "source": "https://github.com/php-fig/cache/tree/master" + }, + "time": "2016-08-06T20:24:11+00:00" }, { - "name": "phpunit/phpunit-mock-objects", - "version": "5.0.10", + "name": "psr/container", + "version": "1.1.1", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", - "reference": "cd1cf05c553ecfec36b170070573e540b67d3f1f" + "url": "https://github.com/php-fig/container.git", + "reference": "8622567409010282b7aeebe4bb841fe98b58dcaf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/cd1cf05c553ecfec36b170070573e540b67d3f1f", - "reference": "cd1cf05c553ecfec36b170070573e540b67d3f1f", + "url": "https://api.github.com/repos/php-fig/container/zipball/8622567409010282b7aeebe4bb841fe98b58dcaf", + "reference": "8622567409010282b7aeebe4bb841fe98b58dcaf", "shasum": "" }, "require": { - "doctrine/instantiator": "^1.0.5", - "php": "^7.0", - "phpunit/php-text-template": "^1.2.1", - "sebastian/exporter": "^3.1" - }, - "conflict": { - "phpunit/phpunit": "<6.0" - }, - "require-dev": { - "phpunit/phpunit": "^6.5.11" - }, - "suggest": { - "ext-soap": "*" + "php": ">=7.2.0" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.0.x-dev" - } - }, "autoload": { - "classmap": [ - "src/" - ] + "psr-4": { + "Psr\\Container\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" } ], - "description": "Mock Object library for PHPUnit", - "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/", + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", "keywords": [ - "mock", - "xunit" + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" ], - "abandoned": true, - "time": "2018-08-09T05:50:03+00:00" + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/1.1.1" + }, + "time": "2021-03-05T17:36:06+00:00" }, { - "name": "psr/cache", + "name": "psr/http-client", "version": "1.0.1", "source": { "type": "git", - "url": "https://github.com/php-fig/cache.git", - "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8" + "url": "https://github.com/php-fig/http-client.git", + "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/cache/zipball/d11b50ad223250cf17b86e38383413f5a6764bf8", - "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8", + "url": "https://api.github.com/repos/php-fig/http-client/zipball/2dfb5f6c5eff0e91e20e913f8c5452ed95b86621", + "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621", "shasum": "" }, "require": { - "php": ">=5.3.0" + "php": "^7.0 || ^8.0", + "psr/http-message": "^1.0" }, "type": "library", "extra": { @@ -3472,7 +3626,7 @@ }, "autoload": { "psr-4": { - "Psr\\Cache\\": "src/" + "Psr\\Http\\Client\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -3485,30 +3639,36 @@ "homepage": "http://www.php-fig.org/" } ], - "description": "Common interface for caching libraries", + "description": "Common interface for HTTP clients", + "homepage": "https://github.com/php-fig/http-client", "keywords": [ - "cache", + "http", + "http-client", "psr", - "psr-6" + "psr-18" ], - "time": "2016-08-06T20:24:11+00:00" + "support": { + "source": "https://github.com/php-fig/http-client/tree/master" + }, + "time": "2020-06-29T06:28:15+00:00" }, { - "name": "psr/container", - "version": "1.0.0", + "name": "psr/http-factory", + "version": "1.0.1", "source": { "type": "git", - "url": "https://github.com/php-fig/container.git", - "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f" + "url": "https://github.com/php-fig/http-factory.git", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/container/zipball/b7ce3b176482dbbc1245ebf52b181af44c2cf55f", - "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", "shasum": "" }, "require": { - "php": ">=5.3.0" + "php": ">=7.0.0", + "psr/http-message": "^1.0" }, "type": "library", "extra": { @@ -3518,7 +3678,7 @@ }, "autoload": { "psr-4": { - "Psr\\Container\\": "src/" + "Psr\\Http\\Message\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -3531,16 +3691,21 @@ "homepage": "http://www.php-fig.org/" } ], - "description": "Common Container Interface (PHP FIG PSR-11)", - "homepage": "https://github.com/php-fig/container", + "description": "Common interfaces for PSR-7 HTTP message factories", "keywords": [ - "PSR-11", - "container", - "container-interface", - "container-interop", - "psr" + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" ], - "time": "2017-02-14T16:28:37+00:00" + "support": { + "source": "https://github.com/php-fig/http-factory/tree/master" + }, + "time": "2019-04-30T12:38:16+00:00" }, { "name": "psr/http-message", @@ -3590,20 +3755,23 @@ "request", "response" ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/master" + }, "time": "2016-08-06T14:39:51+00:00" }, { "name": "psr/log", - "version": "1.0.2", + "version": "1.1.4", "source": { "type": "git", "url": "https://github.com/php-fig/log.git", - "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d" + "reference": "d49695b909c3b7628b6289db5479a1c204601f11" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/4ebe3a8bf773a19edfe0a84b6585ba3d401b724d", - "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d", + "url": "https://api.github.com/repos/php-fig/log/zipball/d49695b909c3b7628b6289db5479a1c204601f11", + "reference": "d49695b909c3b7628b6289db5479a1c204601f11", "shasum": "" }, "require": { @@ -3612,7 +3780,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "1.1.x-dev" } }, "autoload": { @@ -3627,7 +3795,7 @@ "authors": [ { "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "homepage": "https://www.php-fig.org/" } ], "description": "Common interface for logging libraries", @@ -3637,34 +3805,259 @@ "psr", "psr-3" ], - "time": "2016-10-10T12:19:37+00:00" + "support": { + "source": "https://github.com/php-fig/log/tree/1.1.4" + }, + "time": "2021-05-03T11:20:27+00:00" }, { - "name": "psr/simple-cache", - "version": "1.0.1", + "name": "ralouphie/getallheaders", + "version": "3.0.3", "source": { "type": "git", - "url": "https://github.com/php-fig/simple-cache.git", - "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b" + "url": "https://github.com/ralouphie/getallheaders.git", + "reference": "120b605dfeb996808c31b6477290a714d356e822" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/408d5eafb83c57f6365a3ca330ff23aa4a5fa39b", - "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b", + "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", + "reference": "120b605dfeb996808c31b6477290a714d356e822", "shasum": "" }, "require": { - "php": ">=5.3.0" + "php": ">=5.6" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^5 || ^6.5" + }, + "type": "library", + "autoload": { + "files": [ + "src/getallheaders.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ralph Khattar", + "email": "ralph.khattar@gmail.com" + } + ], + "description": "A polyfill for getallheaders.", + "support": { + "issues": "https://github.com/ralouphie/getallheaders/issues", + "source": "https://github.com/ralouphie/getallheaders/tree/develop" + }, + "time": "2019-03-08T08:55:37+00:00" + }, + { + "name": "ramsey/collection", + "version": "1.2.2", + "source": { + "type": "git", + "url": "https://github.com/ramsey/collection.git", + "reference": "cccc74ee5e328031b15640b51056ee8d3bb66c0a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ramsey/collection/zipball/cccc74ee5e328031b15640b51056ee8d3bb66c0a", + "reference": "cccc74ee5e328031b15640b51056ee8d3bb66c0a", + "shasum": "" + }, + "require": { + "php": "^7.3 || ^8", + "symfony/polyfill-php81": "^1.23" + }, + "require-dev": { + "captainhook/captainhook": "^5.3", + "dealerdirect/phpcodesniffer-composer-installer": "^0.7.0", + "ergebnis/composer-normalize": "^2.6", + "fakerphp/faker": "^1.5", + "hamcrest/hamcrest-php": "^2", + "jangregor/phpstan-prophecy": "^0.8", + "mockery/mockery": "^1.3", + "phpspec/prophecy-phpunit": "^2.0", + "phpstan/extension-installer": "^1", + "phpstan/phpstan": "^0.12.32", + "phpstan/phpstan-mockery": "^0.12.5", + "phpstan/phpstan-phpunit": "^0.12.11", + "phpunit/phpunit": "^8.5 || ^9", + "psy/psysh": "^0.10.4", + "slevomat/coding-standard": "^6.3", + "squizlabs/php_codesniffer": "^3.5", + "vimeo/psalm": "^4.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "Ramsey\\Collection\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ben Ramsey", + "email": "ben@benramsey.com", + "homepage": "https://benramsey.com" + } + ], + "description": "A PHP library for representing and manipulating collections.", + "keywords": [ + "array", + "collection", + "hash", + "map", + "queue", + "set" + ], + "support": { + "issues": "https://github.com/ramsey/collection/issues", + "source": "https://github.com/ramsey/collection/tree/1.2.2" + }, + "funding": [ + { + "url": "https://github.com/ramsey", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/ramsey/collection", + "type": "tidelift" + } + ], + "time": "2021-10-10T03:01:02+00:00" + }, + { + "name": "ramsey/uuid", + "version": "4.2.3", + "source": { + "type": "git", + "url": "https://github.com/ramsey/uuid.git", + "reference": "fc9bb7fb5388691fd7373cd44dcb4d63bbcf24df" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ramsey/uuid/zipball/fc9bb7fb5388691fd7373cd44dcb4d63bbcf24df", + "reference": "fc9bb7fb5388691fd7373cd44dcb4d63bbcf24df", + "shasum": "" + }, + "require": { + "brick/math": "^0.8 || ^0.9", + "ext-json": "*", + "php": "^7.2 || ^8.0", + "ramsey/collection": "^1.0", + "symfony/polyfill-ctype": "^1.8", + "symfony/polyfill-php80": "^1.14" + }, + "replace": { + "rhumsaa/uuid": "self.version" + }, + "require-dev": { + "captainhook/captainhook": "^5.10", + "captainhook/plugin-composer": "^5.3", + "dealerdirect/phpcodesniffer-composer-installer": "^0.7.0", + "doctrine/annotations": "^1.8", + "ergebnis/composer-normalize": "^2.15", + "mockery/mockery": "^1.3", + "moontoast/math": "^1.1", + "paragonie/random-lib": "^2", + "php-mock/php-mock": "^2.2", + "php-mock/php-mock-mockery": "^1.3", + "php-parallel-lint/php-parallel-lint": "^1.1", + "phpbench/phpbench": "^1.0", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^0.12", + "phpstan/phpstan-mockery": "^0.12", + "phpstan/phpstan-phpunit": "^0.12", + "phpunit/phpunit": "^8.5 || ^9", + "slevomat/coding-standard": "^7.0", + "squizlabs/php_codesniffer": "^3.5", + "vimeo/psalm": "^4.9" + }, + "suggest": { + "ext-bcmath": "Enables faster math with arbitrary-precision integers using BCMath.", + "ext-ctype": "Enables faster processing of character classification using ctype functions.", + "ext-gmp": "Enables faster math with arbitrary-precision integers using GMP.", + "ext-uuid": "Enables the use of PeclUuidTimeGenerator and PeclUuidRandomGenerator.", + "paragonie/random-lib": "Provides RandomLib for use with the RandomLibAdapter", + "ramsey/uuid-doctrine": "Allows the use of Ramsey\\Uuid\\Uuid as Doctrine field type." }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-main": "4.x-dev" + }, + "captainhook": { + "force-install": true + } + }, + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "Ramsey\\Uuid\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A PHP library for generating and working with universally unique identifiers (UUIDs).", + "keywords": [ + "guid", + "identifier", + "uuid" + ], + "support": { + "issues": "https://github.com/ramsey/uuid/issues", + "source": "https://github.com/ramsey/uuid/tree/4.2.3" + }, + "funding": [ + { + "url": "https://github.com/ramsey", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/ramsey/uuid", + "type": "tidelift" } + ], + "time": "2021-09-25T23:10:38+00:00" + }, + { + "name": "react/promise", + "version": "v2.8.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/promise.git", + "reference": "f3cff96a19736714524ca0dd1d4130de73dbbbc4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/promise/zipball/f3cff96a19736714524ca0dd1d4130de73dbbbc4", + "reference": "f3cff96a19736714524ca0dd1d4130de73dbbbc4", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^7.0 || ^6.5 || ^5.7 || ^4.8.36" }, + "type": "library", "autoload": { + "files": [ + "src/functions_include.php" + ], "psr-4": { - "Psr\\SimpleCache\\": "src/" + "React\\Promise\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -3673,126 +4066,157 @@ ], "authors": [ { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com" + } + ], + "description": "A lightweight implementation of CommonJS Promises/A for PHP", + "keywords": [ + "promise", + "promises" + ], + "support": { + "issues": "https://github.com/reactphp/promise/issues", + "source": "https://github.com/reactphp/promise/tree/v2.8.0" + }, + "time": "2020-05-12T15:16:56+00:00" + }, + { + "name": "sebastian/cli-parser", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/cli-parser.git", + "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/442e7c7e687e42adc03470c7b668bc4b2402c0b2", + "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "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", + "role": "lead" + } + ], + "description": "Library for parsing CLI options", + "homepage": "https://github.com/sebastianbergmann/cli-parser", + "support": { + "issues": "https://github.com/sebastianbergmann/cli-parser/issues", + "source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" } ], - "description": "Common interfaces for simple caching", - "keywords": [ - "cache", - "caching", - "psr", - "psr-16", - "simple-cache" - ], - "time": "2017-10-23T01:57:42+00:00" + "time": "2020-09-28T06:08:49+00:00" }, { - "name": "ramsey/uuid", - "version": "3.8.0", + "name": "sebastian/code-unit", + "version": "1.0.8", "source": { "type": "git", - "url": "https://github.com/ramsey/uuid.git", - "reference": "d09ea80159c1929d75b3f9c60504d613aeb4a1e3" + "url": "https://github.com/sebastianbergmann/code-unit.git", + "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ramsey/uuid/zipball/d09ea80159c1929d75b3f9c60504d613aeb4a1e3", - "reference": "d09ea80159c1929d75b3f9c60504d613aeb4a1e3", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/1fc9f64c0927627ef78ba436c9b17d967e68e120", + "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120", "shasum": "" }, "require": { - "paragonie/random_compat": "^1.0|^2.0|9.99.99", - "php": "^5.4 || ^7.0", - "symfony/polyfill-ctype": "^1.8" - }, - "replace": { - "rhumsaa/uuid": "self.version" + "php": ">=7.3" }, "require-dev": { - "codeception/aspect-mock": "^1.0 | ~2.0.0", - "doctrine/annotations": "~1.2.0", - "goaop/framework": "1.0.0-alpha.2 | ^1.0 | ~2.1.0", - "ircmaxell/random-lib": "^1.1", - "jakub-onderka/php-parallel-lint": "^0.9.0", - "mockery/mockery": "^0.9.9", - "moontoast/math": "^1.1", - "php-mock/php-mock-phpunit": "^0.3|^1.1", - "phpunit/phpunit": "^4.7|^5.0|^6.5", - "squizlabs/php_codesniffer": "^2.3" - }, - "suggest": { - "ext-ctype": "Provides support for PHP Ctype functions", - "ext-libsodium": "Provides the PECL libsodium extension for use with the SodiumRandomGenerator", - "ext-uuid": "Provides the PECL UUID extension for use with the PeclUuidTimeGenerator and PeclUuidRandomGenerator", - "ircmaxell/random-lib": "Provides RandomLib for use with the RandomLibAdapter", - "moontoast/math": "Provides support for converting UUID to 128-bit integer (in string form).", - "ramsey/uuid-console": "A console application for generating UUIDs with ramsey/uuid", - "ramsey/uuid-doctrine": "Allows the use of Ramsey\\Uuid\\Uuid as Doctrine field type." + "phpunit/phpunit": "^9.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.x-dev" + "dev-master": "1.0-dev" } }, "autoload": { - "psr-4": { - "Ramsey\\Uuid\\": "src/" - } + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Marijn Huizendveld", - "email": "marijn.huizendveld@gmail.com" - }, - { - "name": "Thibaud Fabre", - "email": "thibaud@aztech.io" - }, - { - "name": "Ben Ramsey", - "email": "ben@benramsey.com", - "homepage": "https://benramsey.com" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" } ], - "description": "Formerly rhumsaa/uuid. A PHP 5.4+ library for generating RFC 4122 version 1, 3, 4, and 5 universally unique identifiers (UUID).", - "homepage": "https://github.com/ramsey/uuid", - "keywords": [ - "guid", - "identifier", - "uuid" + "description": "Collection of value objects that represent the PHP code units", + "homepage": "https://github.com/sebastianbergmann/code-unit", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit/issues", + "source": "https://github.com/sebastianbergmann/code-unit/tree/1.0.8" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } ], - "time": "2018-07-19T23:38:55+00:00" + "time": "2020-10-26T13:08:54+00:00" }, { "name": "sebastian/code-unit-reverse-lookup", - "version": "1.0.1", + "version": "2.0.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", - "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18" + "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/4419fcdb5eabb9caa61a27c7a1db532a6b55dd18", - "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", + "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", "shasum": "" }, "require": { - "php": "^5.6 || ^7.0" + "php": ">=7.3" }, "require-dev": { - "phpunit/phpunit": "^5.7 || ^6.0" + "phpunit/phpunit": "^9.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "2.0-dev" } }, "autoload": { @@ -3812,34 +4236,44 @@ ], "description": "Looks up which function or method a line of code belongs to", "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", - "time": "2017-03-04T06:30:41+00:00" + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", + "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/2.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T05:30:19+00:00" }, { "name": "sebastian/comparator", - "version": "2.1.3", + "version": "4.0.6", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "34369daee48eafb2651bea869b4b15d75ccc35f9" + "reference": "55f4261989e546dc112258c7a75935a81a7ce382" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/34369daee48eafb2651bea869b4b15d75ccc35f9", - "reference": "34369daee48eafb2651bea869b4b15d75ccc35f9", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/55f4261989e546dc112258c7a75935a81a7ce382", + "reference": "55f4261989e546dc112258c7a75935a81a7ce382", "shasum": "" }, "require": { - "php": "^7.0", - "sebastian/diff": "^2.0 || ^3.0", - "sebastian/exporter": "^3.1" + "php": ">=7.3", + "sebastian/diff": "^4.0", + "sebastian/exporter": "^4.0" }, "require-dev": { - "phpunit/phpunit": "^6.4" + "phpunit/phpunit": "^9.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.1.x-dev" + "dev-master": "4.0-dev" } }, "autoload": { @@ -3852,6 +4286,10 @@ "BSD-3-Clause" ], "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, { "name": "Jeff Welch", "email": "whatthejeff@gmail.com" @@ -3863,10 +4301,6 @@ { "name": "Bernhard Schussek", "email": "bschussek@2bepublished.at" - }, - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" } ], "description": "Provides the functionality to compare PHP values for equality", @@ -3876,27 +4310,38 @@ "compare", "equality" ], - "time": "2018-02-01T13:46:46+00:00" + "support": { + "issues": "https://github.com/sebastianbergmann/comparator/issues", + "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.6" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T15:49:45+00:00" }, { - "name": "sebastian/diff", - "version": "2.0.1", + "name": "sebastian/complexity", + "version": "2.0.2", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "347c1d8b49c5c3ee30c7040ea6fc446790e6bddd" + "url": "https://github.com/sebastianbergmann/complexity.git", + "reference": "739b35e53379900cc9ac327b2147867b8b6efd88" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/347c1d8b49c5c3ee30c7040ea6fc446790e6bddd", - "reference": "347c1d8b49c5c3ee30c7040ea6fc446790e6bddd", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/739b35e53379900cc9ac327b2147867b8b6efd88", + "reference": "739b35e53379900cc9ac327b2147867b8b6efd88", "shasum": "" }, "require": { - "php": "^7.0" + "nikic/php-parser": "^4.7", + "php": ">=7.3" }, "require-dev": { - "phpunit/phpunit": "^6.2" + "phpunit/phpunit": "^9.3" }, "type": "library", "extra": { @@ -3915,45 +4360,118 @@ ], "authors": [ { - "name": "Kore Nordmann", - "email": "mail@kore-nordmann.de" - }, + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for calculating the complexity of PHP code units", + "homepage": "https://github.com/sebastianbergmann/complexity", + "support": { + "issues": "https://github.com/sebastianbergmann/complexity/issues", + "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T15:52:27+00:00" + }, + { + "name": "sebastian/diff", + "version": "4.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/3461e3fccc7cfdfc2720be910d3bd73c69be590d", + "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3", + "symfony/process": "^4.2 || ^5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ { "name": "Sebastian Bergmann", "email": "sebastian@phpunit.de" + }, + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" } ], "description": "Diff implementation", "homepage": "https://github.com/sebastianbergmann/diff", "keywords": [ - "diff" + "diff", + "udiff", + "unidiff", + "unified diff" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/diff/issues", + "source": "https://github.com/sebastianbergmann/diff/tree/4.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } ], - "time": "2017-08-03T08:09:46+00:00" + "time": "2020-10-26T13:10:38+00:00" }, { "name": "sebastian/environment", - "version": "3.1.0", + "version": "5.1.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "cd0871b3975fb7fc44d11314fd1ee20925fce4f5" + "reference": "388b6ced16caa751030f6a69e588299fa09200ac" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/cd0871b3975fb7fc44d11314fd1ee20925fce4f5", - "reference": "cd0871b3975fb7fc44d11314fd1ee20925fce4f5", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/388b6ced16caa751030f6a69e588299fa09200ac", + "reference": "388b6ced16caa751030f6a69e588299fa09200ac", "shasum": "" }, "require": { - "php": "^7.0" + "php": ">=7.3" }, "require-dev": { - "phpunit/phpunit": "^6.1" + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-posix": "*" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.1.x-dev" + "dev-master": "5.1-dev" } }, "autoload": { @@ -3978,34 +4496,44 @@ "environment", "hhvm" ], - "time": "2017-07-01T08:51:00+00:00" + "support": { + "issues": "https://github.com/sebastianbergmann/environment/issues", + "source": "https://github.com/sebastianbergmann/environment/tree/5.1.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T05:52:38+00:00" }, { "name": "sebastian/exporter", - "version": "3.1.0", + "version": "4.0.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "234199f4528de6d12aaa58b612e98f7d36adb937" + "reference": "d89cc98761b8cb5a1a235a6b703ae50d34080e65" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/234199f4528de6d12aaa58b612e98f7d36adb937", - "reference": "234199f4528de6d12aaa58b612e98f7d36adb937", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/d89cc98761b8cb5a1a235a6b703ae50d34080e65", + "reference": "d89cc98761b8cb5a1a235a6b703ae50d34080e65", "shasum": "" }, "require": { - "php": "^7.0", - "sebastian/recursion-context": "^3.0" + "php": ">=7.3", + "sebastian/recursion-context": "^4.0" }, "require-dev": { "ext-mbstring": "*", - "phpunit/phpunit": "^6.0" + "phpunit/phpunit": "^9.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.1.x-dev" + "dev-master": "4.0-dev" } }, "autoload": { @@ -4018,6 +4546,10 @@ "BSD-3-Clause" ], "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, { "name": "Jeff Welch", "email": "whatthejeff@gmail.com" @@ -4026,17 +4558,13 @@ "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" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" } ], "description": "Provides the functionality to export PHP variables for visualization", @@ -4045,27 +4573,40 @@ "export", "exporter" ], - "time": "2017-04-03T13:19:02+00:00" + "support": { + "issues": "https://github.com/sebastianbergmann/exporter/issues", + "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T05:24:23+00:00" }, { "name": "sebastian/global-state", - "version": "2.0.0", + "version": "5.0.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4" + "reference": "23bd5951f7ff26f12d4e3242864df3e08dec4e49" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4", - "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/23bd5951f7ff26f12d4e3242864df3e08dec4e49", + "reference": "23bd5951f7ff26f12d4e3242864df3e08dec4e49", "shasum": "" }, "require": { - "php": "^7.0" + "php": ">=7.3", + "sebastian/object-reflector": "^2.0", + "sebastian/recursion-context": "^4.0" }, "require-dev": { - "phpunit/phpunit": "^6.0" + "ext-dom": "*", + "phpunit/phpunit": "^9.3" }, "suggest": { "ext-uopz": "*" @@ -4073,7 +4614,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-master": "5.0-dev" } }, "autoload": { @@ -4096,34 +4637,101 @@ "keywords": [ "global state" ], - "time": "2017-04-27T15:39:26+00:00" + "support": { + "issues": "https://github.com/sebastianbergmann/global-state/issues", + "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2021-06-11T13:31:12+00:00" + }, + { + "name": "sebastian/lines-of-code", + "version": "1.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/lines-of-code.git", + "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/c1c2e997aa3146983ed888ad08b15470a2e22ecc", + "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.6", + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "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", + "role": "lead" + } + ], + "description": "Library for counting the lines of code in PHP source code", + "homepage": "https://github.com/sebastianbergmann/lines-of-code", + "support": { + "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-11-28T06:42:11+00:00" }, { "name": "sebastian/object-enumerator", - "version": "3.0.3", + "version": "4.0.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/object-enumerator.git", - "reference": "7cfd9e65d11ffb5af41198476395774d4c8a84c5" + "reference": "5c9eeac41b290a3712d88851518825ad78f45c71" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/7cfd9e65d11ffb5af41198476395774d4c8a84c5", - "reference": "7cfd9e65d11ffb5af41198476395774d4c8a84c5", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/5c9eeac41b290a3712d88851518825ad78f45c71", + "reference": "5c9eeac41b290a3712d88851518825ad78f45c71", "shasum": "" }, "require": { - "php": "^7.0", - "sebastian/object-reflector": "^1.1.1", - "sebastian/recursion-context": "^3.0" + "php": ">=7.3", + "sebastian/object-reflector": "^2.0", + "sebastian/recursion-context": "^4.0" }, "require-dev": { - "phpunit/phpunit": "^6.0" + "phpunit/phpunit": "^9.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0.x-dev" + "dev-master": "4.0-dev" } }, "autoload": { @@ -4143,32 +4751,42 @@ ], "description": "Traverses array structures and object graphs to enumerate all referenced objects", "homepage": "https://github.com/sebastianbergmann/object-enumerator/", - "time": "2017-08-03T12:35:26+00:00" + "support": { + "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/4.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:12:34+00:00" }, { "name": "sebastian/object-reflector", - "version": "1.1.1", + "version": "2.0.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/object-reflector.git", - "reference": "773f97c67f28de00d397be301821b06708fca0be" + "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/773f97c67f28de00d397be301821b06708fca0be", - "reference": "773f97c67f28de00d397be301821b06708fca0be", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", + "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", "shasum": "" }, "require": { - "php": "^7.0" + "php": ">=7.3" }, "require-dev": { - "phpunit/phpunit": "^6.0" + "phpunit/phpunit": "^9.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.1-dev" + "dev-master": "2.0-dev" } }, "autoload": { @@ -4188,32 +4806,42 @@ ], "description": "Allows reflection of object attributes, including inherited and non-public ones", "homepage": "https://github.com/sebastianbergmann/object-reflector/", - "time": "2017-03-29T09:07:27+00:00" + "support": { + "issues": "https://github.com/sebastianbergmann/object-reflector/issues", + "source": "https://github.com/sebastianbergmann/object-reflector/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:14:26+00:00" }, { "name": "sebastian/recursion-context", - "version": "3.0.0", + "version": "4.0.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8" + "reference": "cd9d8cf3c5804de4341c283ed787f099f5506172" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8", - "reference": "5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/cd9d8cf3c5804de4341c283ed787f099f5506172", + "reference": "cd9d8cf3c5804de4341c283ed787f099f5506172", "shasum": "" }, "require": { - "php": "^7.0" + "php": ">=7.3" }, "require-dev": { - "phpunit/phpunit": "^6.0" + "phpunit/phpunit": "^9.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0.x-dev" + "dev-master": "4.0-dev" } }, "autoload": { @@ -4226,14 +4854,14 @@ "BSD-3-Clause" ], "authors": [ - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" - }, { "name": "Sebastian Bergmann", "email": "sebastian@phpunit.de" }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, { "name": "Adam Harvey", "email": "aharvey@php.net" @@ -4241,29 +4869,97 @@ ], "description": "Provides functionality to recursively process PHP variables", "homepage": "http://www.github.com/sebastianbergmann/recursion-context", - "time": "2017-03-03T06:23:57+00:00" + "support": { + "issues": "https://github.com/sebastianbergmann/recursion-context/issues", + "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:17:30+00:00" }, { "name": "sebastian/resource-operations", - "version": "1.0.0", + "version": "3.0.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/resource-operations.git", - "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52" + "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", + "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-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/3.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T06:45:17+00:00" + }, + { + "name": "sebastian/type", + "version": "2.3.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/type.git", + "reference": "b8cd8a1c753c90bc1a0f5372170e3e489136f914" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", - "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/b8cd8a1c753c90bc1a0f5372170e3e489136f914", + "reference": "b8cd8a1c753c90bc1a0f5372170e3e489136f914", "shasum": "" }, "require": { - "php": ">=5.6.0" + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "2.3-dev" } }, "autoload": { @@ -4278,34 +4974,45 @@ "authors": [ { "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" + "email": "sebastian@phpunit.de", + "role": "lead" } ], - "description": "Provides a list of PHP built-in functions that operate on resources", - "homepage": "https://www.github.com/sebastianbergmann/resource-operations", - "time": "2015-07-28T20:34:47+00:00" + "description": "Collection of value objects that represent the types of the PHP type system", + "homepage": "https://github.com/sebastianbergmann/type", + "support": { + "issues": "https://github.com/sebastianbergmann/type/issues", + "source": "https://github.com/sebastianbergmann/type/tree/2.3.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2021-06-15T12:49:02+00:00" }, { "name": "sebastian/version", - "version": "2.0.1", + "version": "3.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/version.git", - "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019" + "reference": "c6c1022351a901512170118436c764e473f6de8c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/99732be0ddb3361e16ad77b68ba41efc8e979019", - "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c6c1022351a901512170118436c764e473f6de8c", + "reference": "c6c1022351a901512170118436c764e473f6de8c", "shasum": "" }, "require": { - "php": ">=5.6" + "php": ">=7.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0.x-dev" + "dev-master": "3.0-dev" } }, "autoload": { @@ -4326,24 +5033,34 @@ ], "description": "Library that helps with managing the version number of Git-hosted PHP projects", "homepage": "https://github.com/sebastianbergmann/version", - "time": "2016-10-03T07:35:21+00:00" + "support": { + "issues": "https://github.com/sebastianbergmann/version/issues", + "source": "https://github.com/sebastianbergmann/version/tree/3.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T06:39:44+00:00" }, { "name": "seld/jsonlint", - "version": "1.7.1", + "version": "1.8.3", "source": { "type": "git", "url": "https://github.com/Seldaek/jsonlint.git", - "reference": "d15f59a67ff805a44c50ea0516d2341740f81a38" + "reference": "9ad6ce79c342fbd44df10ea95511a1b24dee5b57" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/jsonlint/zipball/d15f59a67ff805a44c50ea0516d2341740f81a38", - "reference": "d15f59a67ff805a44c50ea0516d2341740f81a38", + "url": "https://api.github.com/repos/Seldaek/jsonlint/zipball/9ad6ce79c342fbd44df10ea95511a1b24dee5b57", + "reference": "9ad6ce79c342fbd44df10ea95511a1b24dee5b57", "shasum": "" }, "require": { - "php": "^5.3 || ^7.0" + "php": "^5.3 || ^7.0 || ^8.0" }, "require-dev": { "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" @@ -4375,20 +5092,34 @@ "parser", "validator" ], - "time": "2018-01-24T12:46:19+00:00" + "support": { + "issues": "https://github.com/Seldaek/jsonlint/issues", + "source": "https://github.com/Seldaek/jsonlint/tree/1.8.3" + }, + "funding": [ + { + "url": "https://github.com/Seldaek", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/seld/jsonlint", + "type": "tidelift" + } + ], + "time": "2020-11-11T09:19:24+00:00" }, { "name": "seld/phar-utils", - "version": "1.0.1", + "version": "1.1.2", "source": { "type": "git", "url": "https://github.com/Seldaek/phar-utils.git", - "reference": "7009b5139491975ef6486545a39f3e6dad5ac30a" + "reference": "749042a2315705d2dfbbc59234dd9ceb22bf3ff0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/phar-utils/zipball/7009b5139491975ef6486545a39f3e6dad5ac30a", - "reference": "7009b5139491975ef6486545a39f3e6dad5ac30a", + "url": "https://api.github.com/repos/Seldaek/phar-utils/zipball/749042a2315705d2dfbbc59234dd9ceb22bf3ff0", + "reference": "749042a2315705d2dfbbc59234dd9ceb22bf3ff0", "shasum": "" }, "require": { @@ -4417,48 +5148,57 @@ ], "description": "PHAR file format utilities, for when PHP phars you up", "keywords": [ - "phra" + "phar" ], - "time": "2015-10-13T18:44:15+00:00" + "support": { + "issues": "https://github.com/Seldaek/phar-utils/issues", + "source": "https://github.com/Seldaek/phar-utils/tree/1.1.2" + }, + "time": "2021-08-19T21:01:38+00:00" }, { - "name": "symfony/browser-kit", - "version": "v3.4.18", + "name": "spomky-labs/otphp", + "version": "v10.0.1", "source": { "type": "git", - "url": "https://github.com/symfony/browser-kit.git", - "reference": "f6668d1a6182d5a8dec65a1c863a4c1d963816c0" + "url": "https://github.com/Spomky-Labs/otphp.git", + "reference": "f44cce5a9db4b8da410215d992110482c931232f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/browser-kit/zipball/f6668d1a6182d5a8dec65a1c863a4c1d963816c0", - "reference": "f6668d1a6182d5a8dec65a1c863a4c1d963816c0", + "url": "https://api.github.com/repos/Spomky-Labs/otphp/zipball/f44cce5a9db4b8da410215d992110482c931232f", + "reference": "f44cce5a9db4b8da410215d992110482c931232f", "shasum": "" }, "require": { - "php": "^5.5.9|>=7.0.8", - "symfony/dom-crawler": "~2.8|~3.0|~4.0" + "beberlei/assert": "^3.0", + "ext-mbstring": "*", + "paragonie/constant_time_encoding": "^2.0", + "php": "^7.2|^8.0", + "thecodingmachine/safe": "^0.1.14|^1.0" }, "require-dev": { - "symfony/css-selector": "~2.8|~3.0|~4.0", - "symfony/process": "~2.8|~3.0|~4.0" - }, - "suggest": { - "symfony/process": "" + "php-coveralls/php-coveralls": "^2.0", + "phpstan/phpstan": "^0.12", + "phpstan/phpstan-beberlei-assert": "^0.12", + "phpstan/phpstan-deprecation-rules": "^0.12", + "phpstan/phpstan-phpunit": "^0.12", + "phpstan/phpstan-strict-rules": "^0.12", + "phpunit/phpunit": "^8.0", + "thecodingmachine/phpstan-safe-rule": "^1.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.4-dev" + "v10.0": "10.0.x-dev", + "v9.0": "9.0.x-dev", + "v8.3": "8.3.x-dev" } }, "autoload": { "psr-4": { - "Symfony\\Component\\BrowserKit\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] + "OTPHP\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -4466,61 +5206,78 @@ ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" + "name": "Florent Morselli", + "homepage": "https://github.com/Spomky" }, { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" + "name": "All contributors", + "homepage": "https://github.com/Spomky-Labs/otphp/contributors" } ], - "description": "Symfony BrowserKit Component", - "homepage": "https://symfony.com", - "time": "2018-07-26T09:06:28+00:00" + "description": "A PHP library for generating one time passwords according to RFC 4226 (HOTP Algorithm) and the RFC 6238 (TOTP Algorithm) and compatible with Google Authenticator", + "homepage": "https://github.com/Spomky-Labs/otphp", + "keywords": [ + "FreeOTP", + "RFC 4226", + "RFC 6238", + "google authenticator", + "hotp", + "otp", + "totp" + ], + "support": { + "issues": "https://github.com/Spomky-Labs/otphp/issues", + "source": "https://github.com/Spomky-Labs/otphp/tree/v10.0.1" + }, + "time": "2020-01-28T09:24:19+00:00" }, { "name": "symfony/console", - "version": "v3.4.18", + "version": "v4.4.30", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "1d228fb4602047d7b26a0554e0d3efd567da5803" + "reference": "a3f7189a0665ee33b50e9e228c46f50f5acbed22" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/1d228fb4602047d7b26a0554e0d3efd567da5803", - "reference": "1d228fb4602047d7b26a0554e0d3efd567da5803", + "url": "https://api.github.com/repos/symfony/console/zipball/a3f7189a0665ee33b50e9e228c46f50f5acbed22", + "reference": "a3f7189a0665ee33b50e9e228c46f50f5acbed22", "shasum": "" }, "require": { - "php": "^5.5.9|>=7.0.8", - "symfony/debug": "~2.8|~3.0|~4.0", - "symfony/polyfill-mbstring": "~1.0" + "php": ">=7.1.3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php73": "^1.8", + "symfony/polyfill-php80": "^1.16", + "symfony/service-contracts": "^1.1|^2" }, "conflict": { + "psr/log": ">=3", "symfony/dependency-injection": "<3.4", + "symfony/event-dispatcher": "<4.3|>=5", + "symfony/lock": "<4.4", "symfony/process": "<3.3" }, + "provide": { + "psr/log-implementation": "1.0|2.0" + }, "require-dev": { - "psr/log": "~1.0", - "symfony/config": "~3.3|~4.0", - "symfony/dependency-injection": "~3.4|~4.0", - "symfony/event-dispatcher": "~2.8|~3.0|~4.0", - "symfony/lock": "~3.4|~4.0", - "symfony/process": "~3.3|~4.0" + "psr/log": "^1|^2", + "symfony/config": "^3.4|^4.0|^5.0", + "symfony/dependency-injection": "^3.4|^4.0|^5.0", + "symfony/event-dispatcher": "^4.3", + "symfony/lock": "^4.4|^5.0", + "symfony/process": "^3.4|^4.0|^5.0", + "symfony/var-dumper": "^4.3|^5.0" }, "suggest": { - "psr/log-implementation": "For using the console logger", + "psr/log": "For using the console logger", "symfony/event-dispatcher": "", "symfony/lock": "", "symfony/process": "" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.4-dev" - } - }, "autoload": { "psr-4": { "Symfony\\Component\\Console\\": "" @@ -4543,33 +5300,46 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony Console Component", + "description": "Eases the creation of beautiful and testable command line interfaces", "homepage": "https://symfony.com", - "time": "2018-10-30T16:50:50+00:00" + "support": { + "source": "https://github.com/symfony/console/tree/v4.4.30" + }, + "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": "2021-08-25T19:27:26+00:00" }, { "name": "symfony/css-selector", - "version": "v3.4.18", + "version": "v5.3.4", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", - "reference": "3503415d4aafabc31cd08c3a4ebac7f43fde8feb" + "reference": "7fb120adc7f600a59027775b224c13a33530dd90" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/3503415d4aafabc31cd08c3a4ebac7f43fde8feb", - "reference": "3503415d4aafabc31cd08c3a4ebac7f43fde8feb", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/7fb120adc7f600a59027775b224c13a33530dd90", + "reference": "7fb120adc7f600a59027775b224c13a33530dd90", "shasum": "" }, "require": { - "php": "^5.5.9|>=7.0.8" + "php": ">=7.2.5", + "symfony/polyfill-php80": "^1.16" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.4-dev" - } - }, "autoload": { "psr-4": { "Symfony\\Component\\CssSelector\\": "" @@ -4583,59 +5353,70 @@ "MIT" ], "authors": [ - { - "name": "Jean-François Simon", - "email": "jeanfrancois.simon@sensiolabs.com" - }, { "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", + "description": "Converts CSS selectors to XPath expressions", "homepage": "https://symfony.com", - "time": "2018-10-02T16:33:53+00:00" + "support": { + "source": "https://github.com/symfony/css-selector/tree/v5.3.4" + }, + "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": "2021-07-21T12:38:00+00:00" }, { - "name": "symfony/debug", - "version": "v3.4.18", + "name": "symfony/deprecation-contracts", + "version": "v2.4.0", "source": { "type": "git", - "url": "https://github.com/symfony/debug.git", - "reference": "fe9793af008b651c5441bdeab21ede8172dab097" + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "5f38c8804a9e97d23e0c8d63341088cd8a22d627" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/debug/zipball/fe9793af008b651c5441bdeab21ede8172dab097", - "reference": "fe9793af008b651c5441bdeab21ede8172dab097", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/5f38c8804a9e97d23e0c8d63341088cd8a22d627", + "reference": "5f38c8804a9e97d23e0c8d63341088cd8a22d627", "shasum": "" }, "require": { - "php": "^5.5.9|>=7.0.8", - "psr/log": "~1.0" - }, - "conflict": { - "symfony/http-kernel": ">=2.3,<2.3.24|~2.4.0|>=2.5,<2.5.9|>=2.6,<2.6.2" - }, - "require-dev": { - "symfony/http-kernel": "~2.8|~3.0|~4.0" + "php": ">=7.1" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.4-dev" + "dev-main": "2.4-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" } }, "autoload": { - "psr-4": { - "Symfony\\Component\\Debug\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" + "files": [ + "function.php" ] }, "notification-url": "https://packagist.org/downloads/", @@ -4644,52 +5425,60 @@ ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony Debug Component", + "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", - "time": "2018-10-31T09:06:03+00:00" + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v2.4.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": "2021-03-23T23:28:01+00:00" }, { - "name": "symfony/dom-crawler", - "version": "v3.4.18", + "name": "symfony/dotenv", + "version": "v5.3.8", "source": { "type": "git", - "url": "https://github.com/symfony/dom-crawler.git", - "reference": "c705bee03ade5b47c087807dd9ffaaec8dda2722" + "url": "https://github.com/symfony/dotenv.git", + "reference": "12888c9c46ac750ec5c1381db5bf3d534e7d70cb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/c705bee03ade5b47c087807dd9ffaaec8dda2722", - "reference": "c705bee03ade5b47c087807dd9ffaaec8dda2722", + "url": "https://api.github.com/repos/symfony/dotenv/zipball/12888c9c46ac750ec5c1381db5bf3d534e7d70cb", + "reference": "12888c9c46ac750ec5c1381db5bf3d534e7d70cb", "shasum": "" }, "require": { - "php": "^5.5.9|>=7.0.8", - "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-mbstring": "~1.0" + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1" }, "require-dev": { - "symfony/css-selector": "~2.8|~3.0|~4.0" - }, - "suggest": { - "symfony/css-selector": "" + "symfony/process": "^4.4|^5.0" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.4-dev" - } - }, "autoload": { "psr-4": { - "Symfony\\Component\\DomCrawler\\": "" + "Symfony\\Component\\Dotenv\\": "" }, "exclude-from-classmap": [ "/Tests/" @@ -4709,47 +5498,73 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony DomCrawler Component", + "description": "Registers environment variables from a .env file", "homepage": "https://symfony.com", - "time": "2018-10-02T12:28:39+00:00" + "keywords": [ + "dotenv", + "env", + "environment" + ], + "support": { + "source": "https://github.com/symfony/dotenv/tree/v5.3.8" + }, + "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": "2021-07-29T06:18:06+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v3.4.18", + "version": "v4.4.30", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "db9e829c8f34c3d35cf37fcd4cdb4293bc4a2f14" + "reference": "2fe81680070043c4c80e7cedceb797e34f377bac" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/db9e829c8f34c3d35cf37fcd4cdb4293bc4a2f14", - "reference": "db9e829c8f34c3d35cf37fcd4cdb4293bc4a2f14", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/2fe81680070043c4c80e7cedceb797e34f377bac", + "reference": "2fe81680070043c4c80e7cedceb797e34f377bac", "shasum": "" }, "require": { - "php": "^5.5.9|>=7.0.8" + "php": ">=7.1.3", + "symfony/event-dispatcher-contracts": "^1.1", + "symfony/polyfill-php80": "^1.16" }, "conflict": { - "symfony/dependency-injection": "<3.3" + "symfony/dependency-injection": "<3.4" + }, + "provide": { + "psr/event-dispatcher-implementation": "1.0", + "symfony/event-dispatcher-implementation": "1.1" }, "require-dev": { - "psr/log": "~1.0", - "symfony/config": "~2.8|~3.0|~4.0", - "symfony/dependency-injection": "~3.3|~4.0", - "symfony/expression-language": "~2.8|~3.0|~4.0", - "symfony/stopwatch": "~2.8|~3.0|~4.0" + "psr/log": "^1|^2|^3", + "symfony/config": "^3.4|^4.0|^5.0", + "symfony/dependency-injection": "^3.4|^4.0|^5.0", + "symfony/error-handler": "~3.4|~4.4", + "symfony/expression-language": "^3.4|^4.0|^5.0", + "symfony/http-foundation": "^3.4|^4.0|^5.0", + "symfony/service-contracts": "^1.1|^2", + "symfony/stopwatch": "^3.4|^4.0|^5.0" }, "suggest": { "symfony/dependency-injection": "", "symfony/http-kernel": "" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.4-dev" - } - }, "autoload": { "psr-4": { "Symfony\\Component\\EventDispatcher\\": "" @@ -4772,34 +5587,126 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony EventDispatcher Component", + "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", "homepage": "https://symfony.com", - "time": "2018-10-30T16:50:50+00:00" + "support": { + "source": "https://github.com/symfony/event-dispatcher/tree/v4.4.30" + }, + "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": "2021-08-04T20:31:23+00:00" }, { - "name": "symfony/filesystem", - "version": "v3.4.18", + "name": "symfony/event-dispatcher-contracts", + "version": "v1.1.9", "source": { "type": "git", - "url": "https://github.com/symfony/filesystem.git", - "reference": "d69930fc337d767607267d57c20a7403d0a822a4" + "url": "https://github.com/symfony/event-dispatcher-contracts.git", + "reference": "84e23fdcd2517bf37aecbd16967e83f0caee25a7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/d69930fc337d767607267d57c20a7403d0a822a4", - "reference": "d69930fc337d767607267d57c20a7403d0a822a4", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/84e23fdcd2517bf37aecbd16967e83f0caee25a7", + "reference": "84e23fdcd2517bf37aecbd16967e83f0caee25a7", "shasum": "" }, "require": { - "php": "^5.5.9|>=7.0.8", - "symfony/polyfill-ctype": "~1.8" + "php": ">=7.1.3" + }, + "suggest": { + "psr/event-dispatcher": "", + "symfony/event-dispatcher-implementation": "" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.4-dev" + "dev-master": "1.1-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\EventDispatcher\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to dispatching event", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v1.1.9" + }, + "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-07-06T13:19:58+00:00" + }, + { + "name": "symfony/filesystem", + "version": "v5.3.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/filesystem.git", + "reference": "343f4fe324383ca46792cae728a3b6e2f708fb32" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/343f4fe324383ca46792cae728a3b6e2f708fb32", + "reference": "343f4fe324383ca46792cae728a3b6e2f708fb32", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-php80": "^1.16" }, + "type": "library", "autoload": { "psr-4": { "Symfony\\Component\\Filesystem\\": "" @@ -4822,33 +5729,46 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony Filesystem Component", + "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", - "time": "2018-10-02T12:28:39+00:00" + "support": { + "source": "https://github.com/symfony/filesystem/tree/v5.3.4" + }, + "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": "2021-07-21T12:40:44+00:00" }, { "name": "symfony/finder", - "version": "v3.4.18", + "version": "v5.3.7", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "54ba444dddc5bd5708a34bd095ea67c6eb54644d" + "reference": "a10000ada1e600d109a6c7632e9ac42e8bf2fb93" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/54ba444dddc5bd5708a34bd095ea67c6eb54644d", - "reference": "54ba444dddc5bd5708a34bd095ea67c6eb54644d", + "url": "https://api.github.com/repos/symfony/finder/zipball/a10000ada1e600d109a6c7632e9ac42e8bf2fb93", + "reference": "a10000ada1e600d109a6c7632e9ac42e8bf2fb93", "shasum": "" }, "require": { - "php": "^5.5.9|>=7.0.8" + "php": ">=7.2.5", + "symfony/polyfill-php80": "^1.16" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.4-dev" - } - }, "autoload": { "psr-4": { "Symfony\\Component\\Finder\\": "" @@ -4871,41 +5791,139 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony Finder Component", + "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", - "time": "2018-10-03T08:46:40+00:00" + "support": { + "source": "https://github.com/symfony/finder/tree/v5.3.7" + }, + "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": "2021-08-04T21:20:46+00:00" }, { "name": "symfony/http-foundation", - "version": "v3.4.18", + "version": "v5.3.7", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "5aea7a86ca3203dd7a257e765b4b9c9cfd01c6c0" + "reference": "e36c8e5502b4f3f0190c675f1c1f1248a64f04e5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/5aea7a86ca3203dd7a257e765b4b9c9cfd01c6c0", - "reference": "5aea7a86ca3203dd7a257e765b4b9c9cfd01c6c0", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/e36c8e5502b4f3f0190c675f1c1f1248a64f04e5", + "reference": "e36c8e5502b4f3f0190c675f1c1f1248a64f04e5", "shasum": "" }, "require": { - "php": "^5.5.9|>=7.0.8", + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1", "symfony/polyfill-mbstring": "~1.1", - "symfony/polyfill-php70": "~1.6" + "symfony/polyfill-php80": "^1.16" }, "require-dev": { - "symfony/expression-language": "~2.8|~3.0|~4.0" + "predis/predis": "~1.0", + "symfony/cache": "^4.4|^5.0", + "symfony/expression-language": "^4.4|^5.0", + "symfony/mime": "^4.4|^5.0" + }, + "suggest": { + "symfony/mime": "To use the file extension guesser" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.4-dev" + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpFoundation\\": "" + }, + "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": "Defines an object-oriented layer for the HTTP specification", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/http-foundation/tree/v5.3.7" + }, + "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": "2021-08-27T11:20:35+00:00" + }, + { + "name": "symfony/mime", + "version": "v5.3.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/mime.git", + "reference": "a756033d0a7e53db389618653ae991eba5a19a11" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/mime/zipball/a756033d0a7e53db389618653ae991eba5a19a11", + "reference": "a756033d0a7e53db389618653ae991eba5a19a11", + "shasum": "" }, + "require": { + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1", + "symfony/polyfill-intl-idn": "^1.10", + "symfony/polyfill-mbstring": "^1.0", + "symfony/polyfill-php80": "^1.16" + }, + "conflict": { + "egulias/email-validator": "~3.0.0", + "phpdocumentor/reflection-docblock": "<3.2.2", + "phpdocumentor/type-resolver": "<1.4.0", + "symfony/mailer": "<4.4" + }, + "require-dev": { + "egulias/email-validator": "^2.1.10|^3.1", + "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", + "symfony/dependency-injection": "^4.4|^5.0", + "symfony/property-access": "^4.4|^5.1", + "symfony/property-info": "^4.4|^5.1", + "symfony/serializer": "^5.2" + }, + "type": "library", "autoload": { "psr-4": { - "Symfony\\Component\\HttpFoundation\\": "" + "Symfony\\Component\\Mime\\": "" }, "exclude-from-classmap": [ "/Tests/" @@ -4925,26 +5943,47 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony HttpFoundation Component", + "description": "Allows manipulating MIME messages", "homepage": "https://symfony.com", - "time": "2018-10-31T08:57:11+00:00" + "keywords": [ + "mime", + "mime-type" + ], + "support": { + "source": "https://github.com/symfony/mime/tree/v5.3.8" + }, + "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": "2021-09-10T12:30:38+00:00" }, { "name": "symfony/polyfill-ctype", - "version": "v1.11.0", + "version": "v1.23.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "82ebae02209c21113908c229e9883c419720738a" + "reference": "46cd95797e9df938fdd2b03693b5fca5e64b01ce" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/82ebae02209c21113908c229e9883c419720738a", - "reference": "82ebae02209c21113908c229e9883c419720738a", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/46cd95797e9df938fdd2b03693b5fca5e64b01ce", + "reference": "46cd95797e9df938fdd2b03693b5fca5e64b01ce", "shasum": "" }, "require": { - "php": ">=5.3.3" + "php": ">=7.1" }, "suggest": { "ext-ctype": "For best performance" @@ -4952,29 +5991,33 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.11-dev" + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" } }, "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Ctype\\": "" - }, "files": [ "bootstrap.php" - ] + ], + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "authors": [ - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - }, { "name": "Gert de Pagter", "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], "description": "Symfony polyfill for ctype functions", @@ -4985,41 +6028,64 @@ "polyfill", "portable" ], - "time": "2019-02-06T07:57:58+00:00" + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.23.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": "2021-02-19T12:13:01+00:00" }, { - "name": "symfony/polyfill-mbstring", - "version": "v1.10.0", + "name": "symfony/polyfill-intl-idn", + "version": "v1.23.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "c79c051f5b3a46be09205c73b80b346e4153e494" + "url": "https://github.com/symfony/polyfill-intl-idn.git", + "reference": "65bd267525e82759e7d8c4e8ceea44f398838e65" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/c79c051f5b3a46be09205c73b80b346e4153e494", - "reference": "c79c051f5b3a46be09205c73b80b346e4153e494", + "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/65bd267525e82759e7d8c4e8ceea44f398838e65", + "reference": "65bd267525e82759e7d8c4e8ceea44f398838e65", "shasum": "" }, "require": { - "php": ">=5.3.3" + "php": ">=7.1", + "symfony/polyfill-intl-normalizer": "^1.10", + "symfony/polyfill-php72": "^1.10" }, "suggest": { - "ext-mbstring": "For best performance" + "ext-intl": "For best performance" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.9-dev" + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" } }, "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Mbstring\\": "" - }, "files": [ "bootstrap.php" - ] + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Idn\\": "" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -5027,56 +6093,84 @@ ], "authors": [ { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" + "name": "Laurent Bassin", + "email": "laurent@bassin.info" + }, + { + "name": "Trevor Rowbotham", + "email": "trevor.rowbotham@pm.me" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill for the Mbstring extension", + "description": "Symfony polyfill for intl's idn_to_ascii and idn_to_utf8 functions", "homepage": "https://symfony.com", "keywords": [ "compatibility", - "mbstring", + "idn", + "intl", "polyfill", "portable", "shim" ], - "time": "2018-09-21T13:07:52+00:00" + "support": { + "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.23.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": "2021-05-27T09:27:20+00:00" }, { - "name": "symfony/polyfill-php70", - "version": "v1.10.0", + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.23.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-php70.git", - "reference": "6b88000cdd431cd2e940caa2cb569201f3f84224" + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "8590a5f561694770bdcd3f9b5c69dde6945028e8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php70/zipball/6b88000cdd431cd2e940caa2cb569201f3f84224", - "reference": "6b88000cdd431cd2e940caa2cb569201f3f84224", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/8590a5f561694770bdcd3f9b5c69dde6945028e8", + "reference": "8590a5f561694770bdcd3f9b5c69dde6945028e8", "shasum": "" }, "require": { - "paragonie/random_compat": "~1.0|~2.0|~9.99", - "php": ">=5.3.3" + "php": ">=7.1" + }, + "suggest": { + "ext-intl": "For best performance" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.9-dev" + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" } }, "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Php70\\": "" - }, "files": [ "bootstrap.php" ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + }, "classmap": [ "Resources/stubs" ] @@ -5095,46 +6189,72 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill backporting some PHP 7.0+ features to lower PHP versions", + "description": "Symfony polyfill for intl's Normalizer class and related functions", "homepage": "https://symfony.com", "keywords": [ "compatibility", + "intl", + "normalizer", "polyfill", "portable", "shim" ], - "time": "2018-09-21T06:26:08+00:00" + "support": { + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.23.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": "2021-02-19T12:13:01+00:00" }, { - "name": "symfony/process", - "version": "v3.4.18", + "name": "symfony/polyfill-mbstring", + "version": "v1.23.1", "source": { "type": "git", - "url": "https://github.com/symfony/process.git", - "reference": "35c2914a9f50519bd207164c353ae4d59182c2cb" + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "9174a3d80210dca8daa7f31fec659150bbeabfc6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/35c2914a9f50519bd207164c353ae4d59182c2cb", - "reference": "35c2914a9f50519bd207164c353ae4d59182c2cb", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9174a3d80210dca8daa7f31fec659150bbeabfc6", + "reference": "9174a3d80210dca8daa7f31fec659150bbeabfc6", "shasum": "" }, "require": { - "php": "^5.5.9|>=7.0.8" + "php": ">=7.1" + }, + "suggest": { + "ext-mbstring": "For best performance" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.4-dev" + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" } }, "autoload": { + "files": [ + "bootstrap.php" + ], "psr-4": { - "Symfony\\Component\\Process\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] + "Symfony\\Polyfill\\Mbstring\\": "" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -5142,58 +6262,76 @@ ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony Process Component", + "description": "Symfony polyfill for the Mbstring extension", "homepage": "https://symfony.com", - "time": "2018-10-14T17:33:21+00:00" + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.23.1" + }, + "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": "2021-05-27T12:26:48+00:00" }, { - "name": "symfony/yaml", - "version": "v3.4.28", + "name": "symfony/polyfill-php72", + "version": "v1.23.0", "source": { "type": "git", - "url": "https://github.com/symfony/yaml.git", - "reference": "212a27b731e5bfb735679d1ffaac82bd6a1dc996" + "url": "https://github.com/symfony/polyfill-php72.git", + "reference": "9a142215a36a3888e30d0a9eeea9766764e96976" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/212a27b731e5bfb735679d1ffaac82bd6a1dc996", - "reference": "212a27b731e5bfb735679d1ffaac82bd6a1dc996", + "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/9a142215a36a3888e30d0a9eeea9766764e96976", + "reference": "9a142215a36a3888e30d0a9eeea9766764e96976", "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" + "php": ">=7.1" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.4-dev" + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" } }, "autoload": { + "files": [ + "bootstrap.php" + ], "psr-4": { - "Symfony\\Component\\Yaml\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] + "Symfony\\Polyfill\\Php72\\": "" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -5201,139 +6339,240 @@ ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony Yaml Component", + "description": "Symfony polyfill backporting some PHP 7.2+ features to lower PHP versions", "homepage": "https://symfony.com", - "time": "2019-03-25T07:48:46+00:00" + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php72/tree/v1.23.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": "2021-05-27T09:17:38+00:00" }, { - "name": "theseer/tokenizer", - "version": "1.1.0", + "name": "symfony/polyfill-php73", + "version": "v1.23.0", "source": { "type": "git", - "url": "https://github.com/theseer/tokenizer.git", - "reference": "cb2f008f3f05af2893a87208fe6a6c4985483f8b" + "url": "https://github.com/symfony/polyfill-php73.git", + "reference": "fba8933c384d6476ab14fb7b8526e5287ca7e010" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theseer/tokenizer/zipball/cb2f008f3f05af2893a87208fe6a6c4985483f8b", - "reference": "cb2f008f3f05af2893a87208fe6a6c4985483f8b", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/fba8933c384d6476ab14fb7b8526e5287ca7e010", + "reference": "fba8933c384d6476ab14fb7b8526e5287ca7e010", "shasum": "" }, "require": { - "ext-dom": "*", - "ext-tokenizer": "*", - "ext-xmlwriter": "*", - "php": "^7.0" + "php": ">=7.1" }, "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php73\\": "" + }, "classmap": [ - "src/" + "Resources/stubs" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Arne Blankerts", - "email": "arne@blankerts.de", - "role": "Developer" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", - "time": "2017-04-07T12:08:54+00:00" + "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php73/tree/v1.23.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": "2021-02-19T12:13:01+00:00" }, { - "name": "vlucas/phpdotenv", - "version": "v2.5.1", + "name": "symfony/polyfill-php80", + "version": "v1.23.1", "source": { "type": "git", - "url": "https://github.com/vlucas/phpdotenv.git", - "reference": "8abb4f9aa89ddea9d52112c65bbe8d0125e2fa8e" + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "1100343ed1a92e3a38f9ae122fc0eb21602547be" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/8abb4f9aa89ddea9d52112c65bbe8d0125e2fa8e", - "reference": "8abb4f9aa89ddea9d52112c65bbe8d0125e2fa8e", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/1100343ed1a92e3a38f9ae122fc0eb21602547be", + "reference": "1100343ed1a92e3a38f9ae122fc0eb21602547be", "shasum": "" }, "require": { - "php": ">=5.3.9" - }, - "require-dev": { - "phpunit/phpunit": "^4.8.35 || ^5.0" + "php": ">=7.1" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.5-dev" + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" } }, "autoload": { + "files": [ + "bootstrap.php" + ], "psr-4": { - "Dotenv\\": "src/" - } + "Symfony\\Polyfill\\Php80\\": "" + }, + "classmap": [ + "Resources/stubs" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Vance Lucas", - "email": "vance@vancelucas.com", - "homepage": "http://www.vancelucas.com" + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "Loads environment variables from `.env` to `getenv()`, `$_ENV` and `$_SERVER` automagically.", + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", "keywords": [ - "dotenv", - "env", - "environment" + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.23.1" + }, + "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": "2018-07-29T20:33:41+00:00" + "time": "2021-07-28T13:41:28+00:00" }, { - "name": "webmozart/assert", - "version": "1.3.0", + "name": "symfony/polyfill-php81", + "version": "v1.23.0", "source": { "type": "git", - "url": "https://github.com/webmozart/assert.git", - "reference": "0df1908962e7a3071564e857d86874dad1ef204a" + "url": "https://github.com/symfony/polyfill-php81.git", + "reference": "e66119f3de95efc359483f810c4c3e6436279436" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webmozart/assert/zipball/0df1908962e7a3071564e857d86874dad1ef204a", - "reference": "0df1908962e7a3071564e857d86874dad1ef204a", + "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/e66119f3de95efc359483f810c4c3e6436279436", + "reference": "e66119f3de95efc359483f810c4c3e6436279436", "shasum": "" }, "require": { - "php": "^5.3.3 || ^7.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.6", - "sebastian/version": "^1.0.1" + "php": ">=7.1" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.3-dev" + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" } }, "autoload": { + "files": [ + "bootstrap.php" + ], "psr-4": { - "Webmozart\\Assert\\": "src/" - } + "Symfony\\Polyfill\\Php81\\": "" + }, + "classmap": [ + "Resources/stubs" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -5341,40 +6580,66 @@ ], "authors": [ { - "name": "Bernhard Schussek", - "email": "bschussek@gmail.com" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "Assertions to validate method input/output with nice error messages.", + "description": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions", + "homepage": "https://symfony.com", "keywords": [ - "assert", - "check", - "validate" + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php81/tree/v1.23.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": "2018-01-29T19:49:41+00:00" + "time": "2021-05-21T13:25:03+00:00" }, { - "name": "weew/helpers-array", - "version": "v1.3.1", + "name": "symfony/process", + "version": "v4.4.30", "source": { "type": "git", - "url": "https://github.com/weew/helpers-array.git", - "reference": "9bff63111f9765b4277750db8d276d92b3e16ed0" + "url": "https://github.com/symfony/process.git", + "reference": "13d3161ef63a8ec21eeccaaf9a4d7f784a87a97d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/weew/helpers-array/zipball/9bff63111f9765b4277750db8d276d92b3e16ed0", - "reference": "9bff63111f9765b4277750db8d276d92b3e16ed0", + "url": "https://api.github.com/repos/symfony/process/zipball/13d3161ef63a8ec21eeccaaf9a4d7f784a87a97d", + "reference": "13d3161ef63a8ec21eeccaaf9a4d7f784a87a97d", "shasum": "" }, - "require-dev": { - "phpunit/phpunit": "^4.7", - "satooshi/php-coveralls": "^0.6.1" + "require": { + "php": ">=7.1.3", + "symfony/polyfill-php80": "^1.16" }, "type": "library", "autoload": { - "files": [ - "src/array.php" + "psr-4": { + "Symfony\\Component\\Process\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" ] }, "notification-url": "https://packagist.org/downloads/", @@ -5383,63 +6648,70 @@ ], "authors": [ { - "name": "Maxim Kott", - "email": "maximkott@gmail.com" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "Useful collection of php array helpers.", - "time": "2016-07-21T11:18:01+00:00" - } - ], - "packages-dev": [ + "description": "Executes commands in sub-processes", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/process/tree/v4.4.30" + }, + "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": "2021-08-04T20:31:23+00:00" + }, { - "name": "brainmaestro/composer-git-hooks", - "version": "v2.6.1", + "name": "symfony/service-contracts", + "version": "v2.4.0", "source": { "type": "git", - "url": "https://github.com/BrainMaestro/composer-git-hooks.git", - "reference": "137dd2aec2be494918f8bdfb18f57b55ff20015e" + "url": "https://github.com/symfony/service-contracts.git", + "reference": "f040a30e04b57fbcc9c6cbcf4dbaa96bd318b9bb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/BrainMaestro/composer-git-hooks/zipball/137dd2aec2be494918f8bdfb18f57b55ff20015e", - "reference": "137dd2aec2be494918f8bdfb18f57b55ff20015e", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/f040a30e04b57fbcc9c6cbcf4dbaa96bd318b9bb", + "reference": "f040a30e04b57fbcc9c6cbcf4dbaa96bd318b9bb", "shasum": "" }, "require": { - "php": "^5.6 || >=7.0", - "symfony/console": "^3.2 || ^4.0" + "php": ">=7.2.5", + "psr/container": "^1.1" }, - "require-dev": { - "friendsofphp/php-cs-fixer": "^2.9", - "phpunit/phpunit": "^5.7|^7.0" + "suggest": { + "symfony/service-implementation": "" }, - "bin": [ - "cghooks" - ], "type": "library", "extra": { - "hooks": { - "pre-commit": "composer check-style", - "pre-push": [ - "composer test", - "appver=$(grep -o -P '\\d.\\d.\\d' cghooks)", - "tag=$(git tag --sort=-v:refname | head -n 1 | tr -d v)", - "if [ \"$tag\" != \"$appver\" ]; then", - "echo \"The most recent tag v$tag does not match the application version $appver\n\"", - "sed -i -E \"s/$appver/$tag/\" cghooks", - "exit 1", - "fi" - ] + "branch-alias": { + "dev-main": "2.4-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" } }, "autoload": { "psr-4": { - "BrainMaestro\\GitHooks\\": "src/" - }, - "files": [ - "src/helpers.php" - ] + "Symfony\\Contracts\\Service\\": "" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -5447,47 +6719,81 @@ ], "authors": [ { - "name": "Ezinwa Okpoechi", - "email": "brainmaestro@outlook.com" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "Easily manage git hooks in your composer config", + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", "keywords": [ - "HOOK", - "composer", - "git" + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/service-contracts/tree/v2.4.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": "2018-12-28T14:57:06+00:00" + "time": "2021-04-01T10:43:52+00:00" }, { - "name": "codacy/coverage", - "version": "1.4.2", + "name": "symfony/yaml", + "version": "v5.3.6", "source": { "type": "git", - "url": "https://github.com/codacy/php-codacy-coverage.git", - "reference": "4988cd098db4d578681bfd3176071931ad475150" + "url": "https://github.com/symfony/yaml.git", + "reference": "4500fe63dc9c6ffc32d3b1cb0448c329f9c814b7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/codacy/php-codacy-coverage/zipball/4988cd098db4d578681bfd3176071931ad475150", - "reference": "4988cd098db4d578681bfd3176071931ad475150", + "url": "https://api.github.com/repos/symfony/yaml/zipball/4500fe63dc9c6ffc32d3b1cb0448c329f9c814b7", + "reference": "4500fe63dc9c6ffc32d3b1cb0448c329f9c814b7", "shasum": "" }, "require": { - "gitonomy/gitlib": ">=1.0", - "php": ">=5.3.3", - "symfony/console": "~2.5|~3.0|~4.0" + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1", + "symfony/polyfill-ctype": "~1.8" + }, + "conflict": { + "symfony/console": "<4.4" }, "require-dev": { - "phpunit/phpunit": "~6.5" + "symfony/console": "^4.4|^5.0" + }, + "suggest": { + "symfony/console": "For validating YAML files using the lint command" }, "bin": [ - "bin/codacycoverage" + "Resources/bin/yaml-lint" ], "type": "library", "autoload": { - "classmap": [ - "src/" + "psr-4": { + "Symfony\\Component\\Yaml\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" ] }, "notification-url": "https://packagist.org/downloads/", @@ -5496,158 +6802,258 @@ ], "authors": [ { - "name": "Jakob Pupke", - "email": "jakob.pupke@gmail.com" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "Sends PHP test coverage information to Codacy.", - "homepage": "https://github.com/codacy/php-codacy-coverage", - "time": "2018-03-22T16:43:39+00:00" + "description": "Loads and dumps YAML files", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/yaml/tree/v5.3.6" + }, + "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": "2021-07-29T06:20:01+00:00" }, { - "name": "codeception/aspect-mock", - "version": "3.0.2", + "name": "thecodingmachine/safe", + "version": "v1.3.3", "source": { "type": "git", - "url": "https://github.com/Codeception/AspectMock.git", - "reference": "130afd10a3d8131d267f393ee1ec322e3e583d67" + "url": "https://github.com/thecodingmachine/safe.git", + "reference": "a8ab0876305a4cdaef31b2350fcb9811b5608dbc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Codeception/AspectMock/zipball/130afd10a3d8131d267f393ee1ec322e3e583d67", - "reference": "130afd10a3d8131d267f393ee1ec322e3e583d67", + "url": "https://api.github.com/repos/thecodingmachine/safe/zipball/a8ab0876305a4cdaef31b2350fcb9811b5608dbc", + "reference": "a8ab0876305a4cdaef31b2350fcb9811b5608dbc", "shasum": "" }, "require": { - "goaop/framework": "^2.2.0", - "php": ">=7.0.0", - "phpunit/phpunit": "> 6.0.0", - "symfony/finder": "~2.4|~3.0|~4.0" + "php": ">=7.2" }, "require-dev": { - "codeception/base": "^2.4", - "codeception/specify": "~0.3", - "codeception/verify": "~0.2" + "phpstan/phpstan": "^0.12", + "squizlabs/php_codesniffer": "^3.2", + "thecodingmachine/phpstan-strict-rules": "^0.12" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "0.1-dev" + } + }, "autoload": { - "psr-0": { - "AspectMock": "src/" + "files": [ + "deprecated/apc.php", + "deprecated/libevent.php", + "deprecated/mssql.php", + "deprecated/stats.php", + "lib/special_cases.php", + "generated/apache.php", + "generated/apcu.php", + "generated/array.php", + "generated/bzip2.php", + "generated/calendar.php", + "generated/classobj.php", + "generated/com.php", + "generated/cubrid.php", + "generated/curl.php", + "generated/datetime.php", + "generated/dir.php", + "generated/eio.php", + "generated/errorfunc.php", + "generated/exec.php", + "generated/fileinfo.php", + "generated/filesystem.php", + "generated/filter.php", + "generated/fpm.php", + "generated/ftp.php", + "generated/funchand.php", + "generated/gmp.php", + "generated/gnupg.php", + "generated/hash.php", + "generated/ibase.php", + "generated/ibmDb2.php", + "generated/iconv.php", + "generated/image.php", + "generated/imap.php", + "generated/info.php", + "generated/ingres-ii.php", + "generated/inotify.php", + "generated/json.php", + "generated/ldap.php", + "generated/libxml.php", + "generated/lzf.php", + "generated/mailparse.php", + "generated/mbstring.php", + "generated/misc.php", + "generated/msql.php", + "generated/mysql.php", + "generated/mysqli.php", + "generated/mysqlndMs.php", + "generated/mysqlndQc.php", + "generated/network.php", + "generated/oci8.php", + "generated/opcache.php", + "generated/openssl.php", + "generated/outcontrol.php", + "generated/password.php", + "generated/pcntl.php", + "generated/pcre.php", + "generated/pdf.php", + "generated/pgsql.php", + "generated/posix.php", + "generated/ps.php", + "generated/pspell.php", + "generated/readline.php", + "generated/rpminfo.php", + "generated/rrd.php", + "generated/sem.php", + "generated/session.php", + "generated/shmop.php", + "generated/simplexml.php", + "generated/sockets.php", + "generated/sodium.php", + "generated/solr.php", + "generated/spl.php", + "generated/sqlsrv.php", + "generated/ssdeep.php", + "generated/ssh2.php", + "generated/stream.php", + "generated/strings.php", + "generated/swoole.php", + "generated/uodbc.php", + "generated/uopz.php", + "generated/url.php", + "generated/var.php", + "generated/xdiff.php", + "generated/xml.php", + "generated/xmlrpc.php", + "generated/yaml.php", + "generated/yaz.php", + "generated/zip.php", + "generated/zlib.php" + ], + "psr-4": { + "Safe\\": [ + "lib/", + "deprecated/", + "generated/" + ] } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], - "authors": [ - { - "name": "Michael Bodnarchuk", - "email": "davert@codeception.com" - } - ], - "description": "Experimental Mocking Framework powered by Aspects", - "time": "2018-10-07T16:21:11+00:00" + "description": "PHP core functions that throw exceptions instead of returning FALSE on error", + "support": { + "issues": "https://github.com/thecodingmachine/safe/issues", + "source": "https://github.com/thecodingmachine/safe/tree/v1.3.3" + }, + "time": "2020-10-28T17:51:34+00:00" }, { - "name": "gitonomy/gitlib", - "version": "v1.0.4", + "name": "theseer/tokenizer", + "version": "1.2.1", "source": { "type": "git", - "url": "https://github.com/gitonomy/gitlib.git", - "reference": "932a960221ae3484a3e82553b3be478e56beb68d" + "url": "https://github.com/theseer/tokenizer.git", + "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/gitonomy/gitlib/zipball/932a960221ae3484a3e82553b3be478e56beb68d", - "reference": "932a960221ae3484a3e82553b3be478e56beb68d", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/34a41e998c2183e22995f158c581e7b5e755ab9e", + "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e", "shasum": "" }, "require": { - "php": "^5.3 || ^7.0", - "symfony/process": "^2.3|^3.0|^4.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.8.35|^5.7", - "psr/log": "^1.0" - }, - "suggest": { - "psr/log": "Add some log" + "ext-dom": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": "^7.2 || ^8.0" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - } - }, "autoload": { - "psr-4": { - "Gitonomy\\Git\\": "src/Gitonomy/Git/" - } + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Alexandre Salomé", - "email": "alexandre.salome@gmail.com", - "homepage": "http://alexandre-salome.fr" - }, + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + } + ], + "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", + "support": { + "issues": "https://github.com/theseer/tokenizer/issues", + "source": "https://github.com/theseer/tokenizer/tree/1.2.1" + }, + "funding": [ { - "name": "Julien DIDIER", - "email": "genzo.wm@gmail.com", - "homepage": "http://www.jdidier.net" + "url": "https://github.com/theseer", + "type": "github" } ], - "description": "Library for accessing git", - "homepage": "http://gitonomy.com", - "time": "2018-04-22T19:55:36+00:00" + "time": "2021-07-28T10:34:58+00:00" }, { - "name": "goaop/framework", - "version": "2.2.0", + "name": "webmozart/assert", + "version": "1.10.0", "source": { "type": "git", - "url": "https://github.com/goaop/framework.git", - "reference": "152abbffffcba72d2d159b892deb40b0829d0f28" + "url": "https://github.com/webmozarts/assert.git", + "reference": "6964c76c7804814a842473e0c8fd15bab0f18e25" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/goaop/framework/zipball/152abbffffcba72d2d159b892deb40b0829d0f28", - "reference": "152abbffffcba72d2d159b892deb40b0829d0f28", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/6964c76c7804814a842473e0c8fd15bab0f18e25", + "reference": "6964c76c7804814a842473e0c8fd15bab0f18e25", "shasum": "" }, "require": { - "doctrine/annotations": "^1.2.3", - "doctrine/cache": "^1.5", - "goaop/parser-reflection": "~1.4", - "jakubledl/dissect": "~1.0", - "php": ">=5.6.0" + "php": "^7.2 || ^8.0", + "symfony/polyfill-ctype": "^1.8" }, - "require-dev": { - "adlawson/vfs": "^0.12", - "doctrine/orm": "^2.5", - "phpunit/phpunit": "^5.7", - "symfony/console": "^2.7|^3.0", - "symfony/filesystem": "^3.3", - "symfony/process": "^3.3" + "conflict": { + "phpstan/phpstan": "<0.12.20", + "vimeo/psalm": "<4.6.1 || 4.6.2" }, - "suggest": { - "symfony/console": "Enables the usage of the command-line tool." + "require-dev": { + "phpunit/phpunit": "^8.5.13" }, - "bin": [ - "bin/aspect" - ], "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-master": "1.10-dev" } }, "autoload": { "psr-4": { - "Go\\": "src/" + "Webmozart\\Assert\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -5656,56 +7062,44 @@ ], "authors": [ { - "name": "Lisachenko Alexander", - "homepage": "https://github.com/lisachenko" + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" } ], - "description": "Framework for aspect-oriented programming in PHP.", - "homepage": "http://go.aopphp.com/", + "description": "Assertions to validate method input/output with nice error messages.", "keywords": [ - "aop", - "aspect", - "library", - "php" + "assert", + "check", + "validate" ], - "time": "2018-01-05T23:07:51+00:00" + "support": { + "issues": "https://github.com/webmozarts/assert/issues", + "source": "https://github.com/webmozarts/assert/tree/1.10.0" + }, + "time": "2021-03-09T10:59:23+00:00" }, { - "name": "goaop/parser-reflection", - "version": "1.4.1", + "name": "weew/helpers-array", + "version": "v1.3.1", "source": { "type": "git", - "url": "https://github.com/goaop/parser-reflection.git", - "reference": "d9c1dcc7ce4a5284fe3530e011faf9c9c10e1166" + "url": "https://github.com/weew/helpers-array.git", + "reference": "9bff63111f9765b4277750db8d276d92b3e16ed0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/goaop/parser-reflection/zipball/d9c1dcc7ce4a5284fe3530e011faf9c9c10e1166", - "reference": "d9c1dcc7ce4a5284fe3530e011faf9c9c10e1166", + "url": "https://api.github.com/repos/weew/helpers-array/zipball/9bff63111f9765b4277750db8d276d92b3e16ed0", + "reference": "9bff63111f9765b4277750db8d276d92b3e16ed0", "shasum": "" }, - "require": { - "nikic/php-parser": "^1.2|^2.0|^3.0", - "php": ">=5.6.0" - }, "require-dev": { - "phpunit/phpunit": "~4.0" + "phpunit/phpunit": "^4.7", + "satooshi/php-coveralls": "^0.6.1" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.x-dev" - } - }, "autoload": { - "psr-4": { - "Go\\ParserReflection\\": "src" - }, "files": [ - "src/bootstrap.php" - ], - "exclude-from-classmap": [ - "/tests/" + "src/array.php" ] }, "notification-url": "https://packagist.org/downloads/", @@ -5714,75 +7108,68 @@ ], "authors": [ { - "name": "Alexander Lisachenko", - "email": "lisachenko.it@gmail.com" + "name": "Maxim Kott", + "email": "maximkott@gmail.com" } ], - "description": "Provides reflection information, based on raw source", - "time": "2018-03-19T15:57:41+00:00" - }, + "description": "Useful collection of php array helpers.", + "support": { + "issues": "https://github.com/weew/helpers-array/issues", + "source": "https://github.com/weew/helpers-array/tree/master" + }, + "time": "2016-07-21T11:18:01+00:00" + } + ], + "packages-dev": [ { - "name": "guzzle/guzzle", - "version": "v3.8.1", + "name": "brainmaestro/composer-git-hooks", + "version": "v2.8.5", "source": { "type": "git", - "url": "https://github.com/guzzle/guzzle.git", - "reference": "4de0618a01b34aa1c8c33a3f13f396dcd3882eba" + "url": "https://github.com/BrainMaestro/composer-git-hooks.git", + "reference": "ffed8803690ac12214082120eee3441b00aa390e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/4de0618a01b34aa1c8c33a3f13f396dcd3882eba", - "reference": "4de0618a01b34aa1c8c33a3f13f396dcd3882eba", + "url": "https://api.github.com/repos/BrainMaestro/composer-git-hooks/zipball/ffed8803690ac12214082120eee3441b00aa390e", + "reference": "ffed8803690ac12214082120eee3441b00aa390e", "shasum": "" }, "require": { - "ext-curl": "*", - "php": ">=5.3.3", - "symfony/event-dispatcher": ">=2.1" - }, - "replace": { - "guzzle/batch": "self.version", - "guzzle/cache": "self.version", - "guzzle/common": "self.version", - "guzzle/http": "self.version", - "guzzle/inflection": "self.version", - "guzzle/iterator": "self.version", - "guzzle/log": "self.version", - "guzzle/parser": "self.version", - "guzzle/plugin": "self.version", - "guzzle/plugin-async": "self.version", - "guzzle/plugin-backoff": "self.version", - "guzzle/plugin-cache": "self.version", - "guzzle/plugin-cookie": "self.version", - "guzzle/plugin-curlauth": "self.version", - "guzzle/plugin-error-response": "self.version", - "guzzle/plugin-history": "self.version", - "guzzle/plugin-log": "self.version", - "guzzle/plugin-md5": "self.version", - "guzzle/plugin-mock": "self.version", - "guzzle/plugin-oauth": "self.version", - "guzzle/service": "self.version", - "guzzle/stream": "self.version" + "php": "^5.6 || >=7.0", + "symfony/console": "^3.2 || ^4.0 || ^5.0" }, "require-dev": { - "doctrine/cache": "*", - "monolog/monolog": "1.*", - "phpunit/phpunit": "3.7.*", - "psr/log": "1.0.*", - "symfony/class-loader": "*", - "zendframework/zend-cache": "<2.3", - "zendframework/zend-log": "<2.3" + "ext-json": "*", + "friendsofphp/php-cs-fixer": "^2.9", + "phpunit/phpunit": "^5.7 || ^7.0" }, + "bin": [ + "cghooks" + ], "type": "library", "extra": { - "branch-alias": { - "dev-master": "3.8-dev" + "hooks": { + "pre-commit": "composer check-style", + "pre-push": [ + "composer test", + "appver=$(grep -o -E '\\d.\\d.\\d' cghooks)", + "tag=$(git describe --tags --abbrev=0)", + "if [ \"$tag\" != \"v$appver\" ]; then", + "echo \"The most recent tag $tag does not match the application version $appver\\n\"", + "tag=${tag#v}", + "sed -i -E \"s/$appver/$tag/\" cghooks", + "exit 1", + "fi" + ] } }, "autoload": { - "psr-0": { - "Guzzle": "src/", - "Guzzle\\Tests": "tests/" + "files": [ + "src/helpers.php" + ], + "psr-4": { + "BrainMaestro\\GitHooks\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -5791,163 +7178,178 @@ ], "authors": [ { - "name": "Michael Dowling", - "email": "mtdowling@gmail.com", - "homepage": "https://github.com/mtdowling" - }, - { - "name": "Guzzle Community", - "homepage": "https://github.com/guzzle/guzzle/contributors" + "name": "Ezinwa Okpoechi", + "email": "brainmaestro@outlook.com" } ], - "description": "Guzzle is a PHP HTTP client library and framework for building RESTful web service clients", - "homepage": "http://guzzlephp.org/", + "description": "Easily manage git hooks in your composer config", "keywords": [ - "client", - "curl", - "framework", - "http", - "http client", - "rest", - "web service" + "HOOK", + "composer", + "git" ], - "abandoned": "guzzlehttp/guzzle", - "time": "2014-01-28T22:29:15+00:00" + "support": { + "issues": "https://github.com/BrainMaestro/composer-git-hooks/issues", + "source": "https://github.com/BrainMaestro/composer-git-hooks/tree/v2.8.5" + }, + "time": "2021-02-08T15:59:11+00:00" }, { - "name": "jakubledl/dissect", - "version": "v1.0.1", + "name": "codacy/coverage", + "version": "1.4.3", "source": { "type": "git", - "url": "https://github.com/jakubledl/dissect.git", - "reference": "d3a391de31e45a247e95cef6cf58a91c05af67c4" + "url": "https://github.com/codacy/php-codacy-coverage.git", + "reference": "1852ca987c91ef466ebcfdbdd4e1788b653eaf1d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/jakubledl/dissect/zipball/d3a391de31e45a247e95cef6cf58a91c05af67c4", - "reference": "d3a391de31e45a247e95cef6cf58a91c05af67c4", + "url": "https://api.github.com/repos/codacy/php-codacy-coverage/zipball/1852ca987c91ef466ebcfdbdd4e1788b653eaf1d", + "reference": "1852ca987c91ef466ebcfdbdd4e1788b653eaf1d", "shasum": "" }, "require": { - "php": ">=5.3.3" + "gitonomy/gitlib": ">=1.0", + "php": ">=5.3.3", + "symfony/console": "~2.5|~3.0|~4.0|~5.0" }, "require-dev": { - "symfony/console": "~2.1" - }, - "suggest": { - "symfony/console": "for the command-line tool" + "clue/phar-composer": "^1.1", + "phpunit/phpunit": "~6.5" }, "bin": [ - "bin/dissect.php", - "bin/dissect" + "bin/codacycoverage" ], "type": "library", "autoload": { - "psr-0": { - "Dissect": [ - "src/" - ] - } + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "unlicense" + "MIT" ], "authors": [ { - "name": "Jakub Lédl", - "email": "jakubledl@gmail.com" + "name": "Jakob Pupke", + "email": "jakob.pupke@gmail.com" } ], - "description": "Lexing and parsing in pure PHP", - "homepage": "https://github.com/jakubledl/dissect", - "keywords": [ - "ast", - "lexing", - "parser", - "parsing" - ], - "time": "2013-01-29T21:29:14+00:00" + "description": "Sends PHP test coverage information to Codacy.", + "homepage": "https://github.com/codacy/php-codacy-coverage", + "support": { + "issues": "https://github.com/codacy/php-codacy-coverage/issues", + "source": "https://github.com/codacy/php-codacy-coverage/tree/master" + }, + "abandoned": true, + "time": "2020-01-10T10:52:12+00:00" }, { - "name": "nikic/php-parser", - "version": "v3.1.5", + "name": "gitonomy/gitlib", + "version": "v1.3.2", "source": { "type": "git", - "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "bb87e28e7d7b8d9a7fda231d37457c9210faf6ce" + "url": "https://github.com/gitonomy/gitlib.git", + "reference": "e73e439590b194b0b250b516b22a68c7116e2f21" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/bb87e28e7d7b8d9a7fda231d37457c9210faf6ce", - "reference": "bb87e28e7d7b8d9a7fda231d37457c9210faf6ce", + "url": "https://api.github.com/repos/gitonomy/gitlib/zipball/e73e439590b194b0b250b516b22a68c7116e2f21", + "reference": "e73e439590b194b0b250b516b22a68c7116e2f21", "shasum": "" }, "require": { - "ext-tokenizer": "*", - "php": ">=5.5" + "ext-pcre": "*", + "php": "^5.6 || ^7.0 || ^8.0", + "symfony/polyfill-mbstring": "^1.7", + "symfony/process": "^3.4 || ^4.4 || ^5.0 || ^6.0" }, "require-dev": { - "phpunit/phpunit": "~4.0|~5.0" + "ext-fileinfo": "*", + "phpunit/phpunit": "^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.20 || ^9.5.9", + "psr/log": "^1.0" }, - "bin": [ - "bin/php-parse" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.0-dev" - } + "suggest": { + "ext-fileinfo": "Required to determine the mimetype of a blob", + "psr/log": "Required to use loggers for reporting of execution" }, + "type": "library", "autoload": { "psr-4": { - "PhpParser\\": "lib/PhpParser" + "Gitonomy\\Git\\": "src/Gitonomy/Git/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Nikita Popov" + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk" + }, + { + "name": "Julien Didier", + "email": "genzo.wm@gmail.com" + }, + { + "name": "Grégoire Pineau", + "email": "lyrixx@lyrixx.info" + }, + { + "name": "Alexandre Salomé", + "email": "alexandre.salome@gmail.com" } ], - "description": "A PHP parser written in PHP", - "keywords": [ - "parser", - "php" + "description": "Library for accessing git", + "support": { + "issues": "https://github.com/gitonomy/gitlib/issues", + "source": "https://github.com/gitonomy/gitlib/tree/v1.3.2" + }, + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/gitonomy/gitlib", + "type": "tidelift" + } ], - "time": "2018-02-28T20:30:58+00:00" + "time": "2021-09-06T20:30:10+00:00" }, { "name": "pdepend/pdepend", - "version": "2.5.2", + "version": "2.10.1", "source": { "type": "git", "url": "https://github.com/pdepend/pdepend.git", - "reference": "9daf26d0368d4a12bed1cacae1a9f3a6f0adf239" + "reference": "30452fdabb3dfca89f4bf977abc44adc5391e062" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/pdepend/pdepend/zipball/9daf26d0368d4a12bed1cacae1a9f3a6f0adf239", - "reference": "9daf26d0368d4a12bed1cacae1a9f3a6f0adf239", + "url": "https://api.github.com/repos/pdepend/pdepend/zipball/30452fdabb3dfca89f4bf977abc44adc5391e062", + "reference": "30452fdabb3dfca89f4bf977abc44adc5391e062", "shasum": "" }, "require": { "php": ">=5.3.7", - "symfony/config": "^2.3.0|^3|^4", - "symfony/dependency-injection": "^2.3.0|^3|^4", - "symfony/filesystem": "^2.3.0|^3|^4" + "symfony/config": "^2.3.0|^3|^4|^5", + "symfony/dependency-injection": "^2.3.0|^3|^4|^5", + "symfony/filesystem": "^2.3.0|^3|^4|^5" }, "require-dev": { - "phpunit/phpunit": "^4.8|^5.7", + "easy-doc/easy-doc": "0.0.0|^1.2.3", + "gregwar/rst": "^1.0", + "phpunit/phpunit": "^4.8.36|^5.7.27", "squizlabs/php_codesniffer": "^2.0.0" }, "bin": [ "src/bin/pdepend" ], "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, "autoload": { "psr-4": { "PDepend\\": "src/main/php/PDepend" @@ -5958,46 +7360,57 @@ "BSD-3-Clause" ], "description": "Official version of pdepend to be handled with Composer", - "time": "2017-12-13T13:21:38+00:00" + "support": { + "issues": "https://github.com/pdepend/pdepend/issues", + "source": "https://github.com/pdepend/pdepend/tree/2.10.1" + }, + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/pdepend/pdepend", + "type": "tidelift" + } + ], + "time": "2021-10-11T12:15:18+00:00" }, { "name": "php-coveralls/php-coveralls", - "version": "v1.1.0", + "version": "v2.4.3", "source": { "type": "git", "url": "https://github.com/php-coveralls/php-coveralls.git", - "reference": "37f8f83fe22224eb9d9c6d593cdeb33eedd2a9ad" + "reference": "909381bd40a17ae6e9076051f0d73293c1c091af" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-coveralls/php-coveralls/zipball/37f8f83fe22224eb9d9c6d593cdeb33eedd2a9ad", - "reference": "37f8f83fe22224eb9d9c6d593cdeb33eedd2a9ad", + "url": "https://api.github.com/repos/php-coveralls/php-coveralls/zipball/909381bd40a17ae6e9076051f0d73293c1c091af", + "reference": "909381bd40a17ae6e9076051f0d73293c1c091af", "shasum": "" }, "require": { "ext-json": "*", "ext-simplexml": "*", - "guzzle/guzzle": "^2.8 || ^3.0", - "php": "^5.3.3 || ^7.0", + "guzzlehttp/guzzle": "^6.0 || ^7.0", + "php": "^5.5 || ^7.0 || ^8.0", "psr/log": "^1.0", - "symfony/config": "^2.1 || ^3.0 || ^4.0", - "symfony/console": "^2.1 || ^3.0 || ^4.0", - "symfony/stopwatch": "^2.0 || ^3.0 || ^4.0", - "symfony/yaml": "^2.0 || ^3.0 || ^4.0" + "symfony/config": "^2.1 || ^3.0 || ^4.0 || ^5.0", + "symfony/console": "^2.1 || ^3.0 || ^4.0 || ^5.0", + "symfony/stopwatch": "^2.0 || ^3.0 || ^4.0 || ^5.0", + "symfony/yaml": "^2.0.5 || ^3.0 || ^4.0 || ^5.0" }, "require-dev": { - "phpunit/phpunit": "^4.8.35 || ^5.4.3 || ^6.0" + "phpunit/phpunit": "^4.8.35 || ^5.4.3 || ^6.0 || ^7.0 || ^8.0 || ^9.0", + "sanmai/phpunit-legacy-adapter": "^6.1 || ^8.0" }, "suggest": { "symfony/http-kernel": "Allows Symfony integration" }, "bin": [ - "bin/coveralls" + "bin/php-coveralls" ], "type": "library", "autoload": { "psr-4": { - "Satooshi\\": "src/Satooshi/" + "PhpCoveralls\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -6008,7 +7421,24 @@ { "name": "Kitamura Satoshi", "email": "with.no.parachute@gmail.com", - "homepage": "https://www.facebook.com/satooshi.jp" + "homepage": "https://www.facebook.com/satooshi.jp", + "role": "Original creator" + }, + { + "name": "Takashi Matsuo", + "email": "tmatsuo@google.com" + }, + { + "name": "Google Inc" + }, + { + "name": "Dariusz Ruminski", + "email": "dariusz.ruminski@gmail.com", + "homepage": "https://github.com/keradus" + }, + { + "name": "Contributors", + "homepage": "https://github.com/php-coveralls/php-coveralls/graphs/contributors" } ], "description": "PHP client library for Coveralls API", @@ -6019,35 +7449,45 @@ "github", "test" ], - "time": "2017-12-06T23:17:56+00:00" + "support": { + "issues": "https://github.com/php-coveralls/php-coveralls/issues", + "source": "https://github.com/php-coveralls/php-coveralls/tree/v2.4.3" + }, + "time": "2020-12-24T09:17:03+00:00" }, { "name": "phpmd/phpmd", - "version": "2.6.0", + "version": "2.10.2", "source": { "type": "git", "url": "https://github.com/phpmd/phpmd.git", - "reference": "4e9924b2c157a3eb64395460fcf56b31badc8374" + "reference": "1bc74db7cf834662d83abebae265be11bb2eec3a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpmd/phpmd/zipball/4e9924b2c157a3eb64395460fcf56b31badc8374", - "reference": "4e9924b2c157a3eb64395460fcf56b31badc8374", + "url": "https://api.github.com/repos/phpmd/phpmd/zipball/1bc74db7cf834662d83abebae265be11bb2eec3a", + "reference": "1bc74db7cf834662d83abebae265be11bb2eec3a", "shasum": "" }, "require": { + "composer/xdebug-handler": "^1.0 || ^2.0", "ext-xml": "*", - "pdepend/pdepend": "^2.5", + "pdepend/pdepend": "^2.10.0", "php": ">=5.3.9" }, "require-dev": { - "phpunit/phpunit": "^4.0", + "easy-doc/easy-doc": "0.0.0 || ^1.3.2", + "ext-json": "*", + "ext-simplexml": "*", + "gregwar/rst": "^1.0", + "mikey179/vfsstream": "^1.6.8", + "phpunit/phpunit": "^4.8.36 || ^5.7.27", "squizlabs/php_codesniffer": "^2.0" }, "bin": [ "src/bin/phpmd" ], - "type": "project", + "type": "library", "autoload": { "psr-0": { "PHPMD\\": "src/main/php" @@ -6064,20 +7504,20 @@ "homepage": "https://github.com/manuelpichler", "role": "Project Founder" }, - { - "name": "Other contributors", - "homepage": "https://github.com/phpmd/phpmd/graphs/contributors", - "role": "Contributors" - }, { "name": "Marc Würth", "email": "ravage@bluewin.ch", "homepage": "https://github.com/ravage84", "role": "Project Maintainer" + }, + { + "name": "Other contributors", + "homepage": "https://github.com/phpmd/phpmd/graphs/contributors", + "role": "Contributors" } ], "description": "PHPMD is a spin-off project of PHP Depend and aims to be a PHP equivalent of the well known Java tool PMD.", - "homepage": "http://phpmd.org/", + "homepage": "https://phpmd.org/", "keywords": [ "mess detection", "mess detector", @@ -6085,109 +7525,40 @@ "phpmd", "pmd" ], - "time": "2017-01-20T14:41:10+00:00" - }, - { - "name": "rregeer/phpunit-coverage-check", - "version": "0.1.6", - "source": { - "type": "git", - "url": "https://github.com/richardregeer/phpunit-coverage-check.git", - "reference": "330ae9318e0e60960cfb97c4a0459e3e4a3080e0" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/richardregeer/phpunit-coverage-check/zipball/330ae9318e0e60960cfb97c4a0459e3e4a3080e0", - "reference": "330ae9318e0e60960cfb97c4a0459e3e4a3080e0", - "shasum": "" - }, - "require": { - "php": ">=5.5.0" - }, - "bin": [ - "bin/coverage-check" - ], - "type": "library", - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Richard Regeer", - "email": "rich2309@gmail.com" - } - ], - "description": "Check the code coverage using the clover report of phpunit", - "keywords": [ - "ci", - "code coverage", - "php", - "phpunit", - "testing", - "unittest" - ], - "time": "2018-07-29T13:27:58+00:00" - }, - { - "name": "sebastian/finder-facade", - "version": "1.2.2", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/finder-facade.git", - "reference": "4a3174709c2dc565fe5fb26fcf827f6a1fc7b09f" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/finder-facade/zipball/4a3174709c2dc565fe5fb26fcf827f6a1fc7b09f", - "reference": "4a3174709c2dc565fe5fb26fcf827f6a1fc7b09f", - "shasum": "" - }, - "require": { - "symfony/finder": "~2.3|~3.0|~4.0", - "theseer/fdomdocument": "~1.3" - }, - "type": "library", - "autoload": { - "classmap": [ - "src/" - ] + "support": { + "irc": "irc://irc.freenode.org/phpmd", + "issues": "https://github.com/phpmd/phpmd/issues", + "source": "https://github.com/phpmd/phpmd/tree/2.10.2" }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ + "funding": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "url": "https://tidelift.com/funding/github/packagist/phpmd/phpmd", + "type": "tidelift" } ], - "description": "FinderFacade is a convenience wrapper for Symfony's Finder component.", - "homepage": "https://github.com/sebastianbergmann/finder-facade", - "time": "2017-11-18T17:31:49+00:00" + "time": "2021-07-22T09:56:23+00:00" }, { "name": "sebastian/phpcpd", - "version": "3.0.1", + "version": "6.0.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpcpd.git", - "reference": "dfed51c1288790fc957c9433e2f49ab152e8a564" + "reference": "f3683aa0db2e8e09287c2bb33a595b2873ea9176" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpcpd/zipball/dfed51c1288790fc957c9433e2f49ab152e8a564", - "reference": "dfed51c1288790fc957c9433e2f49ab152e8a564", + "url": "https://api.github.com/repos/sebastianbergmann/phpcpd/zipball/f3683aa0db2e8e09287c2bb33a595b2873ea9176", + "reference": "f3683aa0db2e8e09287c2bb33a595b2873ea9176", "shasum": "" }, "require": { - "php": "^5.6|^7.0", - "phpunit/php-timer": "^1.0.6", - "sebastian/finder-facade": "^1.1", - "sebastian/version": "^1.0|^2.0", - "symfony/console": "^2.7|^3.0|^4.0" + "ext-dom": "*", + "php": ">=7.3", + "phpunit/php-file-iterator": "^3.0", + "phpunit/php-timer": "^5.0", + "sebastian/cli-parser": "^1.0", + "sebastian/version": "^3.0" }, "bin": [ "phpcpd" @@ -6195,7 +7566,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0-dev" + "dev-master": "6.0-dev" } }, "autoload": { @@ -6216,20 +7587,30 @@ ], "description": "Copy/Paste Detector (CPD) for PHP code.", "homepage": "https://github.com/sebastianbergmann/phpcpd", - "time": "2017-11-16T08:49:28+00:00" + "support": { + "issues": "https://github.com/sebastianbergmann/phpcpd/issues", + "source": "https://github.com/sebastianbergmann/phpcpd/tree/6.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-12-07T05:39:23+00:00" }, { "name": "squizlabs/php_codesniffer", - "version": "3.3.2", + "version": "3.6.1", "source": { "type": "git", "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", - "reference": "6ad28354c04b364c3c71a34e4a18b629cc3b231e" + "reference": "f268ca40d54617c6e06757f83f699775c9b3ff2e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/6ad28354c04b364c3c71a34e4a18b629cc3b231e", - "reference": "6ad28354c04b364c3c71a34e4a18b629cc3b231e", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/f268ca40d54617c6e06757f83f699775c9b3ff2e", + "reference": "f268ca40d54617c6e06757f83f699775c9b3ff2e", "shasum": "" }, "require": { @@ -6262,51 +7643,54 @@ } ], "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", - "homepage": "http://www.squizlabs.com/php-codesniffer", + "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", "keywords": [ "phpcs", "standards" ], - "time": "2018-09-23T23:08:17+00:00" + "support": { + "issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues", + "source": "https://github.com/squizlabs/PHP_CodeSniffer", + "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki" + }, + "time": "2021-10-11T04:00:11+00:00" }, { "name": "symfony/config", - "version": "v3.4.18", + "version": "v5.3.4", "source": { "type": "git", "url": "https://github.com/symfony/config.git", - "reference": "99b2fa8acc244e656cdf324ff419fbe6fd300a4d" + "reference": "4268f3059c904c61636275182707f81645517a37" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/config/zipball/99b2fa8acc244e656cdf324ff419fbe6fd300a4d", - "reference": "99b2fa8acc244e656cdf324ff419fbe6fd300a4d", + "url": "https://api.github.com/repos/symfony/config/zipball/4268f3059c904c61636275182707f81645517a37", + "reference": "4268f3059c904c61636275182707f81645517a37", "shasum": "" }, "require": { - "php": "^5.5.9|>=7.0.8", - "symfony/filesystem": "~2.8|~3.0|~4.0", - "symfony/polyfill-ctype": "~1.8" + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1", + "symfony/filesystem": "^4.4|^5.0", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-php80": "^1.16", + "symfony/polyfill-php81": "^1.22" }, "conflict": { - "symfony/dependency-injection": "<3.3", - "symfony/finder": "<3.3" + "symfony/finder": "<4.4" }, "require-dev": { - "symfony/dependency-injection": "~3.3|~4.0", - "symfony/event-dispatcher": "~3.3|~4.0", - "symfony/finder": "~3.3|~4.0", - "symfony/yaml": "~3.0|~4.0" + "symfony/event-dispatcher": "^4.4|^5.0", + "symfony/finder": "^4.4|^5.0", + "symfony/messenger": "^4.4|^5.0", + "symfony/service-contracts": "^1.1|^2", + "symfony/yaml": "^4.4|^5.0" }, "suggest": { "symfony/yaml": "To use the yaml reference dumper" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.4-dev" - } - }, "autoload": { "psr-4": { "Symfony\\Component\\Config\\": "" @@ -6329,41 +7713,63 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony Config Component", + "description": "Helps you find, load, combine, autofill and validate configuration values of any kind", "homepage": "https://symfony.com", - "time": "2018-10-31T09:06:03+00:00" + "support": { + "source": "https://github.com/symfony/config/tree/v5.3.4" + }, + "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": "2021-07-21T12:40:44+00:00" }, { "name": "symfony/dependency-injection", - "version": "v3.4.18", + "version": "v5.3.8", "source": { "type": "git", "url": "https://github.com/symfony/dependency-injection.git", - "reference": "9c98452ac7fff4b538956775630bc9701f5384ba" + "reference": "e39c344e06a3ceab531ebeb6c077e6652c4a0829" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/9c98452ac7fff4b538956775630bc9701f5384ba", - "reference": "9c98452ac7fff4b538956775630bc9701f5384ba", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/e39c344e06a3ceab531ebeb6c077e6652c4a0829", + "reference": "e39c344e06a3ceab531ebeb6c077e6652c4a0829", "shasum": "" }, "require": { - "php": "^5.5.9|>=7.0.8", - "psr/container": "^1.0" + "php": ">=7.2.5", + "psr/container": "^1.1.1", + "symfony/deprecation-contracts": "^2.1", + "symfony/polyfill-php80": "^1.16", + "symfony/service-contracts": "^1.1.6|^2" }, "conflict": { - "symfony/config": "<3.3.7", - "symfony/finder": "<3.3", - "symfony/proxy-manager-bridge": "<3.4", - "symfony/yaml": "<3.4" + "ext-psr": "<1.1|>=2", + "symfony/config": "<5.3", + "symfony/finder": "<4.4", + "symfony/proxy-manager-bridge": "<4.4", + "symfony/yaml": "<4.4" }, "provide": { - "psr/container-implementation": "1.0" + "psr/container-implementation": "1.0", + "symfony/service-implementation": "1.0|2.0" }, "require-dev": { - "symfony/config": "~3.3|~4.0", - "symfony/expression-language": "~2.8|~3.0|~4.0", - "symfony/yaml": "~3.4|~4.0" + "symfony/config": "^5.3", + "symfony/expression-language": "^4.4|^5.0", + "symfony/yaml": "^4.4|^5.0" }, "suggest": { "symfony/config": "", @@ -6373,11 +7779,6 @@ "symfony/yaml": "" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.4-dev" - } - }, "autoload": { "psr-4": { "Symfony\\Component\\DependencyInjection\\": "" @@ -6400,33 +7801,46 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony DependencyInjection Component", + "description": "Allows you to standardize and centralize the way objects are constructed in your application", "homepage": "https://symfony.com", - "time": "2018-10-31T10:49:51+00:00" + "support": { + "source": "https://github.com/symfony/dependency-injection/tree/v5.3.8" + }, + "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": "2021-09-21T20:52:44+00:00" }, { "name": "symfony/stopwatch", - "version": "v3.4.18", + "version": "v5.3.4", "source": { "type": "git", "url": "https://github.com/symfony/stopwatch.git", - "reference": "05e52a39de52ba690aebaed462b2bc8a9649f0a4" + "reference": "b24c6a92c6db316fee69e38c80591e080e41536c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/stopwatch/zipball/05e52a39de52ba690aebaed462b2bc8a9649f0a4", - "reference": "05e52a39de52ba690aebaed462b2bc8a9649f0a4", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/b24c6a92c6db316fee69e38c80591e080e41536c", + "reference": "b24c6a92c6db316fee69e38c80591e080e41536c", "shasum": "" }, "require": { - "php": "^5.5.9|>=7.0.8" + "php": ">=7.2.5", + "symfony/service-contracts": "^1.0|^2" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.4-dev" - } - }, "autoload": { "psr-4": { "Symfony\\Component\\Stopwatch\\": "" @@ -6449,49 +7863,26 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony Stopwatch Component", + "description": "Provides a way to profile code", "homepage": "https://symfony.com", - "time": "2018-10-02T12:28:39+00:00" - }, - { - "name": "theseer/fdomdocument", - "version": "1.6.6", - "source": { - "type": "git", - "url": "https://github.com/theseer/fDOMDocument.git", - "reference": "6e8203e40a32a9c770bcb62fe37e68b948da6dca" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/theseer/fDOMDocument/zipball/6e8203e40a32a9c770bcb62fe37e68b948da6dca", - "reference": "6e8203e40a32a9c770bcb62fe37e68b948da6dca", - "shasum": "" - }, - "require": { - "ext-dom": "*", - "lib-libxml": "*", - "php": ">=5.3.3" - }, - "type": "library", - "autoload": { - "classmap": [ - "src/" - ] + "support": { + "source": "https://github.com/symfony/stopwatch/tree/v5.3.4" }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ + "funding": [ { - "name": "Arne Blankerts", - "email": "arne@blankerts.de", - "role": "lead" + "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" } ], - "description": "The classes contained within this repository extend the standard DOM to use exceptions at all occasions of errors instead of PHP warnings or notices. They also add various custom methods and shortcuts for convenience and to simplify the usage of DOM.", - "homepage": "https://github.com/theseer/fDOMDocument", - "time": "2017-06-30T11:53:12+00:00" + "time": "2021-07-10T08:58:57+00:00" } ], "aliases": [], @@ -6500,8 +7891,14 @@ "prefer-stable": false, "prefer-lowest": false, "platform": { - "php": "7.0.2||7.0.4||~7.0.6||~7.1.0||~7.2.0||~7.3.0", - "ext-curl": "*" + "php": ">7.3", + "ext-curl": "*", + "ext-dom": "*", + "ext-iconv": "*", + "ext-intl": "*", + "ext-json": "*", + "ext-openssl": "*" }, - "platform-dev": [] + "platform-dev": [], + "plugin-api-version": "2.3.0" } diff --git a/dev/tests/_bootstrap.php b/dev/tests/_bootstrap.php index b41f80394..3b31c6e4a 100644 --- a/dev/tests/_bootstrap.php +++ b/dev/tests/_bootstrap.php @@ -9,33 +9,18 @@ $vendorAutoloadPath = realpath(PROJECT_ROOT . '/vendor/autoload.php'); $mftfTestCasePath = realpath(PROJECT_ROOT . '/dev/tests/util/MftfTestCase.php'); +$mftfStaticTestCasePath = realpath(PROJECT_ROOT . '/dev/tests/util/MftfStaticTestCase.php'); require_once $vendorAutoloadPath; require_once $mftfTestCasePath; - -// Set up AspectMock -$kernel = \AspectMock\Kernel::getInstance(); -$kernel->init([ - 'debug' => true, - 'includePaths' => [ - PROJECT_ROOT . DIRECTORY_SEPARATOR . 'src', - PROJECT_ROOT . DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR . 'allure-framework' - ], - 'cacheDir' => PROJECT_ROOT . - DIRECTORY_SEPARATOR . - 'dev' . - DIRECTORY_SEPARATOR . - 'tests' . - DIRECTORY_SEPARATOR . - '.cache' -]); +require_once $mftfStaticTestCasePath; // set mftf appplication context \Magento\FunctionalTestingFramework\Config\MftfApplicationConfig::create( true, \Magento\FunctionalTestingFramework\Config\MftfApplicationConfig::UNIT_TEST_PHASE, true, - \Magento\FunctionalTestingFramework\Config\MftfApplicationConfig::LEVEL_NONE, + \Magento\FunctionalTestingFramework\Config\MftfApplicationConfig::LEVEL_DEFAULT, false ); @@ -45,7 +30,8 @@ 'MAGENTO_BACKEND_NAME' => 'admin', 'MAGENTO_ADMIN_USERNAME' => 'admin', 'MAGENTO_ADMIN_PASSWORD' => 'admin123', - 'DEFAULT_TIMEZONE' => 'America/Los_Angeles' + 'DEFAULT_TIMEZONE' => 'America/Los_Angeles', + 'WAIT_TIMEOUT' => '10' ]; foreach ($TEST_ENVS as $key => $value) { @@ -53,8 +39,8 @@ putenv("{$key}=${value}"); } -// Add our test module to the whitelist -putenv('MODULE_WHITELIST=Magento_TestModule'); +// Add our test module to the allowlist +putenv('MODULE_ALLOWLIST=Magento_TestModule'); // Define our own set of paths for the tests defined('FW_BP') || define('FW_BP', PROJECT_ROOT); @@ -101,28 +87,6 @@ require($unitUtilFile); } - -// Mocks suite files location getter return to get files in verification/_suite Directory -// This mocks the paths of the suite files but still parses the xml files -$suiteDirectory = TESTS_BP . DIRECTORY_SEPARATOR . "verification" . DIRECTORY_SEPARATOR . "_suite"; - -$paths = [ - $suiteDirectory . DIRECTORY_SEPARATOR . 'functionalSuite.xml', - $suiteDirectory . DIRECTORY_SEPARATOR . 'functionalSuiteHooks.xml', - $suiteDirectory . DIRECTORY_SEPARATOR . 'functionalSuiteExtends.xml' -]; - -// create and return the iterator for these file paths -$iterator = new Magento\FunctionalTestingFramework\Util\Iterator\File($paths); -try { - AspectMock\Test::double( - Magento\FunctionalTestingFramework\Config\FileResolver\Root::class, - ['get' => $iterator] - )->make(); -} catch (Exception $e) { - echo "Suite directory not mocked."; -} - function sortInterfaces($files) { $bottom = []; diff --git a/dev/tests/functional/standalone_bootstrap.php b/dev/tests/functional/standalone_bootstrap.php index 763062d04..e048498c3 100755 --- a/dev/tests/functional/standalone_bootstrap.php +++ b/dev/tests/functional/standalone_bootstrap.php @@ -15,11 +15,17 @@ require_once realpath(PROJECT_ROOT . '/vendor/autoload.php'); +$envFilePath = dirname(dirname(__DIR__)) . DIRECTORY_SEPARATOR; +defined('ENV_FILE_PATH') || define('ENV_FILE_PATH', $envFilePath); + //Load constants from .env file -$envFilePath = dirname(dirname(__DIR__)); -if (file_exists($envFilePath . DIRECTORY_SEPARATOR . '.env')) { - $env = new \Dotenv\Loader($envFilePath . DIRECTORY_SEPARATOR . '.env'); - $env->load(); +if (file_exists(ENV_FILE_PATH . '.env')) { + $env = new \Symfony\Component\Dotenv\Dotenv(); + if (function_exists('putenv')) { + $env->usePutenv(); + } + $env->populate($env->parse(file_get_contents(ENV_FILE_PATH . '.env'), ENV_FILE_PATH . '.env'), true); + foreach ($_ENV as $key => $var) { defined($key) || define($key, $var); @@ -40,14 +46,21 @@ 'MAGENTO_CLI_COMMAND_PATH', 'dev/tests/acceptance/utils/command.php' ); - $env->setEnvironmentVariable('MAGENTO_CLI_COMMAND_PATH', MAGENTO_CLI_COMMAND_PATH); - defined('MAGENTO_CLI_COMMAND_PARAMETER') || define('MAGENTO_CLI_COMMAND_PARAMETER', 'command'); - $env->setEnvironmentVariable('MAGENTO_CLI_COMMAND_PARAMETER', MAGENTO_CLI_COMMAND_PARAMETER); - defined('DEFAULT_TIMEZONE') || define('DEFAULT_TIMEZONE', 'America/Los_Angeles'); - $env->setEnvironmentVariable('DEFAULT_TIMEZONE', DEFAULT_TIMEZONE); - + defined('WAIT_TIMEOUT') || define('WAIT_TIMEOUT', 30); + defined('VERBOSE_ARTIFACTS') || define('VERBOSE_ARTIFACTS', false); + $env->populate( + [ + 'MAGENTO_CLI_COMMAND_PATH' => MAGENTO_CLI_COMMAND_PATH, + 'MAGENTO_CLI_COMMAND_PARAMETER' => MAGENTO_CLI_COMMAND_PARAMETER, + 'DEFAULT_TIMEZONE' => DEFAULT_TIMEZONE, + 'WAIT_TIMEOUT' => WAIT_TIMEOUT, + 'VERBOSE_ARTIFACTS' => VERBOSE_ARTIFACTS, + ], + true + ); + try { new DateTimeZone(DEFAULT_TIMEZONE); } catch (\Exception $e) { @@ -62,10 +75,3 @@ $RELATIVE_TESTS_MODULE_PATH = '/tests/functional/tests/MFTF'; defined('TESTS_MODULE_PATH') || define('TESTS_MODULE_PATH', realpath(TESTS_BP . $RELATIVE_TESTS_MODULE_PATH)); - - -// add the debug flag here -$debug_mode = $_ENV['MFTF_DEBUG'] ?? false; -if (!(bool)$debug_mode && extension_loaded('xdebug')) { - xdebug_disable(); -} diff --git a/dev/tests/functional/tests/MFTF/DevDocs/ActionGroup/DeprecatedCommentActionGroup.xml b/dev/tests/functional/tests/MFTF/DevDocs/ActionGroup/DeprecatedCommentActionGroup.xml new file mode 100644 index 000000000..6c27627d5 --- /dev/null +++ b/dev/tests/functional/tests/MFTF/DevDocs/ActionGroup/DeprecatedCommentActionGroup.xml @@ -0,0 +1,13 @@ + + + + + + + diff --git a/dev/tests/functional/tests/MFTF/DevDocs/ActionGroup/HelperActionGroup.xml b/dev/tests/functional/tests/MFTF/DevDocs/ActionGroup/HelperActionGroup.xml new file mode 100644 index 000000000..42159fc66 --- /dev/null +++ b/dev/tests/functional/tests/MFTF/DevDocs/ActionGroup/HelperActionGroup.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + {{contentSection.parametrizedSelector(entityTest.entityField)}} + ['{{entityTest.entityField}}', 'Bla'] + {{test}} + true + 4.400000000234234 + 42 + + + diff --git a/dev/tests/functional/tests/MFTF/DevDocs/Data/DeprecatedMessageData.xml b/dev/tests/functional/tests/MFTF/DevDocs/Data/DeprecatedMessageData.xml new file mode 100644 index 000000000..ba1460a60 --- /dev/null +++ b/dev/tests/functional/tests/MFTF/DevDocs/Data/DeprecatedMessageData.xml @@ -0,0 +1,17 @@ + + + + + + Introduction to the Magento Functional Testing Framework + + + Some data + + diff --git a/dev/tests/functional/tests/MFTF/DevDocs/Data/ExtendMessageData.xml b/dev/tests/functional/tests/MFTF/DevDocs/Data/ExtendMessageData.xml new file mode 100644 index 000000000..040b80597 --- /dev/null +++ b/dev/tests/functional/tests/MFTF/DevDocs/Data/ExtendMessageData.xml @@ -0,0 +1,16 @@ + + + + + + + Something New + + + diff --git a/dev/tests/functional/tests/MFTF/DevDocs/Data/HelperData.xml b/dev/tests/functional/tests/MFTF/DevDocs/Data/HelperData.xml new file mode 100644 index 000000000..bc84a5fb2 --- /dev/null +++ b/dev/tests/functional/tests/MFTF/DevDocs/Data/HelperData.xml @@ -0,0 +1,14 @@ + + + + + + Some kind of data for testing purposes + + diff --git a/dev/tests/functional/tests/MFTF/DevDocs/Data/MessageData.xml b/dev/tests/functional/tests/MFTF/DevDocs/Data/MessageData.xml new file mode 100644 index 000000000..f35812e54 --- /dev/null +++ b/dev/tests/functional/tests/MFTF/DevDocs/Data/MessageData.xml @@ -0,0 +1,26 @@ + + + + + + Introduction to the Magento Functional Testing Framework + + 0 + 1 + 2 + 3 + + + + + + TESTING CASE + + + diff --git a/dev/tests/functional/tests/MFTF/DevDocs/Helper/CustomHelper.php b/dev/tests/functional/tests/MFTF/DevDocs/Helper/CustomHelper.php new file mode 100644 index 000000000..3705ec1d0 --- /dev/null +++ b/dev/tests/functional/tests/MFTF/DevDocs/Helper/CustomHelper.php @@ -0,0 +1,60 @@ + 'value', 'test'] + ) { + print('Hello, this is custom helper which provides an ability to write custom solutions.' . PHP_EOL); + print('string $url = ' . $url . PHP_EOL); + print('$test = ' . $test . PHP_EOL); + print('$bool = ' . $bool . PHP_EOL); + print('$int = ' . $int . PHP_EOL); + print('$float = ' . $float . PHP_EOL); + print('array $module = [' . implode(', ', $module) . ']' . PHP_EOL); + print('$superBla = ' . $superBla . PHP_EOL); + print('$bla = ' . $bla . PHP_EOL); + print('array $arraysomething = [' . implode(', ', $arraysomething) . ']' . PHP_EOL); + } + + /** + * Returns value of provided param $text + * + * @param string $text + * @return string + */ + public function getText(string $text): string + { + return $text; + } +} diff --git a/dev/tests/functional/tests/MFTF/DevDocs/Page/DeprecatedMFTFDocPage.xml b/dev/tests/functional/tests/MFTF/DevDocs/Page/DeprecatedMFTFDocPage.xml new file mode 100644 index 000000000..8be64e9ca --- /dev/null +++ b/dev/tests/functional/tests/MFTF/DevDocs/Page/DeprecatedMFTFDocPage.xml @@ -0,0 +1,14 @@ + + + + + +
+ + diff --git a/dev/tests/functional/tests/MFTF/DevDocs/Page/MFTFDocPage.xml b/dev/tests/functional/tests/MFTF/DevDocs/Page/MFTFDocPage.xml index 88c577aef..9783fb0b3 100644 --- a/dev/tests/functional/tests/MFTF/DevDocs/Page/MFTFDocPage.xml +++ b/dev/tests/functional/tests/MFTF/DevDocs/Page/MFTFDocPage.xml @@ -7,7 +7,7 @@ --> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd">
diff --git a/dev/tests/functional/tests/MFTF/DevDocs/Section/ContentSection.xml b/dev/tests/functional/tests/MFTF/DevDocs/Section/ContentSection.xml index cf82b69e9..a365c9ff5 100644 --- a/dev/tests/functional/tests/MFTF/DevDocs/Section/ContentSection.xml +++ b/dev/tests/functional/tests/MFTF/DevDocs/Section/ContentSection.xml @@ -6,9 +6,13 @@ */ --> + + + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd">
+ +
diff --git a/dev/tests/functional/tests/MFTF/DevDocs/Section/DeprecatedContentSection.xml b/dev/tests/functional/tests/MFTF/DevDocs/Section/DeprecatedContentSection.xml new file mode 100644 index 000000000..8ed018146 --- /dev/null +++ b/dev/tests/functional/tests/MFTF/DevDocs/Section/DeprecatedContentSection.xml @@ -0,0 +1,14 @@ + + + + +
+ +
+
diff --git a/dev/tests/functional/tests/MFTF/DevDocs/Test/DeprecatedDevDocsTest.xml b/dev/tests/functional/tests/MFTF/DevDocs/Test/DeprecatedDevDocsTest.xml new file mode 100644 index 000000000..6aae104ed --- /dev/null +++ b/dev/tests/functional/tests/MFTF/DevDocs/Test/DeprecatedDevDocsTest.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + <description value="[Deprecated] Magento Functional Testing Framework Documentation is available."/> + <severity value="MINOR"/> + <group value="mftf"/> + </annotations> + + <!-- Open MFTF DevDocs Page --> + <amOnPage stepKey="openMFTFDevDocPage" url="{{DeprecatedMFTFDocPage.url}}" /> + <see stepKey="verifyPageIntroText" selector="{{DeprecatedContentSection.pageIntro}}" userInput="{{DeprecatedMessageData.message}}" /> + <actionGroup ref="DeprecatedCommentActionGroup" stepKey="commentActionGroup"/> + </test> +</tests> diff --git a/dev/tests/functional/tests/MFTF/DevDocs/Test/DevDocsTest.xml b/dev/tests/functional/tests/MFTF/DevDocs/Test/DevDocsTest.xml index 3bdeeb9e8..911bc65eb 100644 --- a/dev/tests/functional/tests/MFTF/DevDocs/Test/DevDocsTest.xml +++ b/dev/tests/functional/tests/MFTF/DevDocs/Test/DevDocsTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="DevDocsTest"> <annotations> <!-- Comment in Annotations for DevDocs Test are not affecting test generation --> @@ -21,6 +21,44 @@ <!-- Open MFTF DevDocs Page --> <amOnPage stepKey="openMFTFDevDocPage" url="{{MFTFDocPage.url}}" /> - <see stepKey="verifyPageIntroText" selector="{{contentSection.pageIntro}}" userInput="Introduction to the Magento Functional Testing Framework" /> + <see stepKey="verifyPageIntroText" selector="{{contentSection.pageIntro}}" userInput="{{MessageData.message}}" /> + <helper class="\MFTF\DevDocs\Helper\CustomHelper" method="goTo" stepKey="customHelper"> + <argument name="test">{{contentSection.pageIntro}}</argument> + <argument name="module">['Test', 'Bla']</argument> + <argument name="url">{{MFTFDocPage.url}}</argument> + <argument name="bool">true</argument> + <argument name="float">1.2</argument> + <argument name="int">123</argument> + </helper> + + <helper class="\MFTF\DevDocs\Helper\CustomHelper" method="goTo" stepKey="customHelperWithArrayParametrized"> + <argument name="test">{{contentSection.pageIntro}}</argument> + <argument name="module">[]</argument> + <argument name="url">{{DeprecatedMFTFDocPage.url}}</argument> + <argument name="superBla">1.2</argument> + <argument name="bla" /> + <argument name="bool">false</argument> + <argument name="float">4.223</argument> + <argument name="int">987</argument> + </helper> + + <helper class="\MFTF\DevDocs\Helper\CustomHelper" method="getText" stepKey="getText"> + <argument name="text">some text</argument> + </helper> + <assertEquals stepKey="assertHelperReturnValue" message="Method getText of CustomHelper should return value which may be used as variable in test"> + <expectedResult type="string">some text</expectedResult> + <actualResult type="variable">getText</actualResult> + </assertEquals> + + <actionGroup ref="HelperActionGroup" stepKey="actionGroupWithCustomHelper"> + <argument name="test" value="{{HelperData.entityField}}" /> + <argument name="entityTest" value="HelperData" /> + </actionGroup> + + <assertEqualsCanonicalizing stepKey="assertMergedArray"> + <actualResult type="array">{{ExtendedMessageData.numbers}}</actualResult> + <expectedResult type="array">["Something New", "0", "1", "2", "3", "TESTING CASE"]</expectedResult> + </assertEqualsCanonicalizing> + <pause stepKey="testingPause" /> </test> </tests> diff --git a/dev/tests/functional/tests/MFTF/DevDocs/Test/FormatCurrencyTest.xml b/dev/tests/functional/tests/MFTF/DevDocs/Test/FormatCurrencyTest.xml new file mode 100644 index 000000000..ab3d36c4e --- /dev/null +++ b/dev/tests/functional/tests/MFTF/DevDocs/Test/FormatCurrencyTest.xml @@ -0,0 +1,52 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="FormatCurrencyTest"> + <comment userInput="formatCurrency uses NumberFormatter::formatCurrency(), see https://www.php.net/manual/en/numberformatter.formatcurrency.php" stepKey="comment"/> + <formatCurrency userInput="1234.56789000" locale="de_DE" currency="EUR" stepKey="eurInDE"/> + <assertEquals stepKey="assertEurInDE"> + <expectedResult type="string">1.234,57 €</expectedResult> + <actualResult type="variable">$eurInDE</actualResult> + </assertEquals> + <formatCurrency userInput="+1234" locale="de_DE" currency="EUR" stepKey="eurInDEPos"/> + <assertEquals stepKey="assertEurInDEPos"> + <expectedResult type="string">1.234,00 €</expectedResult> + <actualResult type="variable">$eurInDEPos</actualResult> + </assertEquals> + <formatCurrency userInput="-1234.56" locale="de_DE" currency="EUR" stepKey="eurInDENeg"/> + <assertEquals stepKey="assertEurInDENeg"> + <expectedResult type="string">-1.234,56 €</expectedResult> + <actualResult type="variable">$eurInDENeg</actualResult> + </assertEquals> + + <formatCurrency userInput="1234.56789000" locale="de_DE" currency="USD" stepKey="usdInDE"/> + <assertEquals stepKey="assertUsdInDE"> + <expectedResult type="string">1.234,57 $</expectedResult> + <actualResult type="variable">$usdInDE</actualResult> + </assertEquals> + <formatCurrency userInput="+1234" locale="de_DE" currency="USD" stepKey="usdInDEPos"/> + <assertEquals stepKey="assertUsdInDEPos"> + <expectedResult type="string">1.234,00 $</expectedResult> + <actualResult type="variable">$usdInDEPos</actualResult> + </assertEquals> + <formatCurrency userInput="-1234.56" locale="de_DE" currency="USD" stepKey="usdInDENeg"/> + <assertEquals stepKey="assertUsdInDENeg"> + <expectedResult type="string">-1.234,56 $</expectedResult> + <actualResult type="variable">$usdInDENeg</actualResult> + </assertEquals> + + <executeJS function="return 10.5;" stepKey="variable"/> + <formatCurrency userInput="$variable" locale="de_DE" currency="EUR" stepKey="usingVariable"/> + <assertEquals stepKey="assertUsingVariable"> + <expectedResult type="string">10,50 €</expectedResult> + <actualResult type="variable">$usingVariable</actualResult> + </assertEquals> + </test> +</tests> diff --git a/dev/tests/phpunit.xml b/dev/tests/phpunit.xml index 9648ab3fd..049977650 100644 --- a/dev/tests/phpunit.xml +++ b/dev/tests/phpunit.xml @@ -1,16 +1,25 @@ +<?xml version="1.0"?> <!-- /** * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ --> - -<phpunit - xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/6.3/phpunit.xsd" - convertNoticesToExceptions="false" - bootstrap="_bootstrap.php" - backupGlobals="false"> +<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd" convertNoticesToExceptions="false" + bootstrap="_bootstrap.php" backupGlobals="false"> + <coverage processUncoveredFiles="false"> + <include> + <directory suffix=".php">../../src/Magento/FunctionalTestingFramework/DataGenerator</directory> + <directory suffix=".php">../../src/Magento/FunctionalTestingFramework/Page</directory> + <directory suffix=".php">../../src/Magento/FunctionalTestingFramework/Suite</directory> + <directory suffix=".php">../../src/Magento/FunctionalTestingFramework/Test</directory> + <directory suffix=".php">../../src/Magento/FunctionalTestingFramework/Util</directory> + </include> + <report> + <clover outputFile="build/logs/clover.xml"/> + </report> + </coverage> <testsuites> <testsuite name="verification"> <directory>verification</directory> @@ -19,16 +28,5 @@ <directory>unit</directory> </testsuite> </testsuites> - <filter> - <whitelist processUncoveredFilesFromWhitelist="false"> - <directory suffix=".php">../../src/Magento/FunctionalTestingFramework/DataGenerator</directory> - <directory suffix=".php">../../src/Magento/FunctionalTestingFramework/Page</directory> - <directory suffix=".php">../../src/Magento/FunctionalTestingFramework/Suite</directory> - <directory suffix=".php">../../src/Magento/FunctionalTestingFramework/Test</directory> - <directory suffix=".php">../../src/Magento/FunctionalTestingFramework/Util</directory> - </whitelist> - </filter> - <logging> - <log type="coverage-clover" target="build/logs/clover.xml"/> - </logging> + <logging/> </phpunit> diff --git a/dev/tests/static/Magento/Sniffs/Commenting/FunctionCommentSniff.php b/dev/tests/static/Magento/Sniffs/Commenting/FunctionCommentSniff.php index 5050d5f03..8985f2407 100644 --- a/dev/tests/static/Magento/Sniffs/Commenting/FunctionCommentSniff.php +++ b/dev/tests/static/Magento/Sniffs/Commenting/FunctionCommentSniff.php @@ -214,7 +214,7 @@ protected function processThrows(File $phpcsFile, $stackPtr, $commentStart) } // Starts with a capital letter and ends with a fullstop. - $firstChar = $comment{0}; + $firstChar = $comment[0]; if (strtoupper($firstChar) !== $firstChar) { $error = '@throws tag comment must start with a capital letter'; $phpcsFile->addError($error, ($tag + 2), 'ThrowsNotCapital'); @@ -437,7 +437,7 @@ protected function processParams(File $phpcsFile, $stackPtr, $commentStart) }//end if }//end foreach - $suggestedType = implode($suggestedTypeNames, '|'); + $suggestedType = implode('|', $suggestedTypeNames); if ($param['type'] !== $suggestedType) { $error = 'Expected "%s" but found "%s" for parameter type'; $data = array( diff --git a/dev/tests/static/Magento/Sniffs/MicroOptimizations/IsNullSniff.php b/dev/tests/static/Magento/Sniffs/MicroOptimizations/IsNullSniff.php index 928fc3a0d..9ec74cb4f 100644 --- a/dev/tests/static/Magento/Sniffs/MicroOptimizations/IsNullSniff.php +++ b/dev/tests/static/Magento/Sniffs/MicroOptimizations/IsNullSniff.php @@ -13,7 +13,7 @@ class IsNullSniff implements Sniff /** * @var string */ - protected $blacklist = 'is_null'; + protected $blocklist = 'is_null'; /** * @inheritdoc @@ -29,7 +29,7 @@ public function register() public function process(File $sourceFile, $stackPtr) { $tokens = $sourceFile->getTokens(); - if ($tokens[$stackPtr]['content'] === $this->blacklist) { + if ($tokens[$stackPtr]['content'] === $this->blocklist) { $sourceFile->addError( "is_null must be avoided. Use strict comparison instead.", $stackPtr, diff --git a/dev/tests/static/Magento/Sniffs/NamingConventions/InterfaceNameSniff.php b/dev/tests/static/Magento/Sniffs/NamingConventions/InterfaceNameSniff.php index 1618beb66..4c8461bea 100644 --- a/dev/tests/static/Magento/Sniffs/NamingConventions/InterfaceNameSniff.php +++ b/dev/tests/static/Magento/Sniffs/NamingConventions/InterfaceNameSniff.php @@ -29,9 +29,9 @@ public function process(File $sourceFile, $stackPtr) $declarationLine = $tokens[$stackPtr]['line']; $suffixLength = strlen(self::INTERFACE_SUFFIX); // Find first T_STRING after 'interface' keyword in the line and verify it - while ($tokens[$stackPtr]['line'] == $declarationLine) { - if ($tokens[$stackPtr]['type'] == 'T_STRING') { - if (substr($tokens[$stackPtr]['content'], 0 - $suffixLength) != self::INTERFACE_SUFFIX) { + while ($tokens[$stackPtr]['line'] === $declarationLine) { + if ($tokens[$stackPtr]['type'] === 'T_STRING') { + if (substr($tokens[$stackPtr]['content'], 0 - $suffixLength) !== self::INTERFACE_SUFFIX) { $sourceFile->addError( 'Interface should have name that ends with "Interface" suffix.', $stackPtr, diff --git a/dev/tests/static/Magento/Sniffs/NamingConventions/ReservedWordsSniff.php b/dev/tests/static/Magento/Sniffs/NamingConventions/ReservedWordsSniff.php index f41c235a6..7d1957195 100644 --- a/dev/tests/static/Magento/Sniffs/NamingConventions/ReservedWordsSniff.php +++ b/dev/tests/static/Magento/Sniffs/NamingConventions/ReservedWordsSniff.php @@ -32,6 +32,7 @@ class ReservedWordsSniff implements Sniff 'object' => '7', 'mixed' => '7', 'numeric' => '7', + 'match' => '8', ]; /** diff --git a/dev/tests/unit/Magento/FunctionalTestFramework/Allure/AllureHelperTest.php b/dev/tests/unit/Magento/FunctionalTestFramework/Allure/AllureHelperTest.php index 048c6f7de..832a43978 100644 --- a/dev/tests/unit/Magento/FunctionalTestFramework/Allure/AllureHelperTest.php +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Allure/AllureHelperTest.php @@ -3,38 +3,36 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ -namespace Tests\unit\Magento\FunctionalTestingFramework\Allure; +declare(strict_types=1); + +namespace tests\unit\Magento\FunctionalTestFramework\Allure; use Magento\FunctionalTestingFramework\Allure\AllureHelper; +use Magento\FunctionalTestingFramework\Allure\Event\AddUniqueAttachmentEvent; +use Magento\FunctionalTestingFramework\ObjectManager; +use PHPUnit\Framework\TestCase; +use ReflectionProperty; use Yandex\Allure\Adapter\Allure; -use Yandex\Allure\Adapter\Event\AddAttachmentEvent; +use Yandex\Allure\Adapter\AllureException; use Yandex\Allure\Adapter\Event\StepFinishedEvent; use Yandex\Allure\Adapter\Event\StepStartedEvent; use Yandex\Allure\Adapter\Model\Attachment; -use AspectMock\Test as AspectMock; -use PHPUnit\Framework\TestCase; class AllureHelperTest extends TestCase { - const MOCK_FILENAME = 'filename'; - - /** - * Clear Allure Lifecycle - */ - public function tearDown() - { - Allure::setDefaultLifecycle(); - } + private const MOCK_FILENAME = 'filename'; /** - * AddAtachmentToStep should add an attachment to the current step - * @throws \Yandex\Allure\Adapter\AllureException + * The AddAttachmentToStep should add an attachment to the current step. + * + * @return void + * @throws AllureException */ - public function testAddAttachmentToStep() + public function testAddAttachmentToStep(): void { - $this->mockAttachmentWriteEvent(); - $expectedData = "string"; - $expectedCaption = "caption"; + $expectedData = 'string'; + $expectedCaption = 'caption'; + $this->mockAttachmentWriteEvent($expectedData, $expectedCaption); //Prepare Allure lifecycle Allure::lifecycle()->fire(new StepStartedEvent('firstStep')); @@ -49,14 +47,16 @@ public function testAddAttachmentToStep() } /** - * AddAttachmentToLastStep should add an attachment only to the last step - * @throws \Yandex\Allure\Adapter\AllureException + * The AddAttachmentToLastStep should add an attachment only to the last step. + * + * @return void + * @throws AllureException */ - public function testAddAttachmentToLastStep() + public function testAddAttachmentToLastStep(): void { - $this->mockAttachmentWriteEvent(); - $expectedData = "string"; - $expectedCaption = "caption"; + $expectedData = 'string'; + $expectedCaption = 'caption'; + $this->mockAttachmentWriteEvent($expectedData, $expectedCaption); //Prepare Allure lifecycle Allure::lifecycle()->fire(new StepStartedEvent('firstStep')); @@ -85,13 +85,81 @@ public function testAddAttachmentToLastStep() } /** - * Mock file system manipulation function - * @throws \Exception + * The AddAttachment actions should have files with different attachment names. + * + * @return void + * @throws AllureException */ - public function mockAttachmentWriteEvent() + public function testAddAttachmentUniqueName(): void { - AspectMock::double(AddAttachmentEvent::class, [ - "getAttachmentFileName" => self::MOCK_FILENAME - ]); + $expectedData = 'string'; + $expectedCaption = 'caption'; + + //Prepare Allure lifecycle + Allure::lifecycle()->fire(new StepStartedEvent('firstStep')); + + //Call function twice + AllureHelper::addAttachmentToCurrentStep($expectedData, $expectedCaption); + AllureHelper::addAttachmentToCurrentStep($expectedData, $expectedCaption); + + // Assert file names for both attachments are not the same. + $step = Allure::lifecycle()->getStepStorage()->pollLast(); + $attachmentOne = $step->getAttachments()[0]->getSource(); + $attachmentTwo = $step->getAttachments()[1]->getSource(); + $this->assertNotEquals($attachmentOne, $attachmentTwo); + } + + /** + * Clear Allure Lifecycle. + * + * @return void + */ + protected function tearDown(): void + { + Allure::setDefaultLifecycle(); + + $objectManagerProperty = new ReflectionProperty(ObjectManager::class, 'instance'); + $objectManagerProperty->setAccessible(true); + $objectManagerProperty->setValue(null); + } + + /** + * Mock entire attachment writing mechanisms. + * + * @param string $filePathOrContents + * @param string $caption + * + * @return void + */ + private function mockAttachmentWriteEvent(string $filePathOrContents, string $caption): void + { + $mockInstance = $this->getMockBuilder(AddUniqueAttachmentEvent::class) + ->setConstructorArgs([$filePathOrContents, $caption]) + ->disallowMockingUnknownTypes() + ->onlyMethods(['getAttachmentFileName']) + ->getMock(); + + $mockInstance + ->method('getAttachmentFileName') + ->willReturn(self::MOCK_FILENAME); + + $objectManagerMockInstance = $this->createMock(ObjectManager::class); + $objectManagerMockInstance + ->method('create') + ->will( + $this->returnCallback( + function (string $class) use ($mockInstance) { + if ($class === AddUniqueAttachmentEvent::class) { + return $mockInstance; + } + + return null; + } + ) + ); + + $objectManagerProperty = new ReflectionProperty(ObjectManager::class, 'instance'); + $objectManagerProperty->setAccessible(true); + $objectManagerProperty->setValue($objectManagerMockInstance, $objectManagerMockInstance); } } diff --git a/dev/tests/unit/Magento/FunctionalTestFramework/Composer/ComposerInstallTest.php b/dev/tests/unit/Magento/FunctionalTestFramework/Composer/ComposerInstallTest.php index e4977184f..9b8327bd4 100644 --- a/dev/tests/unit/Magento/FunctionalTestFramework/Composer/ComposerInstallTest.php +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Composer/ComposerInstallTest.php @@ -4,10 +4,10 @@ * See COPYING.txt for license details. */ -namespace tests\unit\Magento\FunctionalTestFramework\Test\Util; +namespace tests\unit\Magento\FunctionalTestFramework\Composer; use Magento\FunctionalTestingFramework\Composer\ComposerInstall; -use Magento\FunctionalTestingFramework\Util\MagentoTestCase; +use tests\unit\Util\MagentoTestCase; class ComposerInstallTest extends MagentoTestCase { @@ -18,7 +18,7 @@ class ComposerInstallTest extends MagentoTestCase */ private $composer; - public function setUp() + public function setUp(): void { $composerJson = dirname(__DIR__) . DIRECTORY_SEPARATOR . 'Composer' . DIRECTORY_SEPARATOR . '_files' . DIRECTORY_SEPARATOR . 'dir1' . DIRECTORY_SEPARATOR . 'dir2' . DIRECTORY_SEPARATOR . 'composer.json'; diff --git a/dev/tests/unit/Magento/FunctionalTestFramework/Composer/ComposerPackageTest.php b/dev/tests/unit/Magento/FunctionalTestFramework/Composer/ComposerPackageTest.php index 4802f0c33..7620f9c1c 100644 --- a/dev/tests/unit/Magento/FunctionalTestFramework/Composer/ComposerPackageTest.php +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Composer/ComposerPackageTest.php @@ -4,10 +4,10 @@ * See COPYING.txt for license details. */ -namespace tests\unit\Magento\FunctionalTestFramework\Test\Util; +namespace tests\unit\Magento\FunctionalTestFramework\Composer; use Magento\FunctionalTestingFramework\Composer\ComposerPackage; -use Magento\FunctionalTestingFramework\Util\MagentoTestCase; +use tests\unit\Util\MagentoTestCase; use Composer\Package\RootPackage; class ComposerPackageTest extends MagentoTestCase @@ -19,7 +19,7 @@ class ComposerPackageTest extends MagentoTestCase */ private $composer; - public function setUp() + public function setUp(): void { $composerJson = dirname(__DIR__) . DIRECTORY_SEPARATOR . 'Composer' . DIRECTORY_SEPARATOR . '_files' . DIRECTORY_SEPARATOR . 'dir1' . DIRECTORY_SEPARATOR . 'dir2' . DIRECTORY_SEPARATOR . 'composer.json'; diff --git a/dev/tests/unit/Magento/FunctionalTestFramework/Config/Reader/FilesystemTest.php b/dev/tests/unit/Magento/FunctionalTestFramework/Config/Reader/FilesystemTest.php index b59858e7f..1fe5bb6b5 100644 --- a/dev/tests/unit/Magento/FunctionalTestFramework/Config/Reader/FilesystemTest.php +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Config/Reader/FilesystemTest.php @@ -3,104 +3,108 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ -namespace Tests\unit\Magento\FunctionalTestFramework\Test\Config\Reader; +declare(strict_types=1); +namespace tests\unit\Magento\FunctionalTestFramework\Config\Reader; + +use Magento\FunctionalTestingFramework\Config\ConverterInterface; use Magento\FunctionalTestingFramework\Config\FileResolver\Module; use Magento\FunctionalTestingFramework\Config\Reader\Filesystem; +use Magento\FunctionalTestingFramework\Config\SchemaLocatorInterface; use Magento\FunctionalTestingFramework\Config\ValidationState; use Magento\FunctionalTestingFramework\Util\Iterator\File; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use AspectMock\Test as AspectMock; use tests\unit\Util\TestLoggingUtil; class FilesystemTest extends TestCase { /** - * Before test functionality * @return void */ - public function setUp() + public function setUp(): void { TestLoggingUtil::getInstance()->setMockLoggingUtil(); } /** - * Test Reading Empty Files * @throws \Exception */ public function testEmptyXmlFile() { - // create mocked items and read the file - $someFile = $this->setMockFile("somepath.xml", ""); - $filesystem = $this->createPseudoFileSystem($someFile); - $filesystem->read(); + $filesystem = $this->getFilesystem($this->getFileIterator('somepath.xml', '')); + $this->assertEquals([], $filesystem->read()); - // validate log statement TestLoggingUtil::getInstance()->validateMockLogStatement( - "warning", - "XML File is empty.", - ["File" => "somepath.xml"] + 'warning', + 'XML File is empty.', + ['File' => 'somepath.xml'] ); } /** - * Function used to set mock for File created in test + * Retrieve mocked file iterator * * @param string $fileName * @param string $content - * @return object + * @return File|MockObject * @throws \Exception */ - public function setMockFile($fileName, $content) + public function getFileIterator(string $fileName, string $content): File { - $file = AspectMock::double( - File::class, - [ - 'current' => "", - 'count' => 1, - 'getFilename' => $fileName - ] - )->make(); + $iterator = new \ArrayIterator([$content]); + + $file = $this->createMock(File::class); + + $file->method('current') + ->willReturn($content); + $file->method('getFilename') + ->willReturn($fileName); + $file->method('count') + ->willReturn(1); + + $file->method('next') + ->willReturnCallback(function () use ($iterator): void { + $iterator->next(); + }); - //set mocked data property for File - $property = new \ReflectionProperty(File::class, 'data'); - $property->setAccessible(true); - $property->setValue($file, [$fileName => $content]); + $file->method('valid') + ->willReturnCallback(function () use ($iterator): bool { + return $iterator->valid(); + }); return $file; } /** - * Function used to set mock for filesystem class during test + * Get real instance of Filesystem class with mocked dependencies * - * @param string $fileList - * @return object - * @throws \Exception + * @param File $fileIterator + * @return Filesystem */ - public function createPseudoFileSystem($fileList) + public function getFilesystem(File $fileIterator): Filesystem { - $filesystem = AspectMock::double(Filesystem::class)->make(); - - //set resolver to use mocked resolver - $mockFileResolver = AspectMock::double(Module::class, ['get' => $fileList])->make(); - $property = new \ReflectionProperty(Filesystem::class, 'fileResolver'); - $property->setAccessible(true); - $property->setValue($filesystem, $mockFileResolver); - - //set validator to use mocked validator - $mockValidation = AspectMock::double(ValidationState::class, ['isValidationRequired' => false])->make(); - $property = new \ReflectionProperty(Filesystem::class, 'validationState'); - $property->setAccessible(true); - $property->setValue($filesystem, $mockValidation); + $fileResolver = $this->createMock(Module::class); + $fileResolver->method('get') + ->willReturn($fileIterator); + $validationState = $this->createMock(ValidationState::class); + $validationState->method('isValidationRequired') + ->willReturn(false); + $filesystem = new Filesystem( + $fileResolver, + $this->createMock(ConverterInterface::class), + $this->createMock(SchemaLocatorInterface::class), + $validationState, + '' + ); return $filesystem; } /** - * After class functionality * @return void */ - public static function tearDownAfterClass() + public static function tearDownAfterClass(): void { TestLoggingUtil::getInstance()->clearMockLoggingUtil(); parent::tearDownAfterClass(); diff --git a/dev/tests/unit/Magento/FunctionalTestFramework/Console/BaseGenerateCommandTest.php b/dev/tests/unit/Magento/FunctionalTestFramework/Console/BaseGenerateCommandTest.php new file mode 100644 index 000000000..fe486b615 --- /dev/null +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Console/BaseGenerateCommandTest.php @@ -0,0 +1,280 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace tests\unit\Magento\FunctionalTestFramework\Console; + +use Exception; +use Magento\FunctionalTestingFramework\Console\BaseGenerateCommand; +use Magento\FunctionalTestingFramework\Suite\Handlers\SuiteObjectHandler; +use Magento\FunctionalTestingFramework\Suite\Objects\SuiteObject; +use Magento\FunctionalTestingFramework\Test\Handlers\TestObjectHandler; +use Magento\FunctionalTestingFramework\Test\Objects\TestObject; +use PHPUnit\Framework\TestCase; +use ReflectionClass; +use ReflectionException; +use ReflectionProperty; + +class BaseGenerateCommandTest extends TestCase +{ + /** + * @inheritDoc + */ + protected function tearDown(): void + { + $handler = TestObjectHandler::getInstance(); + $testsProperty = new ReflectionProperty(TestObjectHandler::class, 'tests'); + $testsProperty->setAccessible(true); + $testsProperty->setValue($handler, []); + $testObjectHandlerProperty = new ReflectionProperty(TestObjectHandler::class, 'testObjectHandler'); + $testObjectHandlerProperty->setAccessible(true); + $testObjectHandlerProperty->setValue($handler); + + $handler = SuiteObjectHandler::getInstance(); + $suiteObjectsProperty = new ReflectionProperty(SuiteObjectHandler::class, 'suiteObjects'); + $suiteObjectsProperty->setAccessible(true); + $suiteObjectsProperty->setValue($handler, []); + $suiteObjectHandlerProperty = new ReflectionProperty(SuiteObjectHandler::class, 'instance'); + $suiteObjectHandlerProperty->setAccessible(true); + $suiteObjectHandlerProperty->setValue($handler); + } + + public function testOneTestOneSuiteConfig(): void + { + $testOne = new TestObject('Test1', [], [], []); + $suiteOne = new SuiteObject('Suite1', ['Test1' => $testOne], [], []); + + $testArray = ['Test1' => $testOne]; + $suiteArray = ['Suite1' => $suiteOne]; + + $this->mockHandlers($testArray, $suiteArray); + + $actual = json_decode($this->callTestConfig(['Test1']), true); + $expected = ['tests' => null, 'suites' => ['Suite1' => ['Test1']]]; + $this->assertEquals($expected, $actual); + } + + public function testOneTestTwoSuitesConfig(): void + { + $testOne = new TestObject('Test1', [], [], []); + $suiteOne = new SuiteObject('Suite1', ['Test1' => $testOne], [], []); + $suiteTwo = new SuiteObject('Suite2', ['Test1' => $testOne], [], []); + + $testArray = ['Test1' => $testOne]; + $suiteArray = ['Suite1' => $suiteOne, 'Suite2' => $suiteTwo]; + + $this->mockHandlers($testArray, $suiteArray); + + $actual = json_decode($this->callTestConfig(['Test1']), true); + $expected = ['tests' => null, 'suites' => ['Suite1' => ['Test1'], 'Suite2' => ['Test1']]]; + $this->assertEquals($expected, $actual); + } + + public function testOneTestOneGroup(): void + { + $testOne = new TestObject('Test1', [], ['group' => ['Group1']], []); + + $testArray = ['Test1' => $testOne]; + $suiteArray = []; + + $this->mockHandlers($testArray, $suiteArray); + + $actual = json_decode($this->callGroupConfig(['Group1']), true); + $expected = ['tests' => ['Test1'], 'suites' => null]; + $this->assertEquals($expected, $actual); + } + + public function testThreeTestsTwoGroup(): void + { + $testOne = new TestObject('Test1', [], ['group' => ['Group1']], []); + $testTwo = new TestObject('Test2', [], ['group' => ['Group1']], []); + $testThree = new TestObject('Test3', [], ['group' => ['Group2']], []); + + $testArray = ['Test1' => $testOne, 'Test2' => $testTwo, 'Test3' => $testThree]; + $suiteArray = []; + + $this->mockHandlers($testArray, $suiteArray); + + $actual = json_decode($this->callGroupConfig(['Group1', 'Group2']), true); + $expected = ['tests' => ['Test1', 'Test2', 'Test3'], 'suites' => null]; + $this->assertEquals($expected, $actual); + } + + public function testOneTestOneSuiteOneGroupConfig(): void + { + $testOne = new TestObject('Test1', [], ['group' => ['Group1']], []); + $suiteOne = new SuiteObject('Suite1', ['Test1' => $testOne], [], []); + + $testArray = ['Test1' => $testOne]; + $suiteArray = ['Suite1' => $suiteOne]; + + $this->mockHandlers($testArray, $suiteArray); + + $actual = json_decode($this->callGroupConfig(['Group1']), true); + $expected = ['tests' => null, 'suites' => ['Suite1' => ['Test1']]]; + $this->assertEquals($expected, $actual); + } + + public function testTwoTestOneSuiteTwoGroupConfig(): void + { + $testOne = new TestObject('Test1', [], ['group' => ['Group1']], []); + $testTwo = new TestObject('Test2', [], ['group' => ['Group2']], []); + $suiteOne = new SuiteObject('Suite1', ['Test1' => $testOne, 'Test2' => $testTwo], [], []); + + $testArray = ['Test1' => $testOne, 'Test2' => $testTwo]; + $suiteArray = ['Suite1' => $suiteOne]; + + $this->mockHandlers($testArray, $suiteArray); + + $actual = json_decode($this->callGroupConfig(['Group1', 'Group2']), true); + $expected = ['tests' => null, 'suites' => ['Suite1' => ['Test1', 'Test2']]]; + $this->assertEquals($expected, $actual); + } + + public function testTwoTestTwoSuiteOneGroupConfig(): void + { + $testOne = new TestObject('Test1', [], ['group' => ['Group1']], []); + $testTwo = new TestObject('Test2', [], ['group' => ['Group1']], []); + $suiteOne = new SuiteObject('Suite1', ['Test1' => $testOne], [], []); + $suiteTwo = new SuiteObject('Suite2', ['Test2' => $testTwo], [], []); + + $testArray = ['Test1' => $testOne, 'Test2' => $testTwo]; + $suiteArray = ['Suite1' => $suiteOne, 'Suite2' => $suiteTwo]; + + $this->mockHandlers($testArray, $suiteArray); + + $actual = json_decode($this->callGroupConfig(['Group1']), true); + $expected = ['tests' => null, 'suites' => ['Suite1' => ['Test1'], 'Suite2' => ['Test2']]]; + $this->assertEquals($expected, $actual); + } + + /** + * Test specific usecase of a test that is in a group with the group being called along with the suite + * i.e. run:group Group1 Suite1. + * + * @return void + * @throws Exception + */ + public function testThreeTestOneSuiteOneGroupMix(): void + { + $testOne = new TestObject('Test1', [], [], []); + $testTwo = new TestObject('Test2', [], [], []); + $testThree = new TestObject('Test3', [], ['group' => ['Group1']], []); + $suiteOne = new SuiteObject( + 'Suite1', + ['Test1' => $testOne, 'Test2' => $testTwo, 'Test3' => $testThree], + [], + [] + ); + + $testArray = ['Test1' => $testOne, 'Test2' => $testTwo, 'Test3' => $testThree]; + $suiteArray = ['Suite1' => $suiteOne]; + + $this->mockHandlers($testArray, $suiteArray); + + $actual = json_decode($this->callGroupConfig(['Group1', 'Suite1']), true); + $expected = ['tests' => null, 'suites' => ['Suite1' => []]]; + $this->assertEquals($expected, $actual); + } + + public function testSuiteToTestSyntax(): void + { + $testOne = new TestObject('Test1', [], [], []); + $suiteOne = new SuiteObject( + 'Suite1', + ['Test1' => $testOne], + [], + [] + ); + + $testArray = ['Test1' => $testOne]; + $suiteArray = ['Suite1' => $suiteOne]; + $this->mockHandlers($testArray, $suiteArray); + $actual = json_decode($this->callTestConfig(['Suite1:Test1']), true); + $expected = ['tests' => null, 'suites' => ['Suite1' => ['Test1']]]; + $this->assertEquals($expected, $actual); + } + + /** + * Mock handlers to skip parsing. + * + * @param array $testArray + * @param array $suiteArray + * + * @return void + * @throws Exception + */ + public function mockHandlers(array $testArray, array $suiteArray): void + { + // bypass the initTestData method + $testObjectHandlerClass = new ReflectionClass(TestObjectHandler::class); + $constructor = $testObjectHandlerClass->getConstructor(); + $constructor->setAccessible(true); + $testObjectHandlerObject = $testObjectHandlerClass->newInstanceWithoutConstructor(); + $constructor->invoke($testObjectHandlerObject); + + $testObjectHandlerProperty = new ReflectionProperty(TestObjectHandler::class, 'testObjectHandler'); + $testObjectHandlerProperty->setAccessible(true); + $testObjectHandlerProperty->setValue($testObjectHandlerObject); + + $handler = TestObjectHandler::getInstance(); + $property = new ReflectionProperty(TestObjectHandler::class, 'tests'); + $property->setAccessible(true); + $property->setValue($handler, $testArray); + + // bypass the initTestData method + $suiteObjectHandlerClass = new ReflectionClass(SuiteObjectHandler::class); + $constructor = $suiteObjectHandlerClass->getConstructor(); + $constructor->setAccessible(true); + $suiteObjectHandlerObject = $suiteObjectHandlerClass->newInstanceWithoutConstructor(); + $constructor->invoke($suiteObjectHandlerObject); + + $suiteObjectHandlerProperty = new ReflectionProperty(SuiteObjectHandler::class, 'instance'); + $suiteObjectHandlerProperty->setAccessible(true); + $suiteObjectHandlerProperty->setValue($suiteObjectHandlerObject); + + $handler = SuiteObjectHandler::getInstance(); + $property = new ReflectionProperty(SuiteObjectHandler::class, 'suiteObjects'); + $property->setAccessible(true); + $property->setValue($handler, $suiteArray); + } + + /** + * Changes visibility and runs getTestAndSuiteConfiguration. + * + * @param array $testArray + * + * @return string + * @throws ReflectionException + */ + public function callTestConfig(array $testArray): string + { + $command = new BaseGenerateCommand(); + $class = new ReflectionClass($command); + $method = $class->getMethod('getTestAndSuiteConfiguration'); + $method->setAccessible(true); + + return $method->invokeArgs($command, [$testArray]); + } + + /** + * Changes visibility and runs getGroupAndSuiteConfiguration. + * + * @param array $groupArray + * + * @return string + * @throws ReflectionException + */ + public function callGroupConfig(array $groupArray): string + { + $command = new BaseGenerateCommand(); + $class = new ReflectionClass($command); + $method = $class->getMethod('getGroupAndSuiteConfiguration'); + $method->setAccessible(true); + + return $method->invokeArgs($command, [$groupArray]); + } +} diff --git a/dev/tests/unit/Magento/FunctionalTestFramework/Console/GenerateTestFailedCommandTest.php b/dev/tests/unit/Magento/FunctionalTestFramework/Console/GenerateTestFailedCommandTest.php new file mode 100644 index 000000000..317868300 --- /dev/null +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Console/GenerateTestFailedCommandTest.php @@ -0,0 +1,166 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace tests\unit\Magento\FunctionalTestFramework\Console; + +use Magento\FunctionalTestingFramework\Console\GenerateTestFailedCommand; +use Magento\FunctionalTestingFramework\Test\Objects\TestObject; +use PHPUnit\Framework\MockObject\MockBuilder; +use PHPUnit\Framework\TestCase; +use Magento\FunctionalTestingFramework\Exceptions\FastFailException; +use Magento\FunctionalTestingFramework\Console\GenerateTestsCommand; +use ReflectionClass; + +class GenerateTestFailedCommandTest extends BaseGenerateCommandTest +{ + public function testSingleTestWithNoSuite(): void + { + $testFileReturn = [ + "tests/functional/tests/MFTF/_generated/default/SingleTestNoSuiteTest.php:SingleTestNoSuiteTest" + ]; + $expectedConfiguration = '{"tests":["SingleTestNoSuiteTest"],"suites":null}'; + + // Create a stub for the SomeClass class. + $stub = $this->getMockBuilder(GenerateTestFailedCommand::class) + ->onlyMethods(["readFailedTestFile", "writeFailedTestToFile"]) + ->getMock(); + // Configure the stub. + $stub + ->method('readFailedTestFile') + ->willReturn($testFileReturn); + $stub + ->method('writeFailedTestToFile') + ->willReturn(null); + + // Run the real code + $configuration = $stub->getFailedTestList("", ""); + $this->assertEquals($expectedConfiguration, $configuration); + } + + public function testMultipleTestsWithSuites(): void + { + $testFileReturn = [ + "tests/functional/tests/MFTF/_generated/SomeSpecificSuite/FirstTestSuiteTest.php:SingleTestSuiteTest", + "tests/functional/tests/MFTF/_generated/SomeSpecificSuite/SecondTestNoSuiteTest.php:SingleTestNoSuiteTest" + ]; + $expectedConfiguration = + '{"tests":null,"suites":{"SomeSpecificSuite":["SingleTestSuiteTest","SingleTestNoSuiteTest"]}}'; + + // Create a stub for the SomeClass class. + $stub = $this->getMockBuilder(GenerateTestFailedCommand::class) + ->onlyMethods(["readFailedTestFile", "writeFailedTestToFile"]) + ->getMock(); + // Configure the stub. + $stub + ->method('readFailedTestFile') + ->willReturn($testFileReturn); + $stub + ->method('writeFailedTestToFile') + ->willReturn(null); + + // Run the real code + $configuration = $stub->getFailedTestList("", ""); + $this->assertEquals($expectedConfiguration, $configuration); + } + + public function testMultipleTestFailureWithNoSuites(): void + { + $testFileReturn = [ + "tests/functional/tests/MFTF/_generated/default/SingleTestNoSuiteTest.php:SingleTestNoSuiteTest", + "tests/functional/tests/MFTF/_generated/default/FirstTestSuiteTest.php:SingleTestSuiteTest" + ]; + $expectedConfiguration = '{"tests":["SingleTestNoSuiteTest","SingleTestSuiteTest"],"suites":null}'; + + // Create a stub for the SomeClass class. + $stub = $this->getMockBuilder(GenerateTestFailedCommand::class) + ->onlyMethods(["readFailedTestFile", "writeFailedTestToFile"]) + ->getMock(); + // Configure the stub. + $stub + ->method('readFailedTestFile') + ->willReturn($testFileReturn); + $stub + ->method('writeFailedTestToFile') + ->willReturn(null); + + // Run the real code + $configuration = $stub->getFailedTestList("", ""); + $this->assertEquals($expectedConfiguration, $configuration); + } + + public function testSingleSuiteAndNoTest(): void + { + $testFileReturn = [ + "tests/functional/tests/MFTF/_generated/SomeSpecificSuite/", + ]; + $expectedConfiguration = '{"tests":null,"suites":{"SomeSpecificSuite":[[]]}}'; + + // Create a stub for the SomeClass class. + $stub = $this->getMockBuilder(GenerateTestFailedCommand::class) + ->onlyMethods(["readFailedTestFile", "writeFailedTestToFile"]) + ->getMock(); + // Configure the stub. + $stub + ->method('readFailedTestFile') + ->willReturn($testFileReturn); + $stub + ->method('writeFailedTestToFile') + ->willReturn(null); + + // Run the real code + $configuration = $stub->getFailedTestList("", ""); + $this->assertEquals($expectedConfiguration, $configuration); + } + + public function testSingleSuiteWithTest(): void + { + $testFileReturn = [ + "tests/functional/tests/MFTF/_generated/SomeSpecificSuite/FirstTestSuiteTest.php:SingleTestSuiteTest", + ]; + $expectedConfiguration = '{"tests":null,"suites":{"SomeSpecificSuite":["SingleTestSuiteTest"]}}'; + + // Create a stub for the SomeClass class. + $stub = $this->getMockBuilder(GenerateTestFailedCommand::class) + ->onlyMethods(["readFailedTestFile", "writeFailedTestToFile"]) + ->getMock(); + // Configure the stub. + $stub + ->method('readFailedTestFile') + ->willReturn($testFileReturn); + $stub + ->method('writeFailedTestToFile') + ->willReturn(null); + + // Run the real code + $configuration = $stub->getFailedTestList("", ""); + $this->assertEquals($expectedConfiguration, $configuration); + } + + public function testMultipleSuitesWithNoTests(): void + { + $testFileReturn = [ + "tests/functional/tests/MFTF/_generated/SomeSpecificSuite/", + "tests/functional/tests/MFTF/_generated/SomeSpecificSuite1/", + + ]; + $expectedConfiguration = '{"tests":null,"suites":{"SomeSpecificSuite":[[]],"SomeSpecificSuite1":[[]]}}'; + + // Create a stub for the SomeClass class. + $stub = $this->getMockBuilder(GenerateTestFailedCommand::class) + ->onlyMethods(["readFailedTestFile", "writeFailedTestToFile"]) + ->getMock(); + // Configure the stub. + $stub + ->method('readFailedTestFile') + ->willReturn($testFileReturn); + $stub + ->method('writeFailedTestToFile') + ->willReturn(null); + + // Run the real code + $configuration = $stub->getFailedTestList("", ""); + $this->assertEquals($expectedConfiguration, $configuration); + } +} diff --git a/dev/tests/unit/Magento/FunctionalTestFramework/Console/GenerateTestsCommandTest.php b/dev/tests/unit/Magento/FunctionalTestFramework/Console/GenerateTestsCommandTest.php new file mode 100644 index 000000000..715031c53 --- /dev/null +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Console/GenerateTestsCommandTest.php @@ -0,0 +1,81 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace tests\unit\Magento\FunctionalTestFramework\Console; + +use PHPUnit\Framework\TestCase; +use Magento\FunctionalTestingFramework\Exceptions\FastFailException; +use Magento\FunctionalTestingFramework\Console\GenerateTestsCommand; + +class GenerateTestsCommandTest extends TestCase +{ + /** + * @param mixed $time + * @param mixed $groups + * @param mixed $expected + * @return void + * @dataProvider configParallelOptions + * @throws \ReflectionException + */ + public function testParseConfigParallelOptions($time, $groups, $expected): void + { + $command = new GenerateTestsCommand(); + $class = new \ReflectionClass($command); + $method = $class->getMethod('parseConfigParallelOptions'); + $method->setAccessible(true); + + if (is_array($expected)) { + $actual = $method->invokeArgs($command, [$time, $groups]); + $this->assertEquals($expected, $actual); + } else { + $this->expectException(FastFailException::class); + $this->expectExceptionMessage($expected); + $method->invokeArgs($command, [$time, $groups]); + } + } + + /** + * Data provider for testParseConfigParallelOptions() + * + * @return array + */ + public function configParallelOptions(): array + { + return [ + [null, null, ['parallelByTime', 600000]], /* #0 */ + ['20', null, ['parallelByTime', 1200000]], /* #1 */ + [5, null, ['parallelByTime', 300000]], /* #2 */ + [null, '300', ['parallelByGroup', 300]], /* #3 */ + [null, 1000, ['parallelByGroup', 1000]], /* #4 */ + [0.5, null, "'time' option must be an integer and greater than 0"], /* #5 */ + [0, null, "'time' option must be an integer and greater than 0"], /* #6 */ + ['0', null, "'time' option must be an integer and greater than 0"], /* #7 */ + [0.0, null, "'time' option must be an integer and greater than 0"], /* #8 */ + ['0.0', null, "'time' option must be an integer and greater than 0"], /* #9 */ + [-10, null, "'time' option must be an integer and greater than 0"], /* #10 */ + ['-10', null, "'time' option must be an integer and greater than 0"], /* #11 */ + ['12x', null, "'time' option must be an integer and greater than 0"], /* #12 */ + [null, 0.5, "'groups' option must be an integer and greater than 0"], /* #13 */ + [null, 0, "'groups' option must be an integer and greater than 0"], /* #14 */ + [null, 0.0, "'groups' option must be an integer and greater than 0"], /* #15 */ + [null, '0', "'groups' option must be an integer and greater than 0"], /* #16 */ + [null, '0.0', "'groups' option must be an integer and greater than 0"], /* #17 */ + [null, -10, "'groups' option must be an integer and greater than 0"], /* #18 */ + [null, '-10', "'groups' option must be an integer and greater than 0"], /* #19 */ + [null, '12x', "'groups' option must be an integer and greater than 0"], /* #20 */ + ['20', '300', "'time' and 'groups' options are mutually exclusive. " + . "Only one can be specified for 'config parallel'" + ], /* #21 */ + [20, 300, "'time' and 'groups' options are mutually exclusive. " + . "Only one can be specified for 'config parallel'" + ], /* #22 */ + ['0', 0, "'time' and 'groups' options are mutually exclusive. " + . "Only one can be specified for 'config parallel'" + ], /* #23 */ + [[1], null, "'time' option must be an integer and greater than 0"], /* #24 */ + [null, [-1], "'groups' option must be an integer and greater than 0"], /* #25 */ + ]; + } +} diff --git a/dev/tests/unit/Magento/FunctionalTestFramework/Console/RunTestFailedCommandTest.php b/dev/tests/unit/Magento/FunctionalTestFramework/Console/RunTestFailedCommandTest.php new file mode 100644 index 000000000..db0c63c61 --- /dev/null +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Console/RunTestFailedCommandTest.php @@ -0,0 +1,132 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace tests\unit\Magento\FunctionalTestFramework\Console; + +use Magento\FunctionalTestingFramework\Console\RunTestFailedCommand; + +class RunTestFailedCommandTest extends BaseGenerateCommandTest +{ + /** + * @throws \ReflectionException + */ + public function testMultipleTests(): void + { + $testFailedFile = [ + "tests/functional/tests/MFTF/_generated/default/SingleTestNoSuiteTest.php:SingleTestNoSuiteTest", + "tests/functional/tests/MFTF/_generated/SomeSpecificSuite/FirstTestSuiteTest.php:SingleTestSuiteTest", + "tests/functional/tests/MFTF/_generated/SomeSpecificSuite/SecondTestNoSuiteTest.php:SingleTestNoSuiteTest", + "tests/functional/tests/MFTF/_generated/SomeOtherSuite/SecondTestNoSuiteTest.php:SingleTestNoSuiteTest", + ]; + + $expectedResult = [ + "tests/functional/tests/MFTF/_generated/default/SingleTestNoSuiteTest.php", + "-g SomeSpecificSuite", + "-g SomeOtherSuite", + ]; + + $runFailed = new RunTestFailedCommand('run:failed'); + $filter = $this->invokePrivateMethod($runFailed, 'filterTestsForExecution', [$testFailedFile]); + $this->assertEquals($expectedResult, $filter); + } + + /** + * Invoking private method to be able to test it. + * NOTE: Bad practice don't repeat it. + * + * @param $object + * @param $methodName + * @param array $parameters + * @return mixed + * @throws \ReflectionException + */ + private function invokePrivateMethod(&$object, $methodName, array $parameters = []) + { + $reflection = new \ReflectionClass(get_class($object)); + $method = $reflection->getMethod($methodName); + $method->setAccessible(true); + return $method->invokeArgs($object, $parameters); + } + + public function testSingleTestNoSuite(): void + { + $testFailedFile = [ + "tests/functional/tests/MFTF/_generated/default/SingleTestNoSuiteTest.php:SingleTestNoSuiteTest" + ]; + + $expectedResult = [ + 'tests/functional/tests/MFTF/_generated/default/SingleTestNoSuiteTest.php' + ]; + + $runFailed = new RunTestFailedCommand('run:failed'); + $filter = $this->invokePrivateMethod($runFailed, 'filterTestsForExecution', [$testFailedFile]); + $this->assertEquals($expectedResult, $filter); + } + + public function testMultipleTestNoSuite(): void + { + $testFailedFile = [ + "tests/functional/tests/MFTF/_generated/default/SingleTestNoSuiteTest.php:SingleTestNoSuiteTest", + "tests/functional/tests/MFTF/_generated/default/FirstTestSuiteTest.php:SingleTestSuiteTest" + ]; + + $expectedResult = [ + "tests/functional/tests/MFTF/_generated/default/SingleTestNoSuiteTest.php", + "tests/functional/tests/MFTF/_generated/default/FirstTestSuiteTest.php" + ]; + + $runFailed = new RunTestFailedCommand('run:failed'); + $filter = $this->invokePrivateMethod($runFailed, 'filterTestsForExecution', [$testFailedFile]); + $this->assertEquals($expectedResult, $filter); + } + + public function testSingleSuiteNoTest(): void + { + $testFailedFile = [ + "tests/functional/tests/MFTF/_generated/SomeSpecificSuite/", + ]; + + $expectedResult = [ + "-g SomeSpecificSuite" + ]; + + $runFailed = new RunTestFailedCommand('run:failed'); + $filter = $this->invokePrivateMethod($runFailed, 'filterTestsForExecution', [$testFailedFile]); + $this->assertEquals($expectedResult, $filter); + } + + public function testSingleSuiteAndTest(): void + { + $testFailedFile = [ + "tests/functional/tests/MFTF/_generated/SomeSpecificSuite/FirstTestSuiteTest.php:SingleTestSuiteTest", + ]; + $expectedResult = [ + "-g SomeSpecificSuite", + ]; + + $runFailed = new RunTestFailedCommand('run:failed'); + $filter = $this->invokePrivateMethod($runFailed, 'filterTestsForExecution', [$testFailedFile]); + $this->assertEquals($expectedResult, $filter); + } + + public function testMultipleSuitesWithNoTest(): void + { + $testFailedFile = [ + "tests/functional/tests/MFTF/_generated/SomeSpecificSuite/", + "tests/functional/tests/MFTF/_generated/SomeSpecificSuite1/", + "tests/functional/tests/MFTF/_generated/SomeSpecificSuite2/" + ]; + $expectedResult = [ + "-g SomeSpecificSuite", + "-g SomeSpecificSuite1", + "-g SomeSpecificSuite2", + ]; + + $runFailed = new RunTestFailedCommand('run:failed'); + $filter = $this->invokePrivateMethod($runFailed, 'filterTestsForExecution', [$testFailedFile]); + $this->assertEquals($expectedResult, $filter); + } +} diff --git a/dev/tests/unit/Magento/FunctionalTestFramework/DataGenerator/Handlers/DataObjectHandlerTest.php b/dev/tests/unit/Magento/FunctionalTestFramework/DataGenerator/Handlers/DataObjectHandlerTest.php index d55f35247..7ef84a53d 100644 --- a/dev/tests/unit/Magento/FunctionalTestFramework/DataGenerator/Handlers/DataObjectHandlerTest.php +++ b/dev/tests/unit/Magento/FunctionalTestFramework/DataGenerator/Handlers/DataObjectHandlerTest.php @@ -3,22 +3,34 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); namespace tests\unit\Magento\FunctionalTestFramework\DataGenerator\Handlers; -use AspectMock\Test as AspectMock; +use Exception; use Magento\FunctionalTestingFramework\DataGenerator\Handlers\DataObjectHandler; use Magento\FunctionalTestingFramework\DataGenerator\Objects\EntityDataObject; use Magento\FunctionalTestingFramework\DataGenerator\Parsers\DataProfileSchemaParser; +use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; use Magento\FunctionalTestingFramework\ObjectManager; use Magento\FunctionalTestingFramework\ObjectManagerFactory; -use Magento\FunctionalTestingFramework\Util\MagentoTestCase; +use ReflectionProperty; +use tests\unit\Util\MagentoTestCase; +use tests\unit\Util\TestLoggingUtil; /** * Class DataObjectHandlerTest */ class DataObjectHandlerTest extends MagentoTestCase { + /** + * @inheritDoc + */ + protected function setUp(): void + { + TestLoggingUtil::getInstance()->setMockLoggingUtil(); + } + // All tests share this array, feel free to add but be careful modifying or removing const PARSER_OUTPUT = [ 'entity' => [ @@ -44,6 +56,22 @@ class DataObjectHandlerTest extends MagentoTestCase ] ]; + const PARSER_OUTPUT_DEPRECATED = [ + 'entity' => [ + 'EntityOne' => [ + 'type' => 'testType', + 'data' => [ + 0 => [ + 'key' => 'testKey', + 'value' => 'testValue' + ] + ], + 'deprecated' => "deprecation message", + 'filename' => "filename.xml" + ], + ] + ]; + const PARSER_OUTPUT_WITH_EXTEND = [ 'entity' => [ 'EntityOne' => [ @@ -119,11 +147,14 @@ class DataObjectHandlerTest extends MagentoTestCase ]; /** - * getAllObjects should contain the expected data object + * Validate getAllObjects should contain the expected data object. + * + * @return void + * @throws Exception */ - public function testGetAllObjects() + public function testGetAllObjects(): void { - $this->setUpMockDataObjectHander(self::PARSER_OUTPUT); + $this->mockDataObjectHandlerWithData(self::PARSER_OUTPUT); // Call the method under test $actual = DataObjectHandler::getInstance()->getAllObjects(); @@ -135,11 +166,35 @@ public function testGetAllObjects() } /** - * getObject should return the expected data object if it exists + * Validate test deprecated data object. + * + * @return void + * @throws Exception + */ + public function testDeprecatedDataObject(): void + { + $this->mockDataObjectHandlerWithData(self::PARSER_OUTPUT_DEPRECATED); + + // Call the method under test + DataObjectHandler::getInstance()->getAllObjects(); + + //validate deprecation warning + TestLoggingUtil::getInstance()->validateMockLogStatement( + 'warning', + 'DEPRECATION: The data entity \'EntityOne\' is deprecated.', + ['fileName' => 'filename.xml', 'deprecatedMessage' => 'deprecation message'] + ); + } + + /** + * Validate getObject should return the expected data object if it exists. + * + * @return void + * @throws Exception */ - public function testGetObject() + public function testGetObject(): void { - $this->setUpMockDataObjectHander(self::PARSER_OUTPUT); + $this->mockDataObjectHandlerWithData(self::PARSER_OUTPUT); // Call the method under test $actual = DataObjectHandler::getInstance()->getObject('EntityOne'); @@ -150,22 +205,28 @@ public function testGetObject() } /** - * getAllObjects should return the expected data object if it exists + * Validate getAllObjects should return the expected data object if it exists. + * + * @return void + * @throws Exception */ - public function testGetObjectNull() + public function testGetObjectNull(): void { - $this->setUpMockDataObjectHander(self::PARSER_OUTPUT); + $this->mockDataObjectHandlerWithData(self::PARSER_OUTPUT); $actual = DataObjectHandler::getInstance()->getObject('h953u789h0g73t521'); // doesnt exist $this->assertNull($actual); } /** - * getAllObjects should contain the expected data object with extends + * Validate getAllObjects should contain the expected data object with extends. + * + * @return void + * @throws Exception */ - public function testGetAllObjectsWithDataExtends() + public function testGetAllObjectsWithDataExtends(): void { - $this->setUpMockDataObjectHander(self::PARSER_OUTPUT_WITH_EXTEND); + $this->mockDataObjectHandlerWithData(self::PARSER_OUTPUT_WITH_EXTEND); // Call the method under test $actual = DataObjectHandler::getInstance()->getAllObjects(); @@ -185,11 +246,14 @@ public function testGetAllObjectsWithDataExtends() } /** - * getObject should return the expected data object with extended data if it exists + * Validate getObject should return the expected data object with extended data if it exists. + * + * @return void + * @throws Exception */ - public function testGetObjectWithDataExtends() + public function testGetObjectWithDataExtends(): void { - $this->setUpMockDataObjectHander(self::PARSER_OUTPUT_WITH_EXTEND); + $this->mockDataObjectHandlerWithData(self::PARSER_OUTPUT_WITH_EXTEND); // Call the method under test $actual = DataObjectHandler::getInstance()->getObject('EntityTwo'); @@ -208,15 +272,18 @@ public function testGetObjectWithDataExtends() } /** - * getAllObjects should throw TestFrameworkException exception if some data extends itself + * Validate getAllObjects should throw TestFrameworkException exception if some data extends itself. + * + * @return void + * @throws Exception */ - public function testGetAllObjectsWithDataExtendsItself() + public function testGetAllObjectsWithDataExtendsItself(): void { - $this->setUpMockDataObjectHander(self::PARSER_OUTPUT_WITH_EXTEND_INVALID); + $this->mockDataObjectHandlerWithData(self::PARSER_OUTPUT_WITH_EXTEND_INVALID); - $this->expectException(\Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException::class); + $this->expectException(TestFrameworkException::class); $this->expectExceptionMessage( - "Mftf Data can not extend from itself: " + 'Mftf Data can not extend from itself: ' . self::PARSER_OUTPUT_WITH_EXTEND_INVALID['entity']['EntityOne']['name'] ); @@ -225,15 +292,18 @@ public function testGetAllObjectsWithDataExtendsItself() } /** - * getObject should throw TestFrameworkException exception if requested data extends itself + * Validate getObject should throw TestFrameworkException exception if requested data extends itself. + * + * @return void + * @throws Exception */ - public function testGetObjectWithDataExtendsItself() + public function testGetObjectWithDataExtendsItself(): void { - $this->setUpMockDataObjectHander(self::PARSER_OUTPUT_WITH_EXTEND_INVALID); + $this->mockDataObjectHandlerWithData(self::PARSER_OUTPUT_WITH_EXTEND_INVALID); - $this->expectException(\Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException::class); + $this->expectException(TestFrameworkException::class); $this->expectExceptionMessage( - "Mftf Data can not extend from itself: " + 'Mftf Data can not extend from itself: ' . self::PARSER_OUTPUT_WITH_EXTEND_INVALID['entity']['EntityOne']['name'] ); @@ -244,29 +314,65 @@ public function testGetObjectWithDataExtendsItself() } /** - * Set up everything required to mock DataObjectHander::getInstance() - * The first call to getInstance() uses these mocks to emulate the parser, initializing internal state - * according to the PARSER_OUTPUT value + * Create mock data object handler with data. + * + * @param array $mockData * - * @param array $entityDataArray + * @return void */ - private function setUpMockDataObjectHander($entityDataArray) + private function mockDataObjectHandlerWithData(array $mockData): void { - // Clear DataObjectHandler singleton if already set - $property = new \ReflectionProperty(DataObjectHandler::class, "INSTANCE"); + $dataObjectHandlerProperty = new ReflectionProperty(DataObjectHandler::class, "INSTANCE"); + $dataObjectHandlerProperty->setAccessible(true); + $dataObjectHandlerProperty->setValue(null); + + $mockDataProfileSchemaParser = $this->createMock(DataProfileSchemaParser::class); + $mockDataProfileSchemaParser + ->method('readDataProfiles') + ->willReturn($mockData); + + $objectManager = ObjectManagerFactory::getObjectManager(); + $mockObjectManagerInstance = $this->createMock(ObjectManager::class); + $mockObjectManagerInstance + ->method('create') + ->will( + $this->returnCallback( + function ( + string $class, + array $arguments = [] + ) use ( + $objectManager, + $mockDataProfileSchemaParser + ) { + if ($class === DataProfileSchemaParser::class) { + return $mockDataProfileSchemaParser; + } + + return $objectManager->create($class, $arguments); + } + ) + ); + + $property = new ReflectionProperty(ObjectManager::class, 'instance'); $property->setAccessible(true); - $property->setValue(null); + $property->setValue($mockObjectManagerInstance); + } + + /** + * @inheritDoc + */ + public static function tearDownAfterClass(): void + { + parent::tearDownAfterClass(); - $mockDataProfileSchemaParser = AspectMock::double(DataProfileSchemaParser::class, [ - 'readDataProfiles' => $entityDataArray - ])->make(); + $dataObjectHandlerProperty = new ReflectionProperty(DataObjectHandler::class, "INSTANCE"); + $dataObjectHandlerProperty->setAccessible(true); + $dataObjectHandlerProperty->setValue(null); - $mockObjectManager = AspectMock::double(ObjectManager::class, [ - 'create' => $mockDataProfileSchemaParser - ])->make(); + $objectManagerProperty = new ReflectionProperty(ObjectManager::class, 'instance'); + $objectManagerProperty->setAccessible(true); + $objectManagerProperty->setValue(null); - AspectMock::double(ObjectManagerFactory::class, [ - 'getObjectManager' => $mockObjectManager - ]); + TestLoggingUtil::getInstance()->clearMockLoggingUtil(); } } diff --git a/dev/tests/unit/Magento/FunctionalTestFramework/DataGenerator/Handlers/OperationDefinitionObjectHandlerTest.php b/dev/tests/unit/Magento/FunctionalTestFramework/DataGenerator/Handlers/OperationDefinitionObjectHandlerTest.php index b54980314..33079b6cd 100644 --- a/dev/tests/unit/Magento/FunctionalTestFramework/DataGenerator/Handlers/OperationDefinitionObjectHandlerTest.php +++ b/dev/tests/unit/Magento/FunctionalTestFramework/DataGenerator/Handlers/OperationDefinitionObjectHandlerTest.php @@ -3,29 +3,46 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); namespace tests\unit\Magento\FunctionalTestFramework\DataGenerator\Handlers; -use AspectMock\Test as AspectMock; +use Exception; +use Magento\FunctionalTestingFramework\DataGenerator\Handlers\OperationDefinitionObjectHandler; use Magento\FunctionalTestingFramework\DataGenerator\Objects\OperationDefinitionObject; use Magento\FunctionalTestingFramework\DataGenerator\Objects\OperationElement; +use Magento\FunctionalTestingFramework\DataGenerator\Parsers\OperationDefinitionParser; use Magento\FunctionalTestingFramework\ObjectManager; use Magento\FunctionalTestingFramework\ObjectManagerFactory; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\OperationDefinitionObjectHandler; -use Magento\FunctionalTestingFramework\DataGenerator\Parsers\OperationDefinitionParser; -use Magento\FunctionalTestingFramework\Util\MagentoTestCase; +use ReflectionProperty; +use tests\unit\Util\MagentoTestCase; +use tests\unit\Util\TestLoggingUtil; /** * Class OperationDefinitionObjectHandlerTest */ class OperationDefinitionObjectHandlerTest extends MagentoTestCase { - public function testGetMultipleObjects() + /** + * @inheritDoc + */ + protected function setUp(): void + { + TestLoggingUtil::getInstance()->setMockLoggingUtil(); + } + + /** + * Validate testGetMultipleObjects. + * + * @return void + * @throws Exception + */ + public function testGetMultipleObjects(): void { // Data Variables for Assertions - $dataType1 = "type1"; - $operationType1 = "create"; - $operationType2 = "update"; + $dataType1 = 'type1'; + $operationType1 = 'create'; + $operationType2 = 'update'; /** * Parser Output. Just two simple pieces of metadata with 1 field each @@ -37,33 +54,37 @@ public function testGetMultipleObjects() * has field * key=id, value=integer */ - $mockData = [OperationDefinitionObjectHandler::ENTITY_OPERATION_ROOT_TAG => [ - "testOperationName" => [ - OperationDefinitionObjectHandler::ENTITY_OPERATION_DATA_TYPE => $dataType1, - OperationDefinitionObjectHandler::ENTITY_OPERATION_TYPE => $operationType1, - OperationDefinitionObjectHandler::ENTITY_OPERATION_AUTH => "auth", - OperationDefinitionObjectHandler::ENTITY_OPERATION_URL => "V1/Type1", - OperationDefinitionObjectHandler::ENTITY_OPERATION_METHOD => "POST", - OperationDefinitionObjectHandler::ENTITY_OPERATION_ENTRY => [ - 0 => [ - OperationDefinitionObjectHandler::ENTITY_OPERATION_ENTRY_KEY => "id", - OperationDefinitionObjectHandler::ENTITY_OPERATION_ENTRY_VALUE => "integer" - ], + $mockData = [ + OperationDefinitionObjectHandler::ENTITY_OPERATION_ROOT_TAG => [ + 'testOperationName' => [ + OperationDefinitionObjectHandler::ENTITY_OPERATION_DATA_TYPE => $dataType1, + OperationDefinitionObjectHandler::ENTITY_OPERATION_TYPE => $operationType1, + OperationDefinitionObjectHandler::ENTITY_OPERATION_AUTH => 'auth', + OperationDefinitionObjectHandler::ENTITY_OPERATION_URL => 'V1/Type1', + OperationDefinitionObjectHandler::ENTITY_OPERATION_METHOD => 'POST', + OperationDefinitionObjectHandler::ENTITY_OPERATION_ENTRY => [ + 0 => [ + OperationDefinitionObjectHandler::ENTITY_OPERATION_ENTRY_KEY => 'id', + OperationDefinitionObjectHandler::ENTITY_OPERATION_ENTRY_VALUE => 'integer' + ], + ] + ], + [ + OperationDefinitionObjectHandler::ENTITY_OPERATION_DATA_TYPE => $dataType1, + OperationDefinitionObjectHandler::ENTITY_OPERATION_TYPE => $operationType2, + OperationDefinitionObjectHandler::ENTITY_OPERATION_AUTH => 'auth', + OperationDefinitionObjectHandler::ENTITY_OPERATION_URL => 'V1/Type1/{id}', + OperationDefinitionObjectHandler::ENTITY_OPERATION_METHOD => 'PUT', + OperationDefinitionObjectHandler::ENTITY_OPERATION_ENTRY => [ + 0 => [ + OperationDefinitionObjectHandler::ENTITY_OPERATION_ENTRY_KEY => 'id', + OperationDefinitionObjectHandler::ENTITY_OPERATION_ENTRY_VALUE => 'integer' + ], ] - ],[ - OperationDefinitionObjectHandler::ENTITY_OPERATION_DATA_TYPE => $dataType1, - OperationDefinitionObjectHandler::ENTITY_OPERATION_TYPE => $operationType2, - OperationDefinitionObjectHandler::ENTITY_OPERATION_AUTH => "auth", - OperationDefinitionObjectHandler::ENTITY_OPERATION_URL => "V1/Type1/{id}", - OperationDefinitionObjectHandler::ENTITY_OPERATION_METHOD => "PUT", - OperationDefinitionObjectHandler::ENTITY_OPERATION_ENTRY => [ - 0 => [ - OperationDefinitionObjectHandler::ENTITY_OPERATION_ENTRY_KEY => "id", - OperationDefinitionObjectHandler::ENTITY_OPERATION_ENTRY_VALUE => "integer" - ], ] - ]]]; - $this->setMockParserOutput($mockData); + ] + ]; + $this->mockOperationHandlerWithData($mockData); //Perform Assertions $operationDefinitionManager = OperationDefinitionObjectHandler::getInstance(); @@ -72,28 +93,93 @@ public function testGetMultipleObjects() $this->assertArrayHasKey($operationType2 . $dataType1, $operations); } - public function testObjectCreation() + /** + * Validate testDeprecatedOperation. + * + * @return void + * @throws Exception + */ + public function testDeprecatedOperation(): void { // Data Variables for Assertions - $testDataTypeName1 = "type1"; - $testAuth = "auth"; - $testUrl = "V1/dataType"; - $testOperationType = "create"; - $testMethod = "POST"; - $testSuccessRegex = "/messages-message-success/"; - $testContentType = "application/json"; - $testHeaderParam = "testParameter"; - $testHeaderValue = "testHeader"; + $dataType1 = 'type1'; + $operationType1 = 'create'; + + /** + * Parser Output. Just one metadata with 1 field + * operationName + * createType1 + * has field + * key=id, value=integer + */ + $mockData = [ + OperationDefinitionObjectHandler::ENTITY_OPERATION_ROOT_TAG => [ + 'testOperationName' => [ + OperationDefinitionObjectHandler::ENTITY_OPERATION_DATA_TYPE => $dataType1, + OperationDefinitionObjectHandler::ENTITY_OPERATION_TYPE => $operationType1, + OperationDefinitionObjectHandler::ENTITY_OPERATION_AUTH => 'auth', + OperationDefinitionObjectHandler::ENTITY_OPERATION_URL => 'V1/Type1', + OperationDefinitionObjectHandler::ENTITY_OPERATION_METHOD => 'POST', + OperationDefinitionObjectHandler::ENTITY_OPERATION_ENTRY => [ + 0 => [ + OperationDefinitionObjectHandler::ENTITY_OPERATION_ENTRY_KEY => 'id', + OperationDefinitionObjectHandler::ENTITY_OPERATION_ENTRY_VALUE => 'integer' + ], + ], + OperationDefinitionObjectHandler::OBJ_DEPRECATED => 'deprecation message' + ] + ] + ]; + $this->mockOperationHandlerWithData($mockData); + + //Perform Assertions + $operationDefinitionManager = OperationDefinitionObjectHandler::getInstance(); + $operations = $operationDefinitionManager->getAllObjects(); + + $this->assertArrayHasKey($operationType1 . $dataType1, $operations); + TestLoggingUtil::getInstance()->validateMockLogStatement( + 'notice', + 'NOTICE: 1 metadata operation name violations detected. See mftf.log for details.', + [] + ); + // test run time deprecation notice + $operation = $operationDefinitionManager->getOperationDefinition($operationType1, $dataType1); + $operation->logDeprecated(); + TestLoggingUtil::getInstance()->validateMockLogStatement( + 'warning', + 'DEPRECATION: The operation testOperationName is deprecated.', + ['operationType' => 'create', 'deprecatedMessage' => 'deprecation message'] + ); + } + + /** + * Validate testObjectCreation. + * + * @return void + * @throws Exception + */ + public function testObjectCreation(): void + { + // Data Variables for Assertions + $testDataTypeName1 = 'type1'; + $testAuth = 'auth'; + $testUrl = 'V1/dataType'; + $testOperationType = 'create'; + $testMethod = 'POST'; + $testSuccessRegex = '/messages-message-success/'; + $testContentType = 'application/json'; + $testHeaderParam = 'testParameter'; + $testHeaderValue = 'testHeader'; // Nested Object variables - $nestedObjectKey = "objectKey"; - $nestedObjectType = "objectType"; - $nestedEntryKey1 = "id"; - $nestedEntryValue1 = "integer"; - $nestedEntryKey2 = "name"; - $nestedEntryValue2 = "string"; - $nestedEntryRequired2 = "true"; - $nestedEntryKey3 = "active"; - $nestedEntryValue3 = "boolean"; + $nestedObjectKey = 'objectKey'; + $nestedObjectType = 'objectType'; + $nestedEntryKey1 = 'id'; + $nestedEntryValue1 = 'integer'; + $nestedEntryKey2 = 'name'; + $nestedEntryValue2 = 'string'; + $nestedEntryRequired2 = 'true'; + $nestedEntryKey3 = 'active'; + $nestedEntryValue3 = 'boolean'; /** * Complex Object @@ -111,56 +197,70 @@ public function testObjectCreation() * key active, value boolean * */ - $mockData = [OperationDefinitionObjectHandler::ENTITY_OPERATION_ROOT_TAG => [ - "testOperationName" => [ - OperationDefinitionObjectHandler::ENTITY_OPERATION_DATA_TYPE => $testDataTypeName1, - OperationDefinitionObjectHandler::ENTITY_OPERATION_TYPE => $testOperationType, - OperationDefinitionObjectHandler::ENTITY_OPERATION_AUTH => $testAuth, - OperationDefinitionObjectHandler::ENTITY_OPERATION_URL => $testUrl, - OperationDefinitionObjectHandler::ENTITY_OPERATION_METHOD => $testMethod, - OperationDefinitionObjectHandler::ENTITY_OPERATION_SUCCESS_REGEX => $testSuccessRegex, - OperationDefinitionObjectHandler::ENTITY_OPERATION_CONTENT_TYPE => [ - 0 => [ - "value" => $testContentType - ] - ], - OperationDefinitionObjectHandler::ENTITY_OPERATION_HEADER => [ - 0 => [ - OperationDefinitionObjectHandler::ENTITY_OPERATION_HEADER_PARAM => $testHeaderParam, - OperationDefinitionObjectHandler::ENTITY_OPERATION_HEADER_VALUE => $testHeaderValue, - ] - ], - OperationDefinitionObjectHandler::ENTITY_OPERATION_URL_PARAM => [ - 0 => [ - OperationDefinitionObjectHandler::ENTITY_OPERATION_URL_PARAM_KEY => "testUrlParamKey", - OperationDefinitionObjectHandler::ENTITY_OPERATION_URL_PARAM_VALUE => "testUrlParamValue" - ] - ], - OperationDefinitionObjectHandler::ENTITY_OPERATION_OBJECT => [ - 0 => [ - OperationDefinitionObjectHandler::ENTITY_OPERATION_OBJECT_KEY => $nestedObjectKey, - OperationDefinitionObjectHandler::ENTITY_OPERATION_DATA_TYPE => $nestedObjectType, - OperationDefinitionObjectHandler::ENTITY_OPERATION_ENTRY => [ - 0 => [ - OperationDefinitionObjectHandler::ENTITY_OPERATION_ENTRY_KEY => $nestedEntryKey1, - OperationDefinitionObjectHandler::ENTITY_OPERATION_ENTRY_VALUE => $nestedEntryValue1 - ], - 1 => [ - OperationDefinitionObjectHandler::ENTITY_OPERATION_ENTRY_KEY => $nestedEntryKey2, - OperationDefinitionObjectHandler::ENTITY_OPERATION_ENTRY_VALUE => $nestedEntryValue2, - OperationDefinitionObjectHandler::ENTITY_OPERATION_REQUIRED => $nestedEntryRequired2 - ], - 2 => [ - OperationDefinitionObjectHandler::ENTITY_OPERATION_ENTRY_KEY => $nestedEntryKey3, - OperationDefinitionObjectHandler::ENTITY_OPERATION_ENTRY_VALUE => $nestedEntryValue3 + $mockData = [ + OperationDefinitionObjectHandler::ENTITY_OPERATION_ROOT_TAG => [ + 'testOperationName' => [ + OperationDefinitionObjectHandler::ENTITY_OPERATION_DATA_TYPE => $testDataTypeName1, + OperationDefinitionObjectHandler::ENTITY_OPERATION_TYPE => $testOperationType, + OperationDefinitionObjectHandler::ENTITY_OPERATION_AUTH => $testAuth, + OperationDefinitionObjectHandler::ENTITY_OPERATION_URL => $testUrl, + OperationDefinitionObjectHandler::ENTITY_OPERATION_METHOD => $testMethod, + OperationDefinitionObjectHandler::ENTITY_OPERATION_SUCCESS_REGEX => $testSuccessRegex, + OperationDefinitionObjectHandler::ENTITY_OPERATION_CONTENT_TYPE => [ + 0 => [ + 'value' => $testContentType + ] + ], + OperationDefinitionObjectHandler::ENTITY_OPERATION_HEADER => [ + 0 => [ + OperationDefinitionObjectHandler::ENTITY_OPERATION_HEADER_PARAM => $testHeaderParam, + OperationDefinitionObjectHandler::ENTITY_OPERATION_HEADER_VALUE => $testHeaderValue, + ] + ], + OperationDefinitionObjectHandler::ENTITY_OPERATION_URL_PARAM => [ + 0 => [ + OperationDefinitionObjectHandler::ENTITY_OPERATION_URL_PARAM_KEY => 'testUrlParamKey', + OperationDefinitionObjectHandler::ENTITY_OPERATION_URL_PARAM_VALUE => 'testUrlParamValue' + ] + ], + OperationDefinitionObjectHandler::ENTITY_OPERATION_OBJECT => [ + 0 => [ + OperationDefinitionObjectHandler::ENTITY_OPERATION_OBJECT_KEY => $nestedObjectKey, + OperationDefinitionObjectHandler::ENTITY_OPERATION_DATA_TYPE => $nestedObjectType, + OperationDefinitionObjectHandler::ENTITY_OPERATION_ENTRY => [ + 0 => [ + OperationDefinitionObjectHandler::ENTITY_OPERATION_ENTRY_KEY => $nestedEntryKey1, + OperationDefinitionObjectHandler::ENTITY_OPERATION_ENTRY_VALUE => + $nestedEntryValue1 + ], + 1 => [ + OperationDefinitionObjectHandler::ENTITY_OPERATION_ENTRY_KEY => $nestedEntryKey2, + OperationDefinitionObjectHandler::ENTITY_OPERATION_ENTRY_VALUE => + $nestedEntryValue2, + OperationDefinitionObjectHandler::ENTITY_OPERATION_REQUIRED => + $nestedEntryRequired2 + ], + 2 => [ + OperationDefinitionObjectHandler::ENTITY_OPERATION_ENTRY_KEY => $nestedEntryKey3, + OperationDefinitionObjectHandler::ENTITY_OPERATION_ENTRY_VALUE => + $nestedEntryValue3 + ] ] ] - ] - ], - ]]]; + ], + ] + ] + ]; // Prepare objects to compare against $field = OperationDefinitionObjectHandler::ENTITY_OPERATION_ENTRY; - $expectedNestedField = new OperationElement($nestedEntryKey1, $nestedEntryValue1, $field, false, [], null); + $expectedNestedField = new OperationElement( + $nestedEntryKey1, + $nestedEntryValue1, + $field, + false, + [], + null + ); $expectedNestedField2 = new OperationElement( $nestedEntryKey2, $nestedEntryValue2, @@ -169,18 +269,29 @@ public function testObjectCreation() [], null ); - $expectedNestedField3 = new OperationElement($nestedEntryKey3, $nestedEntryValue3, $field, false, [], null); + $expectedNestedField3 = new OperationElement( + $nestedEntryKey3, + $nestedEntryValue3, + $field, + false, + [], + null + ); $expectedOperation = new OperationElement( $nestedObjectKey, $nestedObjectType, OperationDefinitionObjectHandler::ENTITY_OPERATION_OBJECT, false, [], - [0 => $expectedNestedField, 1 => $expectedNestedField2, 2 =>$expectedNestedField3] + [ + 0 => $expectedNestedField, + 1 => $expectedNestedField2, + 2 => $expectedNestedField3 + ] ); // Set up mocked data output - $this->setMockParserOutput($mockData); + $this->mockOperationHandlerWithData($mockData); // Get Operation $operationDefinitionManager = OperationDefinitionObjectHandler::getInstance(); @@ -204,16 +315,22 @@ public function testObjectCreation() $this->assertEquals($expectedOperation, $operation->getOperationMetadata()[0]); } - public function testObjectArrayCreation() + /** + * Validate testObjectArrayCreation. + * + * @return void + * @throws Exception + */ + public function testObjectArrayCreation(): void { // Data Variables for Assertions - $dataType1 = "type1"; - $operationType1 = "create"; - $objectArrayKey = "ObjectArray"; - $twiceNestedObjectKey = "nestedObjectKey"; - $twiceNestedObjectType = "nestedObjectType"; - $twiceNestedEntryKey = "nestedFieldKey"; - $twiceNestedEntryValue = "string"; + $dataType1 = 'type1'; + $operationType1 = 'create'; + $objectArrayKey = 'ObjectArray'; + $twiceNestedObjectKey = 'nestedObjectKey'; + $twiceNestedObjectType = 'nestedObjectType'; + $twiceNestedEntryKey = 'nestedFieldKey'; + $twiceNestedEntryValue = 'string'; // Parser Output /** * Metadata with nested array of objects, with a single field @@ -223,32 +340,38 @@ public function testObjectArrayCreation() * objects with key = nestedObjectKey, type = nestedObjectType * has field with key = nestedFieldKey, value = string */ - $mockData = [OperationDefinitionObjectHandler::ENTITY_OPERATION_ROOT_TAG => [ - "testOperationName" => [ - OperationDefinitionObjectHandler::ENTITY_OPERATION_DATA_TYPE => $dataType1, - OperationDefinitionObjectHandler::ENTITY_OPERATION_TYPE => $operationType1, - OperationDefinitionObjectHandler::ENTITY_OPERATION_AUTH => "auth", - OperationDefinitionObjectHandler::ENTITY_OPERATION_URL => "V1/Type1", - OperationDefinitionObjectHandler::ENTITY_OPERATION_METHOD => "POST", - OperationDefinitionObjectHandler::ENTITY_OPERATION_ARRAY => [ - 0 => [ - OperationDefinitionObjectHandler::ENTITY_OPERATION_OBJECT_KEY => $objectArrayKey, - OperationDefinitionObjectHandler::ENTITY_OPERATION_OBJECT => [ - 0 => [ - OperationDefinitionObjectHandler::ENTITY_OPERATION_OBJECT_KEY => $twiceNestedObjectKey, - OperationDefinitionObjectHandler::ENTITY_OPERATION_DATA_TYPE => $twiceNestedObjectType, - OperationDefinitionObjectHandler::ENTITY_OPERATION_ENTRY => [ - 0 => [ - OperationDefinitionObjectHandler::ENTITY_OPERATION_ENTRY_KEY => - $twiceNestedEntryKey, - OperationDefinitionObjectHandler::ENTITY_OPERATION_ENTRY_VALUE => - $twiceNestedEntryValue + $mockData = [ + OperationDefinitionObjectHandler::ENTITY_OPERATION_ROOT_TAG => [ + 'testOperationName' => [ + OperationDefinitionObjectHandler::ENTITY_OPERATION_DATA_TYPE => $dataType1, + OperationDefinitionObjectHandler::ENTITY_OPERATION_TYPE => $operationType1, + OperationDefinitionObjectHandler::ENTITY_OPERATION_AUTH => 'auth', + OperationDefinitionObjectHandler::ENTITY_OPERATION_URL => 'V1/Type1', + OperationDefinitionObjectHandler::ENTITY_OPERATION_METHOD => 'POST', + OperationDefinitionObjectHandler::ENTITY_OPERATION_ARRAY => [ + 0 => [ + OperationDefinitionObjectHandler::ENTITY_OPERATION_OBJECT_KEY => $objectArrayKey, + OperationDefinitionObjectHandler::ENTITY_OPERATION_OBJECT => [ + 0 => [ + OperationDefinitionObjectHandler::ENTITY_OPERATION_OBJECT_KEY => + $twiceNestedObjectKey, + OperationDefinitionObjectHandler::ENTITY_OPERATION_DATA_TYPE => + $twiceNestedObjectType, + OperationDefinitionObjectHandler::ENTITY_OPERATION_ENTRY => [ + 0 => [ + OperationDefinitionObjectHandler::ENTITY_OPERATION_ENTRY_KEY => + $twiceNestedEntryKey, + OperationDefinitionObjectHandler::ENTITY_OPERATION_ENTRY_VALUE => + $twiceNestedEntryValue + ] ] ] ] ] ] - ]]]]; + ] + ] + ]; // Prepare Objects to compare against $twoLevelNestedMetadata = new OperationElement( $twiceNestedEntryKey, @@ -265,7 +388,9 @@ public function testObjectArrayCreation() OperationDefinitionObjectHandler::ENTITY_OPERATION_OBJECT, false, [], - [0 => $twoLevelNestedMetadata] + [ + 0 => $twoLevelNestedMetadata + ] ); $expectedOperation = new OperationElement( @@ -273,12 +398,14 @@ public function testObjectArrayCreation() $twiceNestedObjectType, $twiceNestedObjectKey, false, - [$twiceNestedObjectKey => $oneLevelNestedMetadata], + [ + $twiceNestedObjectKey => $oneLevelNestedMetadata + ], null ); // Set up mocked data output - $this->setMockParserOutput($mockData); + $this->mockOperationHandlerWithData($mockData); // Get Operation $operationDefinitionManager = OperationDefinitionObjectHandler::getInstance(); @@ -288,15 +415,21 @@ public function testObjectArrayCreation() $this->assertEquals($expectedOperation, $operation->getOperationMetadata()[0]); } - public function testLooseJsonCreation() + /** + * Validate testLooseJsonCreation. + * + * @return void + * @throws Exception + */ + public function testLooseJsonCreation(): void { // Data Variables for Assertions - $dataType = "dataType"; - $operationType = "create"; - $entryKey = "id"; - $entryValue = "integer"; - $arrayKey = "arrayKey"; - $arrayValue = "string"; + $dataType = 'dataType'; + $operationType = 'create'; + $entryKey = 'id'; + $entryValue = 'integer'; + $arrayKey = 'arrayKey'; + $arrayValue = 'string'; /** * Operation with no objects, just an entry and an array of strings * testOperationName @@ -305,28 +438,30 @@ public function testLooseJsonCreation() * has array key = arrayKey * fields of value = string */ - $mockData = [OperationDefinitionObjectHandler::ENTITY_OPERATION_ROOT_TAG => [ - "testOperationName" => [ - OperationDefinitionObjectHandler::ENTITY_OPERATION_DATA_TYPE => $dataType, - OperationDefinitionObjectHandler::ENTITY_OPERATION_TYPE => $operationType, - OperationDefinitionObjectHandler::ENTITY_OPERATION_ENTRY => [ - 0 => [ - OperationDefinitionObjectHandler::ENTITY_OPERATION_ENTRY_KEY => $entryKey, - OperationDefinitionObjectHandler::ENTITY_OPERATION_ENTRY_VALUE => $entryValue - ] - ], - OperationDefinitionObjectHandler::ENTITY_OPERATION_ARRAY => [ - 0 => [ - OperationDefinitionObjectHandler::ENTITY_OPERATION_ARRAY_KEY => $arrayKey, - OperationDefinitionObjectHandler::ENTITY_OPERATION_ARRAY_VALUE => [ - 0 => [ - OperationDefinitionObjectHandler::ENTITY_OPERATION_ENTRY_VALUE => $arrayValue + $mockData = [ + OperationDefinitionObjectHandler::ENTITY_OPERATION_ROOT_TAG => [ + 'testOperationName' => [ + OperationDefinitionObjectHandler::ENTITY_OPERATION_DATA_TYPE => $dataType, + OperationDefinitionObjectHandler::ENTITY_OPERATION_TYPE => $operationType, + OperationDefinitionObjectHandler::ENTITY_OPERATION_ENTRY => [ + 0 => [ + OperationDefinitionObjectHandler::ENTITY_OPERATION_ENTRY_KEY => $entryKey, + OperationDefinitionObjectHandler::ENTITY_OPERATION_ENTRY_VALUE => $entryValue + ] + ], + OperationDefinitionObjectHandler::ENTITY_OPERATION_ARRAY => [ + 0 => [ + OperationDefinitionObjectHandler::ENTITY_OPERATION_ARRAY_KEY => $arrayKey, + OperationDefinitionObjectHandler::ENTITY_OPERATION_ARRAY_VALUE => [ + 0 => [ + OperationDefinitionObjectHandler::ENTITY_OPERATION_ENTRY_VALUE => $arrayValue + ] ] ] ] ] ] - ]]; + ]; // Prepare Objects to assert against $entry = new OperationElement( $entryKey, @@ -346,7 +481,7 @@ public function testLooseJsonCreation() ); // Set up mocked data output - $this->setMockParserOutput($mockData); + $this->mockOperationHandlerWithData($mockData); // get Operations $operationDefinitionManager = OperationDefinitionObjectHandler::getInstance(); @@ -358,25 +493,71 @@ public function testLooseJsonCreation() } /** - * Function used to set mock for parser return and force init method to run between tests. + * Create mock operation handler with data. * - * @param array $data + * @param array $mockData + * + * @return void */ - private function setMockParserOutput($data) + private function mockOperationHandlerWithData(array $mockData): void { - // clear Operation object handler value to inject parsed content - $property = new \ReflectionProperty( + $operationDefinitionObjectHandlerProperty = new ReflectionProperty( OperationDefinitionObjectHandler::class, 'INSTANCE' ); + $operationDefinitionObjectHandlerProperty->setAccessible(true); + $operationDefinitionObjectHandlerProperty->setValue(null); + + $mockOperationParser = $this->createMock(OperationDefinitionParser::class); + $mockOperationParser + ->method('readOperationMetadata') + ->willReturn($mockData); + + $objectManager = ObjectManagerFactory::getObjectManager(); + $mockObjectManagerInstance = $this->createMock(ObjectManager::class); + $mockObjectManagerInstance + ->method('create') + ->will( + $this->returnCallback( + function ( + string $class, + array $arguments = [] + ) use ( + $objectManager, + $mockOperationParser + ) { + if ($class === OperationDefinitionParser::class) { + return $mockOperationParser; + } + + return $objectManager->create($class, $arguments); + } + ) + ); + + $property = new ReflectionProperty(ObjectManager::class, 'instance'); $property->setAccessible(true); - $property->setValue(null); - - $mockOperationParser = AspectMock::double( - OperationDefinitionParser::class, - ["readOperationMetadata" => $data] - )->make(); - $instance = AspectMock::double(ObjectManager::class, ['create' => $mockOperationParser])->make(); - AspectMock::double(ObjectManagerFactory::class, ['getObjectManager' => $instance]); + $property->setValue($mockObjectManagerInstance); + } + + /** + * @inheritDoc + */ + public static function tearDownAfterClass(): void + { + parent::tearDownAfterClass(); + + $operationDefinitionObjectHandlerProperty = new ReflectionProperty( + OperationDefinitionObjectHandler::class, + 'INSTANCE' + ); + $operationDefinitionObjectHandlerProperty->setAccessible(true); + $operationDefinitionObjectHandlerProperty->setValue(null); + + $objectManagerProperty = new ReflectionProperty(ObjectManager::class, 'instance'); + $objectManagerProperty->setAccessible(true); + $objectManagerProperty->setValue(null); + + TestLoggingUtil::getInstance()->clearMockLoggingUtil(); } } diff --git a/dev/tests/unit/Magento/FunctionalTestFramework/DataGenerator/Handlers/PersistedObjectHandlerTest.php b/dev/tests/unit/Magento/FunctionalTestFramework/DataGenerator/Handlers/PersistedObjectHandlerTest.php index 6c3823466..f4b798ec5 100644 --- a/dev/tests/unit/Magento/FunctionalTestFramework/DataGenerator/Handlers/PersistedObjectHandlerTest.php +++ b/dev/tests/unit/Magento/FunctionalTestFramework/DataGenerator/Handlers/PersistedObjectHandlerTest.php @@ -3,10 +3,11 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); namespace tests\unit\Magento\FunctionalTestFramework\DataGenerator\Handlers; -use AspectMock\Test as AspectMock; +use Exception; use Magento\FunctionalTestingFramework\DataGenerator\Handlers\DataObjectHandler; use Magento\FunctionalTestingFramework\DataGenerator\Handlers\PersistedObjectHandler; use Magento\FunctionalTestingFramework\DataGenerator\Parsers\DataProfileSchemaParser; @@ -15,7 +16,8 @@ use Magento\FunctionalTestingFramework\Exceptions\TestReferenceException; use Magento\FunctionalTestingFramework\ObjectManager; use Magento\FunctionalTestingFramework\ObjectManagerFactory; -use Magento\FunctionalTestingFramework\Util\MagentoTestCase; +use ReflectionProperty; +use tests\unit\Util\MagentoTestCase; use tests\unit\Util\TestLoggingUtil; /** @@ -24,21 +26,54 @@ class PersistedObjectHandlerTest extends MagentoTestCase { /** - * Before test functionality - * @return void + * @inheritDoc */ - public function setUp() + protected function setUp(): void { TestLoggingUtil::getInstance()->setMockLoggingUtil(); } - public function testCreateSimpleEntity() + /** + * Validate testCreateEntityWithNonExistingName. + * + * @return void + * @throws TestReferenceException + */ + public function testCreateEntityWithNonExistingName(): void { // Test Data and Variables - $entityName = "EntityOne"; - $entityStepKey = "StepKey"; - $dataKey = "testKey"; - $dataValue = "testValue"; + $entityName = 'InvalidEntity'; + $entityStepKey = 'StepKey'; + $scope = PersistedObjectHandler::TEST_SCOPE; + + $exceptionMessage = "Entity \"" . $entityName . "\" does not exist." . + "\nException occurred executing action at StepKey \"" . $entityStepKey . "\""; + + $this->expectException(TestReferenceException::class); + $this->expectExceptionMessage($exceptionMessage); + $handler = PersistedObjectHandler::getInstance(); + + // Call method + $handler->createEntity( + $entityStepKey, + $scope, + $entityName + ); + } + + /** + * Validate testCreateSimpleEntity. + * + * @return void + * @throws TestReferenceException + */ + public function testCreateSimpleEntity(): void + { + // Test Data and Variables + $entityName = 'EntityOne'; + $entityStepKey = 'StepKey'; + $dataKey = 'testKey'; + $dataValue = 'testValue'; $scope = PersistedObjectHandler::TEST_SCOPE; $parserOutput = [ 'entity' => [ @@ -53,15 +88,13 @@ public function testCreateSimpleEntity() ] ] ]; - $jsonResponse = " + $jsonResponse = " { \"" . strtolower($dataKey) . "\" : \"{$dataValue}\" } "; - // Mock Classes - $this->mockDataHandlerWithOutput($parserOutput); - $this->mockCurlHandler($jsonResponse); + $this->mockCurlHandler($jsonResponse, $parserOutput); $handler = PersistedObjectHandler::getInstance(); // Call method @@ -75,13 +108,19 @@ public function testCreateSimpleEntity() $this->assertEquals($dataValue, $persistedValue); } - public function testDeleteSimpleEntity() + /** + * Validate testDeleteSimpleEntity. + * + * @return void + * @throws TestReferenceException + */ + public function testDeleteSimpleEntity(): void { // Test Data and Variables - $entityName = "EntityOne"; - $entityStepKey = "StepKey"; - $dataKey = "testKey"; - $dataValue = "testValue"; + $entityName = 'EntityOne'; + $entityStepKey = 'StepKey'; + $dataKey = 'testKey'; + $dataValue = 'testValue'; $scope = PersistedObjectHandler::TEST_SCOPE; $parserOutput = [ 'entity' => [ @@ -96,15 +135,14 @@ public function testDeleteSimpleEntity() ] ] ]; - $jsonResponse = " + $jsonResponse = " { \"" . strtolower($dataKey) . "\" : \"{$dataValue}\" } "; // Mock Classes - $this->mockDataHandlerWithOutput($parserOutput); - $this->mockCurlHandler($jsonResponse); + $this->mockCurlHandler($jsonResponse, $parserOutput); $handler = PersistedObjectHandler::getInstance(); // Call method @@ -123,13 +161,19 @@ public function testDeleteSimpleEntity() $this->addToAssertionCount(1); } - public function testGetSimpleEntity() + /** + * Validate testGetSimpleEntity. + * + * @return void + * @throws Exception + */ + public function testGetSimpleEntity(): void { // Test Data and Variables - $entityName = "EntityOne"; - $entityStepKey = "StepKey"; - $dataKey = "testKey"; - $dataValue = "testValue"; + $entityName = 'EntityOne'; + $entityStepKey = 'StepKey'; + $dataKey = 'testKey'; + $dataValue = 'testValue'; $scope = PersistedObjectHandler::TEST_SCOPE; $parserOutput = [ 'entity' => [ @@ -144,15 +188,14 @@ public function testGetSimpleEntity() ] ] ]; - $jsonResponse = " + $jsonResponse = " { \"" . strtolower($dataKey) . "\" : \"{$dataValue}\" } "; // Mock Classes - $this->mockDataHandlerWithOutput($parserOutput); - $this->mockCurlHandler($jsonResponse); + $this->mockCurlHandler($jsonResponse, $parserOutput); $handler = PersistedObjectHandler::getInstance(); // Call method @@ -166,16 +209,22 @@ public function testGetSimpleEntity() $this->assertEquals($dataValue, $persistedValue); } - public function testUpdateSimpleEntity() + /** + * Validate testUpdateSimpleEntity. + * + * @return void + * @throws TestReferenceException + */ + public function testUpdateSimpleEntity(): void { - $this->markTestSkipped("Potential Bug in DataPersistenceHandler class"); + $this->markTestSkipped('Potential Bug in DataPersistenceHandler class'); // Test Data and Variables - $entityName = "EntityOne"; - $entityStepKey = "StepKey"; - $dataKey = "testKey"; - $dataValue = "testValue"; - $updateName = "EntityTwo"; - $updateValue = "newValue"; + $entityName = 'EntityOne'; + $entityStepKey = 'StepKey'; + $dataKey = 'testKey'; + $dataValue = 'testValue'; + $updateName = 'EntityTwo'; + $updateValue = 'newValue'; $scope = PersistedObjectHandler::TEST_SCOPE; $parserOutput = [ 'entity' => [ @@ -199,7 +248,7 @@ public function testUpdateSimpleEntity() ] ] ]; - $jsonResponse = " + $jsonResponse = " { \"" . strtolower($dataKey) . "\" : \"{$dataValue}\" } @@ -211,15 +260,14 @@ public function testUpdateSimpleEntity() "; // Mock Classes - $this->mockDataHandlerWithOutput($parserOutput); - $this->mockCurlHandler($jsonResponse); + $this->mockCurlHandler($jsonResponse, $parserOutput); $handler = PersistedObjectHandler::getInstance(); $handler->createEntity( $entityStepKey, $scope, $entityName ); - $this->mockCurlHandler($updatedResponse); + $this->mockCurlHandler($updatedResponse, $parserOutput); // Call method $handler->updateEntity( @@ -232,21 +280,27 @@ public function testUpdateSimpleEntity() $this->assertEquals($updateValue, $persistedValue); } - public function testRetrieveEntityAcrossScopes() + /** + * Validate testRetrieveEntityAcrossScopes. + * + * @return void + * @throws TestReferenceException + */ + public function testRetrieveEntityAcrossScopes(): void { // Test Data and Variables - $entityNameOne = "EntityOne"; - $entityStepKeyOne = "StepKeyOne"; - $dataKeyOne = "testKeyOne"; - $dataValueOne = "testValueOne"; - $entityNameTwo = "EntityTwo"; - $entityStepKeyTwo = "StepKeyTwo"; - $dataKeyTwo = "testKeyTwo"; - $dataValueTwo = "testValueTwo"; - $entityNameThree = "EntityThree"; - $entityStepKeyThree = "StepKeyThree"; - $dataKeyThree = "testKeyThree"; - $dataValueThree = "testValueThree"; + $entityNameOne = 'EntityOne'; + $entityStepKeyOne = 'StepKeyOne'; + $dataKeyOne = 'testKeyOne'; + $dataValueOne = 'testValueOne'; + $entityNameTwo = 'EntityTwo'; + $entityStepKeyTwo = 'StepKeyTwo'; + $dataKeyTwo = 'testKeyTwo'; + $dataValueTwo = 'testValueTwo'; + $entityNameThree = 'EntityThree'; + $entityStepKeyThree = 'StepKeyThree'; + $dataKeyThree = 'testKeyThree'; + $dataValueThree = 'testValueThree'; $parserOutputOne = [ 'entity' => [ @@ -279,7 +333,7 @@ public function testRetrieveEntityAcrossScopes() ] ] ]; - $jsonReponseOne = " + $jsonResponseOne = " { \"" . strtolower($dataKeyOne) . "\" : \"{$dataValueOne}\" } @@ -298,22 +352,21 @@ public function testRetrieveEntityAcrossScopes() // Mock Classes and Create Entities $handler = PersistedObjectHandler::getInstance(); - $this->mockDataHandlerWithOutput($parserOutputOne); - $this->mockCurlHandler($jsonReponseOne); + $this->mockCurlHandler($jsonResponseOne, $parserOutputOne); $handler->createEntity( $entityStepKeyOne, PersistedObjectHandler::TEST_SCOPE, $entityNameOne ); - $this->mockCurlHandler($jsonReponseTwo); + $this->mockCurlHandler($jsonReponseTwo, $parserOutputOne); $handler->createEntity( $entityStepKeyTwo, PersistedObjectHandler::HOOK_SCOPE, $entityNameTwo ); - $this->mockCurlHandler($jsonReponseThree); + $this->mockCurlHandler($jsonReponseThree, $parserOutputOne); $handler->createEntity( $entityStepKeyThree, PersistedObjectHandler::SUITE_SCOPE, @@ -343,6 +396,8 @@ public function testRetrieveEntityAcrossScopes() } /** + * Validate testRetrieveEntityValidField. + * * @param string $name * @param string $key * @param string $value @@ -350,9 +405,18 @@ public function testRetrieveEntityAcrossScopes() * @param string $scope * @param string $stepKey * @dataProvider entityDataProvider + * + * @return void + * @throws TestReferenceException */ - public function testRetrieveEntityValidField($name, $key, $value, $type, $scope, $stepKey) - { + public function testRetrieveEntityValidField( + string $name, + string $key, + string $value, + string $type, + string $scope, + string $stepKey + ): void { $parserOutputOne = [ 'entity' => [ $name => [ @@ -366,7 +430,7 @@ public function testRetrieveEntityValidField($name, $key, $value, $type, $scope, ] ] ]; - $jsonReponseOne = " + $jsonResponseOne = " { \"" . strtolower($key) . "\" : \"{$value}\" } @@ -374,9 +438,7 @@ public function testRetrieveEntityValidField($name, $key, $value, $type, $scope, // Mock Classes and Create Entities $handler = PersistedObjectHandler::getInstance(); - - $this->mockDataHandlerWithOutput($parserOutputOne); - $this->mockCurlHandler($jsonReponseOne); + $this->mockCurlHandler($jsonResponseOne, $parserOutputOne); $handler->createEntity($stepKey, $scope, $name); // Call method @@ -386,6 +448,8 @@ public function testRetrieveEntityValidField($name, $key, $value, $type, $scope, } /** + * Validate testRetrieveEntityInValidField. + * * @param string $name * @param string $key * @param string $value @@ -393,14 +457,21 @@ public function testRetrieveEntityValidField($name, $key, $value, $type, $scope, * @param string $scope * @param string $stepKey * @dataProvider entityDataProvider - * @throws TestReferenceException - * @throws TestFrameworkException + * + * @return void + * @throws TestReferenceException|TestFrameworkException */ - public function testRetrieveEntityInValidField($name, $key, $value, $type, $scope, $stepKey) - { - $invalidDataKey = "invalidDataKey"; + public function testRetrieveEntityInValidField( + string $name, + string $key, + string $value, + string $type, + string $scope, + string $stepKey + ): void { + $invalidDataKey = 'invalidDataKey'; $warnMsg = "Undefined field {$invalidDataKey} in entity object with a stepKey of {$stepKey}\n"; - $warnMsg .= "Please fix the invalid reference. This will result in fatal error in next major release."; + $warnMsg .= 'Please fix the invalid reference. This will result in fatal error in next major release.'; $parserOutputOne = [ 'entity' => [ @@ -415,7 +486,7 @@ public function testRetrieveEntityInValidField($name, $key, $value, $type, $scop ] ] ]; - $jsonReponseOne = " + $jsonResponseOne = " { \"" . strtolower($key) . "\" : \"{$value}\" } @@ -423,9 +494,7 @@ public function testRetrieveEntityInValidField($name, $key, $value, $type, $scop // Mock Classes and Create Entities $handler = PersistedObjectHandler::getInstance(); - - $this->mockDataHandlerWithOutput($parserOutputOne); - $this->mockCurlHandler($jsonReponseOne); + $this->mockCurlHandler($jsonResponseOne, $parserOutputOne); $handler->createEntity($stepKey, $scope, $name); // Call method @@ -440,9 +509,11 @@ public function testRetrieveEntityInValidField($name, $key, $value, $type, $scop } /** - * Data provider for testRetrieveEntityField + * Data provider for testRetrieveEntityField. + * + * @return array */ - public static function entityDataProvider() + public static function entityDataProvider(): array { return [ ['Entity1', 'testKey1', 'testValue1', 'testType', PersistedObjectHandler::HOOK_SCOPE, 'StepKey1'], @@ -452,57 +523,78 @@ public static function entityDataProvider() } /** - * Mocks DataObjectHandler to use given output to create - * @param $parserOutput - * @throws \Exception + * Create mock curl handler. + * + * @param string $response + * @param array $parserOutput + * + * @return void */ - public function mockDataHandlerWithOutput($parserOutput) + public function mockCurlHandler(string $response, array $parserOutput): void { - // Clear DataObjectHandler singleton if already set - $property = new \ReflectionProperty(DataObjectHandler::class, "INSTANCE"); - $property->setAccessible(true); - $property->setValue(null); - - $mockDataProfileSchemaParser = AspectMock::double(DataProfileSchemaParser::class, [ - 'readDataProfiles' => $parserOutput - ])->make(); - - $mockObjectManager = AspectMock::double(ObjectManager::class, [ - 'create' => $mockDataProfileSchemaParser - ])->make(); - - AspectMock::double(ObjectManagerFactory::class, [ - 'getObjectManager' => $mockObjectManager - ]); + $dataObjectHandler = new ReflectionProperty(DataObjectHandler::class, 'INSTANCE'); + $dataObjectHandler->setAccessible(true); + $dataObjectHandler->setValue(null); + + $dataProfileSchemaParser = $this->createMock(DataProfileSchemaParser::class); + $dataProfileSchemaParser + ->method('readDataProfiles') + ->willReturn($parserOutput); + + $curlHandler = $this->createMock(CurlHandler::class); + $curlHandler + ->method('executeRequest') + ->willReturn($response); + $curlHandler + ->method('getRequestDataArray') + ->willReturn([]); + $curlHandler + ->method('isContentTypeJson') + ->willReturn(true); + + $objectManager = ObjectManagerFactory::getObjectManager(); + $objectManagerMockInstance = $this->createMock(ObjectManager::class); + $objectManagerMockInstance->expects($this->any()) + ->method('create') + ->will( + $this->returnCallback( + function ($class, $arguments = []) use ($curlHandler, $objectManager, $dataProfileSchemaParser) { + if ($class === CurlHandler::class) { + return $curlHandler; + } + + if ($class === DataProfileSchemaParser::class) { + return $dataProfileSchemaParser; + } + + return $objectManager->create($class, $arguments); + } + ) + ); + + $objectManagerProperty = new ReflectionProperty(ObjectManager::class, 'instance'); + $objectManagerProperty->setAccessible(true); + $objectManagerProperty->setValue($objectManagerMockInstance); } - public function mockCurlHandler($response) + /** + * After class functionality. + * + * @return void + */ + public static function tearDownAfterClass(): void { - AspectMock::double(CurlHandler::class, [ - "__construct" => null, - "executeRequest" => $response, - "getRequestDataArray" => [], - "isContentTypeJson" => true - ]); - } + parent::tearDownAfterClass(); - public function tearDown() - { // Clear out Singleton between tests - $property = new \ReflectionProperty(PersistedObjectHandler::class, "INSTANCE"); - $property->setAccessible(true); - $property->setValue(null); + $persistedObjectHandlerProperty = new ReflectionProperty(PersistedObjectHandler::class, "INSTANCE"); + $persistedObjectHandlerProperty->setAccessible(true); + $persistedObjectHandlerProperty->setValue(null); - parent::tearDown(); // TODO: Change the autogenerated stub - } + $objectManagerProperty = new ReflectionProperty(ObjectManager::class, 'instance'); + $objectManagerProperty->setAccessible(true); + $objectManagerProperty->setValue(null); - /** - * After class functionality - * @return void - */ - public static function tearDownAfterClass() - { TestLoggingUtil::getInstance()->clearMockLoggingUtil(); - parent::tearDownAfterClass(); } } diff --git a/dev/tests/unit/Magento/FunctionalTestFramework/DataGenerator/Handlers/SecretStorage/AwsSecretsManagerStorageTest.php b/dev/tests/unit/Magento/FunctionalTestFramework/DataGenerator/Handlers/SecretStorage/AwsSecretsManagerStorageTest.php new file mode 100644 index 000000000..3112529c2 --- /dev/null +++ b/dev/tests/unit/Magento/FunctionalTestFramework/DataGenerator/Handlers/SecretStorage/AwsSecretsManagerStorageTest.php @@ -0,0 +1,65 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace tests\unit\Magento\FunctionalTestFramework\DataGenerator\Handlers\SecretStorage; + +use Aws\SecretsManager\SecretsManagerClient; +use Magento\FunctionalTestingFramework\DataGenerator\Handlers\SecretStorage\AwsSecretsManagerStorage; +use Aws\Result; +use tests\unit\Util\MagentoTestCase; +use ReflectionClass; + +class AwsSecretsManagerStorageTest extends MagentoTestCase +{ + /** + * Test encryption/decryption functionality in AwsSecretsManagerStorage class. + */ + public function testEncryptAndDecrypt() + { + // Setup test data + $testProfile = 'profile'; + $testRegion = 'region'; + $testLongKey = 'magento/myKey'; + $testShortKey = 'myKey'; + $testValue = 'myValue'; + $data = [ + 'Name' => 'mftf/magento/' . $testShortKey, + 'SecretString' => json_encode([$testShortKey => $testValue]) + ]; + /** @var Result */ + $result = new Result($data); + + $mockClient = $this->getMockBuilder(SecretsManagerClient::class) + ->disableOriginalConstructor() + ->setMethods(['__call']) + ->getMock(); + + $mockClient->expects($this->once()) + ->method('__call') + ->willReturnCallback(function ($name, $args) use ($result) { + return $result; + }); + + /** @var SecretsManagerClient */ + $credentialStorage = new AwsSecretsManagerStorage($testRegion, $testProfile); + $reflection = new ReflectionClass($credentialStorage); + $reflection_property = $reflection->getProperty('client'); + $reflection_property->setAccessible(true); + $reflection_property->setValue($credentialStorage, $mockClient); + + // Test getEncryptedValue() + $encryptedCred = $credentialStorage->getEncryptedValue($testLongKey); + + // Assert the value we've gotten is in fact not identical to our test value + $this->assertNotEquals($testValue, $encryptedCred); + + // Test getDecryptedValue() + $actualValue = $credentialStorage->getDecryptedValue($encryptedCred); + + // Assert that we are able to successfully decrypt our secret value + $this->assertEquals($testValue, $actualValue); + } +} diff --git a/dev/tests/unit/Magento/FunctionalTestFramework/DataGenerator/Handlers/SecretStorage/FileStorageTest.php b/dev/tests/unit/Magento/FunctionalTestFramework/DataGenerator/Handlers/SecretStorage/FileStorageTest.php index 7e5824c8e..3d5922f9e 100644 --- a/dev/tests/unit/Magento/FunctionalTestFramework/DataGenerator/Handlers/SecretStorage/FileStorageTest.php +++ b/dev/tests/unit/Magento/FunctionalTestFramework/DataGenerator/Handlers/SecretStorage/FileStorageTest.php @@ -3,29 +3,41 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); namespace tests\unit\Magento\FunctionalTestFramework\DataGenerator\Handlers\SecretStorage; use Magento\FunctionalTestingFramework\DataGenerator\Handlers\SecretStorage\FileStorage; -use Magento\FunctionalTestingFramework\Util\MagentoTestCase; -use AspectMock\Test as AspectMock; +use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; +use ReflectionClass; +use ReflectionException; +use tests\unit\Util\MagentoTestCase; class FileStorageTest extends MagentoTestCase { - /** * Test basic encryption/decryption functionality in FileStorage class. + * @throws TestFrameworkException|ReflectionException */ - public function testBasicEncryptDecrypt() + public function testBasicEncryptDecrypt(): void { $testKey = 'magento/myKey'; $testValue = 'myValue'; - - AspectMock::double(FileStorage::class, [ - 'readInCredentialsFile' => ["$testKey=$testValue"] - ]); + $cred = ["$testKey=$testValue"]; $fileStorage = new FileStorage(); + $reflection = new ReflectionClass(FileStorage::class); + + // Emulate initialize() function result with the test credentials + $reflectionMethod = $reflection->getMethod('encryptCredFileContents'); + $reflectionMethod->setAccessible(true); + $secretData = $reflectionMethod->invokeArgs($fileStorage, [$cred]); + + // Set encrypted test credentials to the private 'secretData' property + $reflectionProperty = $reflection->getProperty('secretData'); + $reflectionProperty->setAccessible(true); + $reflectionProperty->setValue($fileStorage, $secretData); + $encryptedCred = $fileStorage->getEncryptedValue($testKey); // assert the value we've gotten is in fact not identical to our test value @@ -36,4 +48,32 @@ public function testBasicEncryptDecrypt() // assert that we are able to successfully decrypt our secret value $this->assertEquals($testValue, $actualValue); } + + /** + * Test empty value encryption/decryption functionality in FileStorage class. + * @return void + * @throws TestFrameworkException|ReflectionException + */ + public function testEmptyValueEncryptDecrypt(): void + { + $this->expectException(TestFrameworkException::class); + + $testKey = 'magento/myKey'; + $cred = ["$testKey"]; + + $fileStorage = new FileStorage(); + $reflection = new ReflectionClass(FileStorage::class); + + // Emulate initialize() function result with the test credentials + $reflectionMethod = $reflection->getMethod('encryptCredFileContents'); + $reflectionMethod->setAccessible(true); + $secretData = $reflectionMethod->invokeArgs($fileStorage, [$cred]); + + // Set encrypted test credentials to the private 'secretData' property + $reflectionProperty = $reflection->getProperty('secretData'); + $reflectionProperty->setAccessible(true); + $reflectionProperty->setValue($fileStorage, $secretData); + + $fileStorage->getEncryptedValue($testKey); + } } diff --git a/dev/tests/unit/Magento/FunctionalTestFramework/DataGenerator/Objects/EntityDataObjectTest.php b/dev/tests/unit/Magento/FunctionalTestFramework/DataGenerator/Objects/EntityDataObjectTest.php index 802a6c108..3fc2aaacb 100644 --- a/dev/tests/unit/Magento/FunctionalTestFramework/DataGenerator/Objects/EntityDataObjectTest.php +++ b/dev/tests/unit/Magento/FunctionalTestFramework/DataGenerator/Objects/EntityDataObjectTest.php @@ -6,7 +6,7 @@ namespace Magento\FunctionalTestingFramework\DataGenerator\Objects; -use Magento\FunctionalTestingFramework\Util\MagentoTestCase; +use tests\unit\Util\MagentoTestCase; use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; use tests\unit\Util\TestLoggingUtil; @@ -40,7 +40,7 @@ class EntityDataObjectTest extends MagentoTestCase * Before test functionality * @return void */ - public function setUp() + public function setUp(): void { TestLoggingUtil::getInstance()->setMockLoggingUtil(); } @@ -141,7 +141,7 @@ public function testGetCamelCaseKeys() * After class functionality * @return void */ - public static function tearDownAfterClass() + public static function tearDownAfterClass(): void { TestLoggingUtil::getInstance()->clearMockLoggingUtil(); } diff --git a/dev/tests/unit/Magento/FunctionalTestFramework/DataGenerator/Persist/OperationDataArrayResolverTest.php b/dev/tests/unit/Magento/FunctionalTestFramework/DataGenerator/Persist/OperationDataArrayResolverTest.php index fb6dcd865..ce1911fd0 100644 --- a/dev/tests/unit/Magento/FunctionalTestFramework/DataGenerator/Persist/OperationDataArrayResolverTest.php +++ b/dev/tests/unit/Magento/FunctionalTestFramework/DataGenerator/Persist/OperationDataArrayResolverTest.php @@ -3,14 +3,16 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace tests\unit\Magento\FunctionalTestFramework\DataGenerator\Persist; -use AspectMock\Test as AspectMock; +use Exception; use Magento\FunctionalTestingFramework\DataGenerator\Handlers\DataObjectHandler; use Magento\FunctionalTestingFramework\DataGenerator\Handlers\OperationDefinitionObjectHandler; use Magento\FunctionalTestingFramework\DataGenerator\Persist\OperationDataArrayResolver; -use Magento\FunctionalTestingFramework\Util\Iterator\AbstractIterator; -use Magento\FunctionalTestingFramework\Util\MagentoTestCase; +use ReflectionProperty; +use tests\unit\Util\MagentoTestCase; use tests\unit\Util\EntityDataObjectBuilder; use tests\unit\Util\OperationDefinitionBuilder; use tests\unit\Util\OperationElementBuilder; @@ -18,30 +20,31 @@ class OperationDataArrayResolverTest extends MagentoTestCase { - const NESTED_METADATA_EXPECTED_RESULT = ["parentType" => [ - "name" => "Hopper", - "address" => ["city" => "Hawkins", "state" => "Indiana", "zip" => 78758], - "isPrimary" => true, - "gpa" => 3.5678, - "phone" => 5555555 + const NESTED_METADATA_EXPECTED_RESULT = ['parentType' => [ + 'name' => 'Hopper', + 'address' => ['city' => 'Hawkins', 'state' => 'Indiana', 'zip' => 78758], + 'isPrimary' => true, + 'gpa' => 3.5678, + 'phone' => 5555555 ]]; - const NESTED_METADATA_ARRAY_RESULT = ["parentType" => [ - "name" => "Hopper", - "isPrimary" => true, - "gpa" => 3.5678, - "phone" => 5555555, - "address" => [ - ["city" => "Hawkins", "state" => "Indiana", "zip" => 78758], - ["city" => "Austin", "state" => "Texas", "zip" => 78701], + const NESTED_METADATA_ARRAY_RESULT = ['parentType' => [ + 'name' => 'Hopper', + 'isPrimary' => true, + 'gpa' => 3.5678, + 'phone' => 5555555, + 'address' => [ + ['city' => 'Hawkins', 'state' => 'Indiana', 'zip' => 78758], + ['city' => 'Austin', 'state' => 'Texas', 'zip' => 78701], ] ]]; /** * Before test functionality + * * @return void */ - public function setUp() + public function setUp(): void { TestLoggingUtil::getInstance()->setMockLoggingUtil(); } @@ -54,8 +57,11 @@ public function setUp() * <field>boolField</field> * <field>doubleField</field> * </object> + * + * @return void + * @throws Exception */ - public function testBasicPrimitiveMetadataResolve() + public function testBasicPrimitiveMetadataResolve(): void { // set up data object $entityObjectBuilder = new EntityDataObjectBuilder(); @@ -74,11 +80,11 @@ public function testBasicPrimitiveMetadataResolve() ); // assert on result - $expectedResult = ["testType" => [ - "name" => "Hopper", - "gpa" => 3.5678, - "phone" => 5555555, - "isPrimary" => true + $expectedResult = ['testType' => [ + 'name' => 'Hopper', + 'gpa' => 3.5678, + 'phone' => 5555555, + 'isPrimary' => true ]]; $this->assertEquals($expectedResult, $result); @@ -90,56 +96,54 @@ public function testBasicPrimitiveMetadataResolve() * <field>someField</field> * <field>objectRef</field> * </object> + * + * @return void + * @throws Exception */ - public function testNestedMetadataResolve() + public function testNestedMetadataResolve(): void { // set up data objects $entityDataObjBuilder = new EntityDataObjectBuilder(); $parentDataObject = $entityDataObjBuilder - ->withName("parentObject") - ->withType("parentType") + ->withName('parentObject') + ->withType('parentType') ->withLinkedEntities(['childObject' => 'childType']) ->build(); $childDataObject = $entityDataObjBuilder - ->withName("childObject") - ->withType("childType") - ->withDataFields(["city" => "Hawkins", "state" => "Indiana", "zip" => "78758"]) + ->withName('childObject') + ->withType('childType') + ->withDataFields(['city' => 'Hawkins', 'state' => 'Indiana', 'zip' => '78758']) ->build(); // mock data object handler - $mockDOHInstance = AspectMock::double(DataObjectHandler::class, ['getObject' => $childDataObject])->make(); - AspectMock::double(DataObjectHandler::class, ['getInstance' => $mockDOHInstance]); + $this->mockDataObjectHandler($childDataObject); // set up metadata objects $parentOpElementBuilder = new OperationElementBuilder(); $parentElement = $parentOpElementBuilder - ->withKey("parentType") - ->withType("parentType") - ->addFields(["address" => "childType"]) + ->withKey('parentType') + ->withType('parentType') + ->addFields(['address' => 'childType']) ->build(); $operationDefinitionBuilder = new OperationDefinitionBuilder(); $childOperationDefinition = $operationDefinitionBuilder - ->withName("createChildType") - ->withOperation("create") - ->withType("childType") + ->withName('createChildType') + ->withOperation('create') + ->withType('childType') ->withMetadata([ - "city" => "string", - "state" => "string", - "zip" => "integer" + 'city' => 'string', + 'state' => 'string', + 'zip' => 'integer' ])->build(); // mock meta data object handler - $mockDOHInstance = AspectMock::double( - OperationDefinitionObjectHandler::class, - ['getObject' => $childOperationDefinition] - )->make(); - AspectMock::double(OperationDefinitionObjectHandler::class, ['getInstance' => $mockDOHInstance]); + $this->mockOperationDefinitionObjectHandler($childOperationDefinition); // resolve data object and metadata array $operationResolver = new OperationDataArrayResolver(); - $result = $operationResolver->resolveOperationDataArray($parentDataObject, [$parentElement], "create", false); + $result = $operationResolver->resolveOperationDataArray($parentDataObject, [$parentElement], 'create', false); // assert on the result $this->assertEquals(self::NESTED_METADATA_EXPECTED_RESULT, $result); @@ -153,45 +157,47 @@ public function testNestedMetadataResolve() * <field>anotherField</field> * </object> * </object> + * + * @return void + * @throws Exception */ - public function testNestedMetadata() + public function testNestedMetadata(): void { // set up data objects $entityDataObjectBuilder = new EntityDataObjectBuilder(); $parentDataObject = $entityDataObjectBuilder - ->withName("parentObject") - ->withType("parentType") + ->withName('parentObject') + ->withType('parentType') ->withLinkedEntities(['childObject' => 'childType']) ->build(); $childDataObject = $entityDataObjectBuilder - ->withName("childObject") - ->withType("childType") - ->withDataFields(["city" => "Hawkins", "state" => "Indiana", "zip" => "78758"]) + ->withName('childObject') + ->withType('childType') + ->withDataFields(['city' => 'Hawkins', 'state' => 'Indiana', 'zip' => '78758']) ->build(); // mock data object handler - $mockDOHInstance = AspectMock::double(DataObjectHandler::class, ['getObject' => $childDataObject])->make(); - AspectMock::double(DataObjectHandler::class, ['getInstance' => $mockDOHInstance]); + $this->mockDataObjectHandler($childDataObject); // set up metadata objects $childOpElementBuilder = new OperationElementBuilder(); $childElement = $childOpElementBuilder - ->withKey("address") - ->withType("childType") - ->withFields(["city" => "string", "state" => "string", "zip" => "integer"]) + ->withKey('address') + ->withType('childType') + ->withFields(['city' => 'string', 'state' => 'string', 'zip' => 'integer']) ->build(); $parentOpElementBuilder = new OperationElementBuilder(); $parentElement = $parentOpElementBuilder - ->withKey("parentType") - ->withType("parentType") - ->addElements(["address" => $childElement]) + ->withKey('parentType') + ->withType('parentType') + ->addElements(['address' => $childElement]) ->build(); // resolve data object and metadata array $operationResolver = new OperationDataArrayResolver(); - $result = $operationResolver->resolveOperationDataArray($parentDataObject, [$parentElement], "create", false); + $result = $operationResolver->resolveOperationDataArray($parentDataObject, [$parentElement], 'create', false); // assert on the result $this->assertEquals(self::NESTED_METADATA_EXPECTED_RESULT, $result); @@ -207,66 +213,69 @@ public function testNestedMetadata() * </object> * </array * </object> + * + * @return void + * @throws Exception */ - public function testNestedMetadataArrayOfObjects() + public function testNestedMetadataArrayOfObjects(): void { // set up data objects $entityDataObjectBuilder = new EntityDataObjectBuilder(); $parentDataObject = $entityDataObjectBuilder - ->withName("parentObject") - ->withType("parentType") + ->withName('parentObject') + ->withType('parentType') ->withLinkedEntities(['childObject1' => 'childType', 'childObject2' => 'childType']) ->build(); // mock data object handler - $mockDOHInstance = AspectMock::double(DataObjectHandler::class, ["getObject" => function ($name) { + $callback = function ($name) { $entityDataObjectBuilder = new EntityDataObjectBuilder(); - if ($name == "childObject1") { + if ($name === 'childObject1') { return $entityDataObjectBuilder - ->withName("childObject1") - ->withType("childType") - ->withDataFields(["city" => "Hawkins", "state" => "Indiana", "zip" => "78758"]) + ->withName('childObject1') + ->withType('childType') + ->withDataFields(['city' => 'Hawkins', 'state' => 'Indiana', 'zip' => '78758']) ->build(); } - if ($name == "childObject2") { + if ($name === 'childObject2') { return $entityDataObjectBuilder - ->withName("childObject2") - ->withType("childType") - ->withDataFields(["city" => "Austin", "state" => "Texas", "zip" => "78701"]) + ->withName('childObject2') + ->withType('childType') + ->withDataFields(['city' => 'Austin', 'state' => 'Texas', 'zip' => '78701']) ->build(); } - }])->make(); - AspectMock::double(DataObjectHandler::class, ['getInstance' => $mockDOHInstance]); + }; + $this->mockDataObjectHandler($callback); // set up metadata objects $childOpElementBuilder = new OperationElementBuilder(); $childElement = $childOpElementBuilder - ->withKey("childType") - ->withType("childType") - ->withFields(["city" => "string", "state" => "string", "zip" => "integer"]) + ->withKey('childType') + ->withType('childType') + ->withFields(['city' => 'string', 'state' => 'string', 'zip' => 'integer']) ->build(); $arrayOpElementBuilder = new OperationElementBuilder(); $arrayElement = $arrayOpElementBuilder - ->withKey("address") - ->withType("childType") + ->withKey('address') + ->withType('childType') ->withFields([]) ->withElementType(OperationDefinitionObjectHandler::ENTITY_OPERATION_ARRAY) - ->withNestedElements(["childType" => $childElement]) + ->withNestedElements(['childType' => $childElement]) ->build(); $parentOpElementBuilder = new OperationElementBuilder(); $parentElement = $parentOpElementBuilder - ->withKey("parentType") - ->withType("parentType") - ->addElements(["address" => $arrayElement]) + ->withKey('parentType') + ->withType('parentType') + ->addElements(['address' => $arrayElement]) ->build(); // resolve data object and metadata array $operationResolver = new OperationDataArrayResolver(); - $result = $operationResolver->resolveOperationDataArray($parentDataObject, [$parentElement], "create", false); + $result = $operationResolver->resolveOperationDataArray($parentDataObject, [$parentElement], 'create', false); // Do assert on result here $this->assertEquals(self::NESTED_METADATA_ARRAY_RESULT, $result); @@ -280,44 +289,47 @@ public function testNestedMetadataArrayOfObjects() * <value>object</value> * </array * </object> + * + * @return void + * @throws Exception */ - public function testNestedMetadataArrayOfValue() + public function testNestedMetadataArrayOfValue(): void { // set up data objects $entityDataObjectBuilder = new EntityDataObjectBuilder(); $parentDataObject = $entityDataObjectBuilder - ->withName("parentObject") - ->withType("parentType") + ->withName('parentObject') + ->withType('parentType') ->withLinkedEntities(['childObject1' => 'childType', 'childObject2' => 'childType']) ->build(); - // mock data object handler - $mockDOHInstance = AspectMock::double(DataObjectHandler::class, ["getObject" => function ($name) { + $callback = function ($name) { $entityDataObjectBuilder = new EntityDataObjectBuilder(); - if ($name == "childObject1") { + if ($name === 'childObject1') { return $entityDataObjectBuilder - ->withName("childObject1") - ->withType("childType") - ->withDataFields(["city" => "Hawkins", "state" => "Indiana", "zip" => "78758"]) + ->withName('childObject1') + ->withType('childType') + ->withDataFields(['city' => 'Hawkins', 'state' => 'Indiana', 'zip' => '78758']) ->build(); }; - if ($name == "childObject2") { + if ($name === 'childObject2') { return $entityDataObjectBuilder - ->withName("childObject2") - ->withType("childType") - ->withDataFields(["city" => "Austin", "state" => "Texas", "zip" => "78701"]) + ->withName('childObject2') + ->withType('childType') + ->withDataFields(['city' => 'Austin', 'state' => 'Texas', 'zip' => '78701']) ->build(); } - }])->make(); - AspectMock::double(DataObjectHandler::class, ['getInstance' => $mockDOHInstance]); + }; + // mock data object handler + $this->mockDataObjectHandler($callback); // set up metadata objects $arrayOpElementBuilder = new OperationElementBuilder(); $arrayElement = $arrayOpElementBuilder - ->withKey("address") - ->withType("childType") + ->withKey('address') + ->withType('childType') ->withElementType(OperationDefinitionObjectHandler::ENTITY_OPERATION_ARRAY) ->withNestedElements([]) ->withFields([]) @@ -325,44 +337,39 @@ public function testNestedMetadataArrayOfValue() $parentOpElementBuilder = new OperationElementBuilder(); $parentElement = $parentOpElementBuilder - ->withKey("parentType") - ->withType("parentType") - ->addElements(["address" => $arrayElement]) + ->withKey('parentType') + ->withType('parentType') + ->addElements(['address' => $arrayElement]) ->build(); $operationDefinitionBuilder = new OperationDefinitionBuilder(); $childOperationDefinition = $operationDefinitionBuilder - ->withName("createChildType") - ->withOperation("create") - ->withType("childType") + ->withName('createChildType') + ->withOperation('create') + ->withType('childType') ->withMetadata([ - "city" => "string", - "state" => "string", - "zip" => "integer" + 'city' => 'string', + 'state' => 'string', + 'zip' => 'integer' ])->build(); // mock meta data object handler - $mockDOHInstance = AspectMock::double( - OperationDefinitionObjectHandler::class, - ['getObject' => $childOperationDefinition] - )->make(); - AspectMock::double(OperationDefinitionObjectHandler::class, ['getInstance' => $mockDOHInstance]); + $this->mockOperationDefinitionObjectHandler($childOperationDefinition); // resolve data object and metadata array $operationResolver = new OperationDataArrayResolver(); - $result = $operationResolver->resolveOperationDataArray($parentDataObject, [$parentElement], "create", false); + $result = $operationResolver->resolveOperationDataArray($parentDataObject, [$parentElement], 'create', false); // Do assert on result here $this->assertEquals(self::NESTED_METADATA_ARRAY_RESULT, $result); } - public function testNestedMetadataArrayOfDiverseObjects() + public function testNestedMetadataArrayOfDiverseObjects(): void { - $entityDataObjBuilder = new EntityDataObjectBuilder(); $parentDataObject = $entityDataObjBuilder - ->withName("parentObject") - ->withType("parentType") + ->withName('parentObject') + ->withType('parentType') ->withLinkedEntities(['child1Object' => 'childType1','child2Object' => 'childType2']) ->build(); @@ -378,22 +385,15 @@ public function testNestedMetadataArrayOfDiverseObjects() ->withDataFields(['city' => 'Testcity 2','zip' => 54321,'state' => 'Teststate']) ->build(); - $mockDOHInstance = AspectMock::double( - DataObjectHandler::class, - [ - 'getObject' => function ($name) use ($child1DataObject, $child2DataObject) { - switch ($name) { - case 'child1Object': - return $child1DataObject; - case 'child2Object': - return $child2DataObject; - } - } - ] - )->make(); - AspectMock::double(DataObjectHandler::class, [ - 'getInstance' => $mockDOHInstance - ]); + $dataObjectCallback = function ($name) use ($child1DataObject, $child2DataObject) { + switch ($name) { + case 'child1Object': + return $child1DataObject; + case 'child2Object': + return $child2DataObject; + } + }; + $this->mockDataObjectHandler($dataObjectCallback); $operationDefinitionBuilder = new OperationDefinitionBuilder(); $child1OperationDefinition = $operationDefinitionBuilder @@ -415,25 +415,15 @@ public function testNestedMetadataArrayOfDiverseObjects() 'state' => 'string' ])->build(); - $mockODOHInstance = AspectMock::double( - OperationDefinitionObjectHandler::class, - [ - 'getObject' => function ($name) use ($child1OperationDefinition, $child2OperationDefinition) { - switch ($name) { - case 'createchildType1': - return $child1OperationDefinition; - case 'createchildType2': - return $child2OperationDefinition; - } - } - ] - )->make(); - AspectMock::double( - OperationDefinitionObjectHandler::class, - [ - 'getInstance' => $mockODOHInstance - ] - ); + $operationObjectCallback = function ($name) use ($child1OperationDefinition, $child2OperationDefinition) { + switch ($name) { + case 'createchildType1': + return $child1OperationDefinition; + case 'createchildType2': + return $child2OperationDefinition; + } + }; + $this->mockOperationDefinitionObjectHandler($operationObjectCallback); $arrayObElementBuilder = new OperationElementBuilder(); $arrayElement = $arrayObElementBuilder @@ -477,12 +467,117 @@ public function testNestedMetadataArrayOfDiverseObjects() $this->assertEquals($expectedResult, $result); } + public function testExtendedWithRequiredEntity(): void + { + $entityDataObjectBuilder = new EntityDataObjectBuilder(); + $extEntityDataObject = $entityDataObjectBuilder + ->withName('extEntity') + ->withType('entity') + ->withLinkedEntities(['baseSubentity' => 'subentity','extSubentity' => 'subentity']) + ->build(); + + $callback = function ($name) { + $entityDataObjectBuilder = new EntityDataObjectBuilder(); + + if ($name === 'baseSubentity') { + return $entityDataObjectBuilder + ->withName('baseSubentity') + ->withType('subentity') + ->withDataFields(['subtest' => 'BaseSubtest']) + ->build(); + } + + if ($name === 'extSubentity') { + return $entityDataObjectBuilder + ->withName('extSubentity') + ->withType('subentity') + ->withDataFields(['subtest' => 'ExtSubtest']) + ->build(); + } + }; + $this->mockDataObjectHandler($callback); + + $subentityOpElementBuilder = new OperationElementBuilder(); + $subentityOpElement = $subentityOpElementBuilder + ->withKey('sub') + ->withType('subentity') + ->withElementType('object') + ->withFields(['subtest' => 'string']) + ->build(); + + $operationResolver = new OperationDataArrayResolver(); + $result = $operationResolver->resolveOperationDataArray( + $extEntityDataObject, + [$subentityOpElement], + 'create', + false + ); + + $expected = [ + 'sub' => [ + 'subtest' => 'ExtSubtest' + ] + ]; + + $this->assertEquals($expected, $result); + } /** * After class functionality + * * @return void */ - public static function tearDownAfterClass() + public static function tearDownAfterClass(): void { TestLoggingUtil::getInstance()->clearMockLoggingUtil(); } + + /** + * Set up mock DataObjectHandler + * + * @param $childDataObject + * + * @return void + */ + private function mockDataObjectHandler($childDataObject): void + { + $instance = $this->createMock(DataObjectHandler::class); + if (is_callable($childDataObject)) { + $instance->expects($this->any()) + ->method('getObject') + ->willReturnCallback($childDataObject); + } else { + $instance->expects($this->any()) + ->method('getObject') + ->willReturn($childDataObject); + } + + $property = new ReflectionProperty(DataObjectHandler::class, 'INSTANCE'); + $property->setAccessible(true); + $property->setValue($instance); + } + + /** + * Set up mock OperationDefinitionObjectHandler + * + * @param $childOperationDefinition + * + * @return void + */ + private function mockOperationDefinitionObjectHandler($childOperationDefinition): void + { + $instance = $this->createPartialMock(OperationDefinitionObjectHandler::class, ['getObject']); + if (is_callable($childOperationDefinition)) { + $instance->expects($this->any()) + ->method('getObject') + ->willReturnCallback($childOperationDefinition); + } else { + $instance->expects($this->any()) + ->method('getObject') + ->willReturn($childOperationDefinition); + } + + $property = new ReflectionProperty(OperationDefinitionObjectHandler::class, 'INSTANCE'); + $property->setAccessible(true); + $property->setValue($instance); + } } diff --git a/dev/tests/unit/Magento/FunctionalTestFramework/DataGenerator/Util/DataExtensionUtilTest.php b/dev/tests/unit/Magento/FunctionalTestFramework/DataGenerator/Util/DataExtensionUtilTest.php index e72c15b31..513715cc1 100644 --- a/dev/tests/unit/Magento/FunctionalTestFramework/DataGenerator/Util/DataExtensionUtilTest.php +++ b/dev/tests/unit/Magento/FunctionalTestFramework/DataGenerator/Util/DataExtensionUtilTest.php @@ -3,37 +3,28 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); -namespace Magento\FunctionalTestingFramework\DataGenerator\Objects; +namespace tests\unit\Magento\FunctionalTestFramework\DataGenerator\Util; use Magento\FunctionalTestingFramework\DataGenerator\Handlers\DataObjectHandler; use Magento\FunctionalTestingFramework\DataGenerator\Parsers\DataProfileSchemaParser; -use Magento\FunctionalTestingFramework\ObjectManager\ObjectManager; -use Magento\FunctionalTestingFramework\ObjectManagerFactory; -use Magento\FunctionalTestingFramework\Util\MagentoTestCase; -use AspectMock\Test as AspectMock; +use Magento\FunctionalTestingFramework\ObjectManager; +use ReflectionProperty; +use tests\unit\Util\MagentoTestCase; /** - * Class EntityDataObjectTest + * Class DataExtensionUtilTest */ -class EntityDataExtensionTest extends MagentoTestCase +class DataExtensionUtilTest extends MagentoTestCase { - /** - * Before method functionality - * @return void - */ - protected function setUp() - { - AspectMock::clean(); - } - - public function testNoParentData() + public function testNoParentData(): void { $extendedDataObject = [ 'entity' => [ 'extended' => [ 'type' => 'testType', - 'extends' => "parent", + 'extends' => 'parent', 'data' => [ 0 => [ 'key' => 'testKey', @@ -46,21 +37,21 @@ public function testNoParentData() $this->setMockEntities($extendedDataObject); - $this->expectExceptionMessage("Parent Entity parent not defined for Entity extended."); - DataObjectHandler::getInstance()->getObject("extended"); + $this->expectExceptionMessage('Parent Entity parent not defined for Entity extended.'); + DataObjectHandler::getInstance()->getObject('extended'); } - public function testAlreadyExtendedParentData() + public function testAlreadyExtendedParentData(): void { $extendedDataObjects = [ 'entity' => [ 'extended' => [ 'type' => 'testType', - 'extends' => "parent" + 'extends' => 'parent' ], 'parent' => [ 'type' => 'type', - 'extends' => "grandparent" + 'extends' => 'grandparent' ], 'grandparent' => [ 'type' => 'grand' @@ -71,18 +62,18 @@ public function testAlreadyExtendedParentData() $this->setMockEntities($extendedDataObjects); $this->expectExceptionMessage( - "Cannot extend an entity that already extends another entity. Entity: parent." . PHP_EOL + 'Cannot extend an entity that already extends another entity. Entity: parent.' . PHP_EOL ); - DataObjectHandler::getInstance()->getObject("extended"); + DataObjectHandler::getInstance()->getObject('extended'); } - public function testExtendedVarGetter() + public function testExtendedVarGetter(): void { $extendedDataObjects = [ 'entity' => [ 'extended' => [ 'type' => 'testType', - 'extends' => "parent" + 'extends' => 'parent' ], 'parent' => [ 'type' => 'type', @@ -98,18 +89,18 @@ public function testExtendedVarGetter() ]; $this->setMockEntities($extendedDataObjects); - $resultextendedDataObject = DataObjectHandler::getInstance()->getObject("extended"); + $resultextendedDataObject = DataObjectHandler::getInstance()->getObject('extended'); // Perform Asserts - $this->assertEquals("someOtherEntity->id", $resultextendedDataObject->getVarReference("someOtherEntity")); + $this->assertEquals('someOtherEntity->id', $resultextendedDataObject->getVarReference('someOtherEntity')); } - public function testGetLinkedEntities() + public function testGetLinkedEntities(): void { $extendedDataObjects = [ 'entity' => [ 'extended' => [ 'type' => 'testType', - 'extends' => "parent" + 'extends' => 'parent' ], 'parent' => [ 'type' => 'type', @@ -129,27 +120,36 @@ public function testGetLinkedEntities() $this->setMockEntities($extendedDataObjects); // Perform Asserts - $resultextendedDataObject = DataObjectHandler::getInstance()->getObject("extended"); - $this->assertEquals("linkedEntity1", $resultextendedDataObject->getLinkedEntitiesOfType("linkedEntityType")[0]); - $this->assertEquals("linkedEntity2", $resultextendedDataObject->getLinkedEntitiesOfType("otherEntityType")[0]); + $resultextendedDataObject = DataObjectHandler::getInstance()->getObject('extended'); + $this->assertEquals('linkedEntity1', $resultextendedDataObject->getLinkedEntitiesOfType('linkedEntityType')[0]); + $this->assertEquals('linkedEntity2', $resultextendedDataObject->getLinkedEntitiesOfType('otherEntityType')[0]); } - private function setMockEntities($mockEntityData) + /** + * Prepare mock entites. + * + * @param $mockEntityData + * + * @return void + */ + private function setMockEntities($mockEntityData): void { - $property = new \ReflectionProperty(DataObjectHandler::class, 'INSTANCE'); + $property = new ReflectionProperty(DataObjectHandler::class, 'INSTANCE'); $property->setAccessible(true); $property->setValue(null); - $mockDataProfileSchemaParser = AspectMock::double(DataProfileSchemaParser::class, [ - 'readDataProfiles' => $mockEntityData - ])->make(); + $mockDataProfileSchemaParser = $this->createMock(DataProfileSchemaParser::class); + $mockDataProfileSchemaParser->expects($this->any()) + ->method('readDataProfiles') + ->willReturn($mockEntityData); - $mockObjectManager = AspectMock::double(ObjectManager::class, [ - 'create' => $mockDataProfileSchemaParser - ])->make(); + $mockObjectManager = $this->createMock(ObjectManager::class); + $mockObjectManager + ->method('create') + ->willReturn($mockDataProfileSchemaParser); - AspectMock::double(ObjectManagerFactory::class, [ - 'getObjectManager' => $mockObjectManager - ]); + $property = new ReflectionProperty(ObjectManager::class, 'instance'); + $property->setAccessible(true); + $property->setValue($mockObjectManager); } } diff --git a/dev/tests/unit/Magento/FunctionalTestFramework/Extension/BrowserLogUtilTest.php b/dev/tests/unit/Magento/FunctionalTestFramework/Extension/BrowserLogUtilTest.php new file mode 100644 index 000000000..45d14ae99 --- /dev/null +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Extension/BrowserLogUtilTest.php @@ -0,0 +1,76 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace tests\unit\Magento\FunctionalTestFramework\Extension; + +use tests\unit\Util\MagentoTestCase; +use Magento\FunctionalTestingFramework\Extension\BrowserLogUtil; + +class BrowserLogUtilTest extends MagentoTestCase +{ + public function testGetLogsOfType() + { + $entryOne = [ + "level" => "WARNING", + "message" => "warningMessage", + "source" => "console-api", + "timestamp" => 1234567890 + ]; + $entryTwo = [ + "level" => "ERROR", + "message" => "errorMessage", + "source" => "other", + "timestamp" => 1234567890 + ]; + $entryThree = [ + "level" => "LOG", + "message" => "logMessage", + "source" => "javascript", + "timestamp" => 1234567890 + ]; + $log = [ + $entryOne, + $entryTwo, + $entryThree + ]; + + $actual = BrowserLogUtil::getLogsOfType($log, 'console-api'); + + self::assertEquals($entryOne, $actual[0]); + } + + public function testFilterLogsOfType() + { + $entryOne = [ + "level" => "WARNING", + "message" => "warningMessage", + "source" => "console-api", + "timestamp" => 1234567890 + ]; + $entryTwo = [ + "level" => "ERROR", + "message" => "errorMessage", + "source" => "other", + "timestamp" => 1234567890 + ]; + $entryThree = [ + "level" => "LOG", + "message" => "logMessage", + "source" => "javascript", + "timestamp" => 1234567890 + ]; + $log = [ + $entryOne, + $entryTwo, + $entryThree + ]; + + $actual = BrowserLogUtil::filterLogsOfType($log, 'console-api'); + + self::assertEquals($entryTwo, $actual[0]); + self::assertEquals($entryThree, $actual[1]); + } +} diff --git a/dev/tests/unit/Magento/FunctionalTestFramework/Module/Util/ModuleUtilTest.php b/dev/tests/unit/Magento/FunctionalTestFramework/Module/Util/ModuleUtilTest.php new file mode 100644 index 000000000..dabf46fd0 --- /dev/null +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Module/Util/ModuleUtilTest.php @@ -0,0 +1,48 @@ +<?php + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace tests\unit\Magento\FunctionalTestFramework\Module\Util; + +use Magento\FunctionalTestingFramework\Module\Util\ModuleUtils; +use PHPUnit\Framework\TestCase; + +class ModuleUtilTest extends TestCase +{ + /** + * Test utf8SafeControlCharacterTrim() + * + * @param string $input + * @param string $output + * @param string $removed + * + * @return void + * @dataProvider inDataProvider + */ + public function testUtf8SafeControlCharacterTrim(string $input, string $output, $removed): void + { + $util = new ModuleUtils(); + $this->assertStringContainsString($output, $util->utf8SafeControlCharacterTrim($input)); + $this->assertStringNotContainsString($removed, $util->utf8SafeControlCharacterTrim($input)); + } + + /** + * Data input. + * + * @return array + */ + public function inDataProvider(): array + { + $ctr1 = '‹'; + $ctr2 = 'Œ'; + $ctr3 = 'Œ ‹'; + return [ + ["some text $ctr1", 'some text', $ctr1], + ["some text $ctr2", 'some text', $ctr2], + ["some text $ctr3", 'some text', $ctr3] + ]; + } +} diff --git a/dev/tests/unit/Magento/FunctionalTestFramework/Page/Handlers/PageObjectHandlerTest.php b/dev/tests/unit/Magento/FunctionalTestFramework/Page/Handlers/PageObjectHandlerTest.php index 28ffcd9e1..c611cc6a7 100644 --- a/dev/tests/unit/Magento/FunctionalTestFramework/Page/Handlers/PageObjectHandlerTest.php +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Page/Handlers/PageObjectHandlerTest.php @@ -3,87 +3,186 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); namespace tests\unit\Magento\FunctionalTestFramework\Page\Handlers; -use AspectMock\Test as AspectMock; +use Magento\FunctionalTestingFramework\Exceptions\XmlException; use Magento\FunctionalTestingFramework\ObjectManager; use Magento\FunctionalTestingFramework\ObjectManagerFactory; use Magento\FunctionalTestingFramework\Page\Handlers\PageObjectHandler; use Magento\FunctionalTestingFramework\XmlParser\PageParser; -use Magento\FunctionalTestingFramework\Util\MagentoTestCase; +use ReflectionProperty; +use tests\unit\Util\MagentoTestCase; +use tests\unit\Util\TestLoggingUtil; +/** + * Class PageObjectHandlerTest + */ class PageObjectHandlerTest extends MagentoTestCase { - public function testGetPageObject() + /** + * @inheritDoc + */ + protected function setUp(): void + { + TestLoggingUtil::getInstance()->setMockLoggingUtil(); + } + + /** + * Validate testGetPageObject. + * + * @return void + * @throws XmlException + */ + public function testGetPageObject(): void { $mockData = [ - "testPage1" => [ - "url" => "testURL1", - "module" => "testModule1", - "section" => [ - "someSection1" => [], - "someSection2" => [] + 'testPage1' => [ + 'url' => 'testURL1', + 'module' => 'testModule1', + 'section' => [ + 'someSection1' => [], + 'someSection2' => [] ], - "area" => "test" + 'area' => 'test' ], - "testPage2" => [ - "url" => "testURL2", - "module" => "testModule2", - "parameterized" => true, - "section" => [ - "someSection1" => [] + 'testPage2' => [ + 'url' => 'testURL2', + 'module' => 'testModule2', + 'parameterized' => true, + 'section' => [ + 'someSection1' => [] ], - "area" => "test" + 'area' => 'test' ]]; - $this->setMockParserOutput($mockData); - // get pages + $this->mockPageObjectHandlerWithData($mockData); $pageHandler = PageObjectHandler::getInstance(); $pages = $pageHandler->getAllObjects(); - $page = $pageHandler->getObject('testPage1'); + $pageHandler->getObject('testPage1'); $invalidPage = $pageHandler->getObject('someInvalidPage'); // perform asserts $this->assertCount(2, $pages); - $this->assertArrayHasKey("testPage1", $pages); - $this->assertArrayHasKey("testPage2", $pages); + $this->assertArrayHasKey('testPage1', $pages); + $this->assertArrayHasKey('testPage2', $pages); $this->assertNull($invalidPage); } - public function testGetEmptyPage() + /** + * Validate testGetEmptyPage. + * + * @return void + * @throws XmlException + */ + public function testGetEmptyPage(): void { $mockData = [ - "testPage1" => [ - "url" => "testURL1", - "module" => "testModule1", - "section" => [ + 'testPage1' => [ + 'url' => 'testURL1', + 'module' => 'testModule1', + 'section' => [ ], - "area" => "test" + 'area' => 'test' ]]; - $this->setMockParserOutput($mockData); - // get pages - $page = PageObjectHandler::getInstance()->getObject('testPage1'); + $this->mockPageObjectHandlerWithData($mockData); + PageObjectHandler::getInstance()->getObject('testPage1'); // Empty page has been read in and gotten without an exception being thrown. $this->addToAssertionCount(1); } /** - * Function used to set mock for parser return and force init method to run between tests. + * Validate testDeprecatedPage. + * + * @return void + * @throws XmlException + */ + public function testDeprecatedPage(): void + { + $mockData = [ + 'testPage1' => [ + 'url' => 'testURL1', + 'module' => 'testModule1', + 'section' => [ + ], + 'area' => 'test', + 'deprecated' => 'deprecation message', + 'filename' => 'filename.xml' + ]]; + + $this->mockPageObjectHandlerWithData($mockData); + PageObjectHandler::getInstance()->getObject('testPage1'); + + TestLoggingUtil::getInstance()->validateMockLogStatement( + 'notice', + 'NOTICE: 1 Page name violations detected. See mftf.log for details.', + [] + ); + } + + /** + * Create mock page object handler with data. + * + * @param array $mockData * - * @param array $data + * @return void */ - private function setMockParserOutput($data) + private function mockPageObjectHandlerWithData(array $mockData): void { - // clear section object handler value to inject parsed content - $property = new \ReflectionProperty(PageObjectHandler::class, 'INSTANCE'); + $pageObjectHandlerProperty = new ReflectionProperty(PageObjectHandler::class, 'INSTANCE'); + $pageObjectHandlerProperty->setAccessible(true); + $pageObjectHandlerProperty->setValue(null); + + $mockSectionParser = $this->createMock(PageParser::class); + $mockSectionParser + ->method('getData') + ->willReturn($mockData); + + $objectManager = ObjectManagerFactory::getObjectManager(); + $mockObjectManagerInstance = $this->createMock(ObjectManager::class); + $mockObjectManagerInstance + ->method('get') + ->will( + $this->returnCallback( + function ( + string $class, + array $arguments = [] + ) use ( + $objectManager, + $mockSectionParser + ) { + if ($class === PageParser::class) { + return $mockSectionParser; + } + + return $objectManager->create($class, $arguments); + } + ) + ); + + $property = new ReflectionProperty(ObjectManager::class, 'instance'); $property->setAccessible(true); - $property->setValue(null); + $property->setValue($mockObjectManagerInstance); + } + + /** + * @inheritDoc + */ + public static function tearDownAfterClass(): void + { + parent::tearDownAfterClass(); + + $pageObjectHandlerProperty = new ReflectionProperty(PageObjectHandler::class, 'INSTANCE'); + $pageObjectHandlerProperty->setAccessible(true); + $pageObjectHandlerProperty->setValue(null); + + $objectManagerProperty = new ReflectionProperty(ObjectManager::class, 'instance'); + $objectManagerProperty->setAccessible(true); + $objectManagerProperty->setValue(null); - $mockSectionParser = AspectMock::double(PageParser::class, ["getData" => $data])->make(); - $instance = AspectMock::double(ObjectManager::class, ['get' => $mockSectionParser])->make(); - AspectMock::double(ObjectManagerFactory::class, ['getObjectManager' => $instance]); + TestLoggingUtil::getInstance()->clearMockLoggingUtil(); } } diff --git a/dev/tests/unit/Magento/FunctionalTestFramework/Page/Handlers/SectionObjectHandlerTest.php b/dev/tests/unit/Magento/FunctionalTestFramework/Page/Handlers/SectionObjectHandlerTest.php index 4a939dbad..b0ce0a4b8 100644 --- a/dev/tests/unit/Magento/FunctionalTestFramework/Page/Handlers/SectionObjectHandlerTest.php +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Page/Handlers/SectionObjectHandlerTest.php @@ -3,69 +3,171 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); namespace tests\unit\Magento\FunctionalTestFramework\Page\Handlers; -use AspectMock\Test as AspectMock; +use Magento\FunctionalTestingFramework\Exceptions\XmlException; use Magento\FunctionalTestingFramework\ObjectManager; use Magento\FunctionalTestingFramework\ObjectManagerFactory; use Magento\FunctionalTestingFramework\Page\Handlers\SectionObjectHandler; use Magento\FunctionalTestingFramework\XmlParser\SectionParser; -use Magento\FunctionalTestingFramework\Util\MagentoTestCase; +use ReflectionProperty; +use tests\unit\Util\MagentoTestCase; +use tests\unit\Util\TestLoggingUtil; +/** + * Class SectionObjectHandlerTest + */ class SectionObjectHandlerTest extends MagentoTestCase { - public function testGetSectionObject() + /** + * @inheritDoc + */ + protected function setUp(): void + { + TestLoggingUtil::getInstance()->setMockLoggingUtil(); + } + + /** + * Validate testGetSectionObject. + * + * @return void + * @throws XmlException + */ + public function testGetSectionObject(): void { $mockData = [ - "testSection1" => [ - "element" => [ - "testElement" => [ - "type" => "input", - "selector" => "#element" + 'testSection1' => [ + 'element' => [ + 'testElement' => [ + 'type' => 'input', + 'selector' => '#element' ] ] ], - "testSection2" => [ - "element" => [ - "testElement" => [ - "type" => "input", - "selector" => "#element" + 'testSection2' => [ + 'element' => [ + 'testElement' => [ + 'type' => 'input', + 'selector' => '#element' ] ] ] ]; - $this->setMockParserOutput($mockData); + $this->mockSectionObjectHandlerWithData($mockData); // get sections $sectionHandler = SectionObjectHandler::getInstance(); $sections = $sectionHandler->getAllObjects(); - $section = $sectionHandler->getObject("testSection1"); - $invalidSection = $sectionHandler->getObject("InvalidSection"); + $sectionHandler->getObject('testSection1'); + $invalidSection = $sectionHandler->getObject('InvalidSection'); // perform asserts $this->assertCount(2, $sections); - $this->assertArrayHasKey("testSection1", $sections); - $this->assertArrayHasKey("testSection2", $sections); + $this->assertArrayHasKey('testSection1', $sections); + $this->assertArrayHasKey('testSection2', $sections); $this->assertNull($invalidSection); } /** - * Set the mock parser return value + * Validate testDeprecatedSection. * - * @param array $data + * @return void + * @throws XmlException */ - private function setMockParserOutput($data) + public function testDeprecatedSection(): void { - // clear section object handler value to inject parsed content - $property = new \ReflectionProperty(SectionObjectHandler::class, "INSTANCE"); + $mockData = [ + 'testSection1' => [ + 'element' => [ + 'testElement' => [ + 'type' => 'input', + 'selector' => '#element', + 'deprecated' => 'element deprecation message' + ] + ], + 'filename' => 'filename.xml', + 'deprecated' => 'section deprecation message' + ] + ]; + + $this->mockSectionObjectHandlerWithData($mockData); + + // get sections + $sectionHandler = SectionObjectHandler::getInstance(); + $sectionHandler->getObject('testSection1'); + + //validate deprecation warning + TestLoggingUtil::getInstance()->validateMockLogStatement( + 'notice', + 'NOTICE: 1 Section name violations detected. See mftf.log for details.', + [] + ); + } + + /** + * Create mock section object handler with data. + * + * @param array $mockData + * + * @return void + */ + private function mockSectionObjectHandlerWithData(array $mockData): void + { + $sectionObjectHandlerProperty = new ReflectionProperty(SectionObjectHandler::class, "INSTANCE"); + $sectionObjectHandlerProperty->setAccessible(true); + $sectionObjectHandlerProperty->setValue(null); + + $mockSectionParser = $this->createMock(SectionParser::class); + $mockSectionParser + ->method('getData') + ->willReturn($mockData); + + $objectManager = ObjectManagerFactory::getObjectManager(); + $mockObjectManagerInstance = $this->createMock(ObjectManager::class); + $mockObjectManagerInstance + ->method('get') + ->will( + $this->returnCallback( + function ( + string $class, + array $arguments = [] + ) use ( + $objectManager, + $mockSectionParser + ) { + if ($class === SectionParser::class) { + return $mockSectionParser; + } + + return $objectManager->create($class, $arguments); + } + ) + ); + + $property = new ReflectionProperty(ObjectManager::class, 'instance'); $property->setAccessible(true); - $property->setValue(null); + $property->setValue($mockObjectManagerInstance); + } + + /** + * @inheritDoc + */ + public static function tearDownAfterClass(): void + { + parent::tearDownAfterClass(); + + $sectionObjectHandlerProperty = new ReflectionProperty(SectionObjectHandler::class, "INSTANCE"); + $sectionObjectHandlerProperty->setAccessible(true); + $sectionObjectHandlerProperty->setValue(null); + + $objectManagerProperty = new ReflectionProperty(ObjectManager::class, 'instance'); + $objectManagerProperty->setAccessible(true); + $objectManagerProperty->setValue(null); - $mockSectionParser = AspectMock::double(SectionParser::class, ["getData" => $data])->make(); - $instance = AspectMock::double(ObjectManager::class, ["get" => $mockSectionParser])->make(); - AspectMock::double(ObjectManagerFactory::class, ["getObjectManager" => $instance]); + TestLoggingUtil::getInstance()->clearMockLoggingUtil(); } } diff --git a/dev/tests/unit/Magento/FunctionalTestFramework/Page/Objects/ElementObjectTest.php b/dev/tests/unit/Magento/FunctionalTestFramework/Page/Objects/ElementObjectTest.php index d585f4085..eacafbc68 100644 --- a/dev/tests/unit/Magento/FunctionalTestFramework/Page/Objects/ElementObjectTest.php +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Page/Objects/ElementObjectTest.php @@ -8,7 +8,7 @@ use Magento\FunctionalTestingFramework\Exceptions\XmlException; use Magento\FunctionalTestingFramework\Page\Objects\ElementObject; -use Magento\FunctionalTestingFramework\Util\MagentoTestCase; +use tests\unit\Util\MagentoTestCase; /** * Class ElementObjectTest @@ -32,7 +32,7 @@ public function testTimeoutNotNull() $element = new ElementObject('name', 'type', 'selector', null, '15', false); $timeout = $element->getTimeout(); $this->assertEquals(15, $timeout); - $this->assertInternalType('int', $timeout); + $this->assertIsInt($timeout); } /** @@ -43,7 +43,7 @@ public function testTimeoutCastFromString() $element = new ElementObject('name', 'type', 'selector', null, 'helloString', true); $timeout = $element->getTimeout(); $this->assertEquals(0, $timeout); - $this->assertInternalType('int', $timeout); + $this->assertIsInt($timeout); } /** diff --git a/dev/tests/unit/Magento/FunctionalTestFramework/Page/Objects/PageObjectTest.php b/dev/tests/unit/Magento/FunctionalTestFramework/Page/Objects/PageObjectTest.php index 7f8053e77..29ec5366d 100644 --- a/dev/tests/unit/Magento/FunctionalTestFramework/Page/Objects/PageObjectTest.php +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Page/Objects/PageObjectTest.php @@ -7,7 +7,7 @@ namespace tests\unit\Magento\FunctionalTestFramework\Page\Objects; use Magento\FunctionalTestingFramework\Page\Objects\PageObject; -use Magento\FunctionalTestingFramework\Util\MagentoTestCase; +use tests\unit\Util\MagentoTestCase; /** * Class PageObjectTest diff --git a/dev/tests/unit/Magento/FunctionalTestFramework/Page/Objects/SectionObjectTest.php b/dev/tests/unit/Magento/FunctionalTestFramework/Page/Objects/SectionObjectTest.php index 5ed1f557f..4305c0101 100644 --- a/dev/tests/unit/Magento/FunctionalTestFramework/Page/Objects/SectionObjectTest.php +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Page/Objects/SectionObjectTest.php @@ -8,7 +8,7 @@ use Magento\FunctionalTestingFramework\Page\Objects\ElementObject; use Magento\FunctionalTestingFramework\Page\Objects\SectionObject; -use Magento\FunctionalTestingFramework\Util\MagentoTestCase; +use tests\unit\Util\MagentoTestCase; /** * Class SectionObjectTest diff --git a/dev/tests/unit/Magento/FunctionalTestFramework/StaticCheck/AnnotationsCheckTest.php b/dev/tests/unit/Magento/FunctionalTestFramework/StaticCheck/AnnotationsCheckTest.php new file mode 100644 index 000000000..004955998 --- /dev/null +++ b/dev/tests/unit/Magento/FunctionalTestFramework/StaticCheck/AnnotationsCheckTest.php @@ -0,0 +1,184 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace tests\unit\Magento\FunctionalTestFramework\StaticCheck; + +use Magento\FunctionalTestingFramework\StaticCheck\AnnotationsCheck; +use Magento\FunctionalTestingFramework\Test\Objects\TestObject; +use ReflectionClass; +use tests\unit\Util\MagentoTestCase; + +class AnnotationsCheckTest extends MagentoTestCase +{ + /** @var AnnotationsCheck */ + private $staticCheck; + + /** @var ReflectionClass */ + private $staticCheckClass; + + public function setUp(): void + { + $this->staticCheck = new AnnotationsCheck(); + $this->staticCheckClass = new ReflectionClass($this->staticCheck); + } + + public function testValidateRequiredAnnotationsNoError() + { + $annotations = [ + 'features' => [ + 0 => 'feature1' + ], + 'stories' => [ + 0 => 'story1' + ], + 'description' => [ + 'main' => 'description1', + 'test_files' => 'file1', + 'deprecated' => [ + 0 => 'deprecated1' + ] + ], + 'severity' => [ + 0 => 'severity1' + ], + 'title' => [ + 0 => '[NO TESTCASEID]: title1' + ], + ]; + $expected = []; + + $test = $this->createMock(TestObject::class); + + $test->expects($this->once())->method('getAnnotations')->willReturn($annotations); + + $validateRequiredAnnotations = $this->staticCheckClass->getMethod('validateRequiredAnnotations'); + $validateRequiredAnnotations->setAccessible(true); + + $validateRequiredAnnotations->invoke($this->staticCheck, $test); + $this->assertEquals($expected, $this->staticCheck->getErrors()); + } + + public function testValidateRequiredAnnotationsMissing() + { + $testCaseId = 'MC-12345'; + + $annotations = [ + 'features' => [ + 0 => 'feature1' + ], + 'stories' => [ + 0 => 'story1' + ], + 'description' => [ + 'test_files' => 'file1', + 'deprecated' => [ + 0 => 'deprecated1' + ] + ], + 'title' => [ + 0 => $testCaseId . ': title1' + ], + 'testCaseId' => [ + 0 => $testCaseId + ] + ]; + $expected = [ + 0 => [ + 0 => 'Test AnnotationsCheckTest is missing the required annotations: description, severity' + ] + ]; + + $test = $this->createMock(TestObject::class); + + $test->expects($this->once())->method('getAnnotations')->willReturn($annotations); + $test->expects($this->once())->method('getName')->willReturn('AnnotationsCheckTest'); + + $validateRequiredAnnotations = $this->staticCheckClass->getMethod('validateRequiredAnnotations'); + $validateRequiredAnnotations->setAccessible(true); + + $validateRequiredAnnotations->invoke($this->staticCheck, $test); + $this->assertEquals($expected, $this->staticCheck->getErrors()); + } + + public function testValidateRequiredAnnotationsMissingNoTestCaseId() + { + $annotations = [ + 'features' => [ + 0 => 'feature1' + ], + 'stories' => [ + 0 => 'story1' + ], + 'description' => [ + 'test_files' => 'file1', + 'deprecated' => [ + 0 => 'deprecated1' + ] + ], + 'title' => [ + 0 => "[NO TESTCASEID]: \t" + ], + ]; + $expected = [ + 0 => [ + 0 => 'Test AnnotationsCheckTest is missing the required annotations: title, description, severity' + ] + ]; + + $test = $this->createMock(TestObject::class); + + $test->expects($this->once())->method('getAnnotations')->willReturn($annotations); + $test->expects($this->once())->method('getName')->willReturn('AnnotationsCheckTest'); + + $validateRequiredAnnotations = $this->staticCheckClass->getMethod('validateRequiredAnnotations'); + $validateRequiredAnnotations->setAccessible(true); + + $validateRequiredAnnotations->invoke($this->staticCheck, $test); + $this->assertEquals($expected, $this->staticCheck->getErrors()); + } + + public function testValidateRequiredAnnotationsEmpty() + { + $annotations = [ + 'features' => [ + 0 => 'feature1' + ], + 'stories' => [ + 0 => 'story1' + ], + 'description' => [ + 'main' => 'description1', + 'test_files' => 'file1', + 'deprecated' => [ + 0 => 'deprecated1' + ] + ], + 'severity' => [ + 0 => 'severity1' + ], + 'title' => [ + 0 => '' + ], + ]; + $expected = [ + 0 => [ + 0 => 'Test AnnotationsCheckTest is missing the required annotations: title' + ] + ]; + + $test = $this->createMock(TestObject::class); + + $test->expects($this->once())->method('getAnnotations')->willReturn($annotations); + $test->expects($this->once())->method('getName')->willReturn('AnnotationsCheckTest'); + + $validateRequiredAnnotations = $this->staticCheckClass->getMethod('validateRequiredAnnotations'); + $validateRequiredAnnotations->setAccessible(true); + + $validateRequiredAnnotations->invoke($this->staticCheck, $test); + $this->assertEquals($expected, $this->staticCheck->getErrors()); + } +} diff --git a/dev/tests/unit/Magento/FunctionalTestFramework/StaticCheck/DeprecatedEntityUsageCheckTest.php b/dev/tests/unit/Magento/FunctionalTestFramework/StaticCheck/DeprecatedEntityUsageCheckTest.php new file mode 100644 index 000000000..d4286060f --- /dev/null +++ b/dev/tests/unit/Magento/FunctionalTestFramework/StaticCheck/DeprecatedEntityUsageCheckTest.php @@ -0,0 +1,368 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace tests\unit\Magento\FunctionalTestFramework\StaticCheck; + +use InvalidArgumentException; +use Magento\FunctionalTestingFramework\DataGenerator\Handlers\OperationDefinitionObjectHandler; +use Magento\FunctionalTestingFramework\DataGenerator\Objects\EntityDataObject; +use Magento\FunctionalTestingFramework\DataGenerator\Parsers\OperationDefinitionParser; +use Magento\FunctionalTestingFramework\ObjectManager; +use Magento\FunctionalTestingFramework\ObjectManagerFactory; +use Magento\FunctionalTestingFramework\Page\Objects\ElementObject; +use Magento\FunctionalTestingFramework\Page\Objects\PageObject; +use Magento\FunctionalTestingFramework\Page\Objects\SectionObject; +use Magento\FunctionalTestingFramework\StaticCheck\DeprecatedEntityUsageCheck; +use Magento\FunctionalTestingFramework\Test\Objects\TestObject; +use ReflectionClass; +use ReflectionException; +use ReflectionProperty; +use Symfony\Component\Console\Input\InputInterface; +use tests\unit\Util\MagentoTestCase; + +/** + * Class DeprecatedEntityUsageCheckTest + */ +class DeprecatedEntityUsageCheckTest extends MagentoTestCase +{ + /** @var DeprecatedEntityUsageCheck */ + private $staticCheck; + + /** @var ReflectionClass*/ + private $staticCheckClass; + + /** + * @inheritDoc + */ + public function setUp(): void + { + $this->staticCheck = new DeprecatedEntityUsageCheck(); + $this->staticCheckClass = new ReflectionClass($this->staticCheck); + } + + /** + * Validate testInvalidPathOption. + * + * @return void + * @throws ReflectionException + */ + public function testInvalidPathOption(): void + { + $input = $this->getMockBuilder(InputInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $input->method('getOption') + ->with('path') + ->willReturn('/invalidPath'); + + $loadAllXmlFiles = $this->staticCheckClass->getMethod('loadAllXMLFiles'); + $loadAllXmlFiles->setAccessible(true); + + $this->expectException(InvalidArgumentException::class); + $loadAllXmlFiles->invoke($this->staticCheck, $input); + } + + /** + * Validate testViolatingElementReferences. + * + * @return void + * @throws ReflectionException + */ + public function testViolatingElementReferences(): void + { + //variables for assertions + $elementName = 'elementOne'; + $sectionName = 'SectionOne'; + $fileName = 'section.xml'; + + $element = new ElementObject($elementName, 'type', '#selector1', null, '41', false, 'deprecated'); + $section = new SectionObject($sectionName, [$element], $fileName); + $elementRef = $sectionName . '.' . $elementName; + $references = [$elementRef => $element, $sectionName => $section]; + $actual = $this->callViolatingReferences($references); + $expected = [ + 'Deprecated Element(s)' => [ + 0 => [ + 'name' => $elementRef, + 'file' => $fileName + ] + ] + ]; + $this->assertEquals($actual, $expected); + } + + /** + * Validate testViolatingPageReferences. + * + * @return void + * @throws ReflectionException + */ + public function testViolatingPageReferences(): void + { + //Page variables for assertions + $pageName = 'Page'; + $fileName = 'page.xml'; + + $page = new PageObject($pageName, '/url.html', 'Test', [], false, 'test', $fileName, 'deprecated'); + $references = ['Page' => $page]; + $actual = $this->callViolatingReferences($references); + $expected = [ + 'Deprecated Page(s)' => [ + 0 => [ + 'name' => $pageName, + 'file' => $fileName + ] + ] + ]; + $this->assertEquals($actual, $expected); + } + + /** + * Validate testViolatingDataReferences. + * + * @return void + * @throws ReflectionException + */ + public function testViolatingDataReferences(): void + { + //Data entity variables for assertions + $entityName = 'EntityOne'; + $fileName = 'entity.xml'; + + $entity = new EntityDataObject( + $entityName, + 'testType', + ['testkey' => 'testValue'], + [], + null, + [], + null, + $fileName, + 'deprecated' + ); + $references = [$entityName => $entity]; + $actual = $this->callViolatingReferences($references); + $expected = [ + 'Deprecated Data(s)' => [ + 0 => [ + 'name' => $entityName, + 'file' => $fileName + ] + ] + ]; + $this->assertEquals($actual, $expected); + } + + /** + * Validate testViolatingTestReferences. + * + * @return void + * @throws ReflectionException + */ + public function testViolatingTestReferences(): void + { + // test variables for assertions + $testName = 'Test1'; + $fileName = 'test.xml'; + + $test = new TestObject($testName, [], [], [], $fileName, null, 'deprecated'); + $references = ['Test1' => $test]; + $actual = $this->callViolatingReferences($references); + $expected = [ + 'Deprecated Test(s)' => [ + 0 => [ + 'name' => $testName, + 'file' => $fileName + ] + ] + ]; + $this->assertEquals($actual, $expected); + } + + /** + * Validate testViolatingMetaDataReferences. + * + * @return void + * @throws ReflectionException + */ + public function testViolatingMetaDataReferences(): void + { + // Data Variables for Assertions + $dataType1 = 'type1'; + $operationType1 = 'create'; + $operationType2 = 'update'; + + /** + * Parser Output. + * operationName + * createType1 + * has field + * key=id, value=integer + * updateType1 + * has field + * key=id, value=integer + */ + $mockData = [OperationDefinitionObjectHandler::ENTITY_OPERATION_ROOT_TAG => [ + 'testOperationName' => [ + OperationDefinitionObjectHandler::ENTITY_OPERATION_DATA_TYPE => $dataType1, + OperationDefinitionObjectHandler::ENTITY_OPERATION_TYPE => $operationType1, + OperationDefinitionObjectHandler::ENTITY_OPERATION_AUTH => 'auth', + OperationDefinitionObjectHandler::ENTITY_OPERATION_URL => 'V1/Type1', + OperationDefinitionObjectHandler::ENTITY_OPERATION_METHOD => 'POST', + OperationDefinitionObjectHandler::ENTITY_OPERATION_ENTRY => [ + 0 => [ + OperationDefinitionObjectHandler::ENTITY_OPERATION_ENTRY_KEY => 'id', + OperationDefinitionObjectHandler::ENTITY_OPERATION_ENTRY_VALUE => 'integer' + ], + ], + OperationDefinitionObjectHandler::OBJ_DEPRECATED => 'deprecated' + ],[ + OperationDefinitionObjectHandler::ENTITY_OPERATION_DATA_TYPE => $dataType1, + OperationDefinitionObjectHandler::ENTITY_OPERATION_TYPE => $operationType2, + OperationDefinitionObjectHandler::ENTITY_OPERATION_AUTH => 'auth', + OperationDefinitionObjectHandler::ENTITY_OPERATION_URL => 'V1/Type1/{id}', + OperationDefinitionObjectHandler::ENTITY_OPERATION_METHOD => 'PUT', + OperationDefinitionObjectHandler::ENTITY_OPERATION_ENTRY => [ + 0 => [ + OperationDefinitionObjectHandler::ENTITY_OPERATION_ENTRY_KEY => 'id', + OperationDefinitionObjectHandler::ENTITY_OPERATION_ENTRY_VALUE => 'integer' + ], + ] + ]]]; + + $this->mockOperationHandlerWithData($mockData); + $dataName = 'dataName1'; + $references = [ + $dataName => [ + $dataType1 => [ + $operationType1, + $operationType2 + ] + ] + ]; + + $expected = [ + '"'.$dataName.'" references deprecated' => [ + 0 => [ + 'name' => $dataType1, + 'file' => 'metadata xml file' + ] + ] + ]; + $property = $this->staticCheckClass->getMethod('findViolatingMetadataReferences'); + $property->setAccessible(true); + $actual = $property->invoke($this->staticCheck, $references); + $this->assertEquals($actual, $expected); + } + + /** + * Validate testIsDeprecated. + * + * @return void + * @throws ReflectionException + */ + public function testIsDeprecated(): void + { + // Test Data + $contents = '<tests> + <test name="test" deprecated="true"> + <comment userInput="input1" stepKey="key1"/> + <comment userInput="input2" stepKey="key1"/> + </test> + </tests> + '; + + $property = $this->staticCheckClass->getMethod('isDeprecated'); + $property->setAccessible(true); + $output = $property->invoke($this->staticCheck, $contents); + $this->assertTrue($output); + } + + /** + * Create mock operation handler with data. + * + * @param array $mockData + * + * @return void + */ + private function mockOperationHandlerWithData(array $mockData): void + { + $operationDefinitionObjectHandlerProperty = new ReflectionProperty( + OperationDefinitionObjectHandler::class, + 'INSTANCE' + ); + $operationDefinitionObjectHandlerProperty->setAccessible(true); + $operationDefinitionObjectHandlerProperty->setValue(null); + + $mockOperationParser = $this->createMock(OperationDefinitionParser::class); + $mockOperationParser + ->method('readOperationMetadata') + ->willReturn($mockData); + + $objectManager = ObjectManagerFactory::getObjectManager(); + $mockObjectManagerInstance = $this->createMock(ObjectManager::class); + $mockObjectManagerInstance + ->method('create') + ->will( + $this->returnCallback( + function ( + string $class, + array $arguments = [] + ) use ( + $objectManager, + $mockOperationParser + ) { + if ($class === OperationDefinitionParser::class) { + return $mockOperationParser; + } + + return $objectManager->create($class, $arguments); + } + ) + ); + + $property = new ReflectionProperty(ObjectManager::class, 'instance'); + $property->setAccessible(true); + $property->setValue($mockObjectManagerInstance); + } + + /** + * @inheritDoc + */ + public static function tearDownAfterClass(): void + { + parent::tearDownAfterClass(); + + $operationDefinitionObjectHandlerProperty = new ReflectionProperty( + OperationDefinitionObjectHandler::class, + 'INSTANCE' + ); + $operationDefinitionObjectHandlerProperty->setAccessible(true); + $operationDefinitionObjectHandlerProperty->setValue(null); + + $objectManagerProperty = new ReflectionProperty(ObjectManager::class, 'instance'); + $objectManagerProperty->setAccessible(true); + $objectManagerProperty->setValue(null); + } + + /** + * Invoke findViolatingReferences. + * + * @param array $references + * + * @return mixed + * @throws ReflectionException + */ + public function callViolatingReferences(array $references) + { + $property = $this->staticCheckClass->getMethod('findViolatingReferences'); + $property->setAccessible(true); + + return $property->invoke($this->staticCheck, $references); + } +} diff --git a/dev/tests/unit/Magento/FunctionalTestFramework/Suite/Handlers/SuiteObjectHandlerTest.php b/dev/tests/unit/Magento/FunctionalTestFramework/Suite/Handlers/SuiteObjectHandlerTest.php index 5bf29507c..373e0d6c1 100644 --- a/dev/tests/unit/Magento/FunctionalTestFramework/Suite/Handlers/SuiteObjectHandlerTest.php +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Suite/Handlers/SuiteObjectHandlerTest.php @@ -3,32 +3,30 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ -namespace Tests\unit\Magento\FunctionalTestFramework\Suite\Handlers; +declare(strict_types=1); -use AspectMock\Test as AspectMock; -use Magento\FunctionalTestingFramework\ObjectManager\ObjectManager; -use Magento\FunctionalTestingFramework\ObjectManagerFactory; +namespace tests\unit\Magento\FunctionalTestFramework\Suite\Handlers; + +use Exception; +use Magento\FunctionalTestingFramework\ObjectManager; use Magento\FunctionalTestingFramework\Suite\Handlers\SuiteObjectHandler; use Magento\FunctionalTestingFramework\Suite\Parsers\SuiteDataParser; use Magento\FunctionalTestingFramework\Test\Handlers\TestObjectHandler; use Magento\FunctionalTestingFramework\Test\Parsers\TestDataParser; -use Magento\FunctionalTestingFramework\Util\MagentoTestCase; +use ReflectionProperty; +use tests\unit\Util\MagentoTestCase; use tests\unit\Util\SuiteDataArrayBuilder; use tests\unit\Util\TestDataArrayBuilder; -use tests\unit\Util\MockModuleResolverBuilder; class SuiteObjectHandlerTest extends MagentoTestCase { - public function setUp() - { - $resolverMock = new MockModuleResolverBuilder(); - $resolverMock->setup(); - } - /** - * Tests basic parsing and accesors of suite object and suite object supporting classes + * Tests basic parsing and accessors of suite object and suite object supporting classes. + * + * @return void + * @throws Exception */ - public function testGetSuiteObject() + public function testGetSuiteObject(): void { $suiteDataArrayBuilder = new SuiteDataArrayBuilder(); $mockData = $suiteDataArrayBuilder @@ -65,7 +63,7 @@ public function testGetSuiteObject() ->withTestActions() ->build(); - $mockTestData = ['tests' => array_merge($mockSimpleTest, $mockGroup1Test1, $mockGroup1Test2, $mockGroup2Test1)]; + $mockTestData = array_merge($mockSimpleTest, $mockGroup1Test1, $mockGroup1Test2, $mockGroup2Test1); $this->setMockTestAndSuiteParserOutput($mockTestData, $mockData); // parse and retrieve suite object with mocked data @@ -82,35 +80,54 @@ public function testGetSuiteObject() * Function used to set mock for parser return and force init method to run between tests. * * @param array $testData - * @throws \Exception + * @param array $suiteData + * + * @return void + * @throws Exception */ - private function setMockTestAndSuiteParserOutput($testData, $suiteData) + private function setMockTestAndSuiteParserOutput(array $testData, array $suiteData): void { // clear test object handler value to inject parsed content - $property = new \ReflectionProperty(TestObjectHandler::class, 'testObjectHandler'); + $property = new ReflectionProperty(TestObjectHandler::class, 'testObjectHandler'); $property->setAccessible(true); $property->setValue(null); // clear suite object handler value to inject parsed content - $property = new \ReflectionProperty(SuiteObjectHandler::class, 'instance'); + $property = new ReflectionProperty(SuiteObjectHandler::class, 'instance'); $property->setAccessible(true); $property->setValue(null); - $mockDataParser = AspectMock::double(TestDataParser::class, ['readTestData' => $testData])->make(); - $mockSuiteDataParser = AspectMock::double(SuiteDataParser::class, ['readSuiteData' => $suiteData])->make(); - $instance = AspectMock::double( - ObjectManager::class, - ['create' => function ($clazz) use ($mockDataParser, $mockSuiteDataParser) { - if ($clazz == TestDataParser::class) { - return $mockDataParser; - } - - if ($clazz == SuiteDataParser::class) { - return $mockSuiteDataParser; - } - }] - )->make(); - // bypass the private constructor - AspectMock::double(ObjectManagerFactory::class, ['getObjectManager' => $instance]); + $mockDataParser = $this->createMock(TestDataParser::class); + $mockDataParser + ->method('readTestData') + ->willReturn($testData); + + $mockSuiteDataParser = $this->createMock(SuiteDataParser::class); + $mockSuiteDataParser + ->method('readSuiteData') + ->willReturn($suiteData); + + $instance = $this->createMock(ObjectManager::class); + $instance + ->method('create') + ->will( + $this->returnCallback( + function ($clazz) use ($mockDataParser, $mockSuiteDataParser) { + if ($clazz === TestDataParser::class) { + return $mockDataParser; + } + + if ($clazz === SuiteDataParser::class) { + return $mockSuiteDataParser; + } + + return null; + } + ) + ); + + $property = new ReflectionProperty(ObjectManager::class, 'instance'); + $property->setAccessible(true); + $property->setValue($instance); } } diff --git a/dev/tests/unit/Magento/FunctionalTestFramework/Suite/SuiteGeneratorTest.php b/dev/tests/unit/Magento/FunctionalTestFramework/Suite/SuiteGeneratorTest.php index 29158b0f5..f6e215a5b 100644 --- a/dev/tests/unit/Magento/FunctionalTestFramework/Suite/SuiteGeneratorTest.php +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Suite/SuiteGeneratorTest.php @@ -3,11 +3,15 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ -namespace Tests\unit\Magento\FunctionalTestFramework\Suite; +declare(strict_types=1); -use AspectMock\Test as AspectMock; -use Magento\FunctionalTestingFramework\ObjectManager\ObjectManager; +namespace tests\unit\Magento\FunctionalTestFramework\Suite; + +use Exception; +use Magento\FunctionalTestingFramework\Exceptions\TestReferenceException; +use Magento\FunctionalTestingFramework\ObjectManager; use Magento\FunctionalTestingFramework\ObjectManagerFactory; +use Magento\FunctionalTestingFramework\Suite\Service\SuiteGeneratorService; use Magento\FunctionalTestingFramework\Suite\SuiteGenerator; use Magento\FunctionalTestingFramework\Suite\Generators\GroupClassGenerator; use Magento\FunctionalTestingFramework\Suite\Handlers\SuiteObjectHandler; @@ -15,42 +19,34 @@ use Magento\FunctionalTestingFramework\Test\Handlers\TestObjectHandler; use Magento\FunctionalTestingFramework\Test\Util\TestObjectExtractor; use Magento\FunctionalTestingFramework\Test\Parsers\TestDataParser; +use Magento\FunctionalTestingFramework\Util\GenerationErrorHandler; use Magento\FunctionalTestingFramework\Util\Manifest\DefaultTestManifest; -use Magento\FunctionalTestingFramework\Util\MagentoTestCase; +use ReflectionProperty; +use tests\unit\Util\MagentoTestCase; +use Magento\FunctionalTestingFramework\Util\Manifest\TestManifestFactory; use tests\unit\Util\SuiteDataArrayBuilder; use tests\unit\Util\TestDataArrayBuilder; use tests\unit\Util\TestLoggingUtil; -use tests\unit\Util\MockModuleResolverBuilder; class SuiteGeneratorTest extends MagentoTestCase { /** - * Setup entry append and clear for Suite Generator - */ - public static function setUpBeforeClass() - { - AspectMock::double(SuiteGenerator::class, [ - 'clearPreviousSessionConfigEntries' => null, - 'appendEntriesToConfig' => null - ]); - } - - /** - * Before test functionality + * Before test functionality. + * * @return void */ - public function setUp() + protected function setUp(): void { TestLoggingUtil::getInstance()->setMockLoggingUtil(); - $resolverMock = new MockModuleResolverBuilder(); - $resolverMock->setup(); } /** - * Tests generating a single suite given a set of parsed test data - * @throws \Exception + * Tests generating a single suite given a set of parsed test data. + * + * @return void + * @throws Exception */ - public function testGenerateSuite() + public function testGenerateSuite(): void { $suiteDataArrayBuilder = new SuiteDataArrayBuilder(); $mockData = $suiteDataArrayBuilder @@ -67,26 +63,28 @@ public function testGenerateSuite() ->withTestActions() ->build(); - $mockTestData = ['tests' => array_merge($mockSimpleTest)]; + $mockTestData = array_merge($mockSimpleTest); $this->setMockTestAndSuiteParserOutput($mockTestData, $mockData); // parse and generate suite object with mocked data $mockSuiteGenerator = SuiteGenerator::getInstance(); - $mockSuiteGenerator->generateSuite("basicTestSuite"); + $mockSuiteGenerator->generateSuite('basicTestSuite'); // assert that expected suite is generated TestLoggingUtil::getInstance()->validateMockLogStatement( 'info', - "suite generated", - ['suite' => 'basicTestSuite', 'relative_path' => "_generated" . DIRECTORY_SEPARATOR . "basicTestSuite"] + 'suite generated', + ['suite' => 'basicTestSuite', 'relative_path' => '_generated' . DIRECTORY_SEPARATOR . 'basicTestSuite'] ); } /** - * Tests generating all suites given a set of parsed test data - * @throws \Exception + * Tests generating all suites given a set of parsed test data. + * + * @return void + * @throws Exception */ - public function testGenerateAllSuites() + public function testGenerateAllSuites(): void { $suiteDataArrayBuilder = new SuiteDataArrayBuilder(); $mockData = $suiteDataArrayBuilder @@ -103,28 +101,37 @@ public function testGenerateAllSuites() ->withTestActions() ->build(); - $mockTestData = ['tests' => array_merge($mockSimpleTest)]; + $mockTestData = array_merge($mockSimpleTest); $this->setMockTestAndSuiteParserOutput($mockTestData, $mockData); // parse and retrieve suite object with mocked data - $exampleTestManifest = new DefaultTestManifest([], "sample" . DIRECTORY_SEPARATOR . "path"); + $exampleTestManifest = new DefaultTestManifest([], 'sample' . DIRECTORY_SEPARATOR . 'path'); $mockSuiteGenerator = SuiteGenerator::getInstance(); $mockSuiteGenerator->generateAllSuites($exampleTestManifest); // assert that expected suites are generated TestLoggingUtil::getInstance()->validateMockLogStatement( 'info', - "suite generated", - ['suite' => 'basicTestSuite', 'relative_path' => "_generated" . DIRECTORY_SEPARATOR . "basicTestSuite"] + 'suite generated', + ['suite' => 'basicTestSuite', 'relative_path' => '_generated' . DIRECTORY_SEPARATOR . 'basicTestSuite'] ); } /** - * Tests attempting to generate a suite with no included/excluded tests and no hooks - * @throws \Exception + * Tests attempting to generate a suite with no included/excluded tests and no hooks. + * + * @return void + * @throws Exception */ - public function testGenerateEmptySuite() + public function testGenerateEmptySuite(): void { + $testDataArrayBuilder = new TestDataArrayBuilder(); + $mockTestData = $testDataArrayBuilder + ->withName('test') + ->withAnnotations() + ->withTestActions() + ->build(); + $suiteDataArrayBuilder = new SuiteDataArrayBuilder(); $mockData = $suiteDataArrayBuilder ->withName('basicTestSuite') @@ -132,82 +139,279 @@ public function testGenerateEmptySuite() unset($mockData['suites']['basicTestSuite'][TestObjectExtractor::TEST_BEFORE_HOOK]); unset($mockData['suites']['basicTestSuite'][TestObjectExtractor::TEST_AFTER_HOOK]); - $mockTestData = null; $this->setMockTestAndSuiteParserOutput($mockTestData, $mockData); // set expected error message - $this->expectExceptionMessage("Suites must not be empty. Suite: \"basicTestSuite\""); + $this->expectExceptionMessage('Suite basicTestSuite is not defined in xml or is invalid'); // parse and generate suite object with mocked data $mockSuiteGenerator = SuiteGenerator::getInstance(); - $mockSuiteGenerator->generateSuite("basicTestSuite"); + $mockSuiteGenerator->generateSuite('basicTestSuite'); + } + + /** + * Tests generating all suites with a suite containing invalid test reference. + * + * @return void + * @throws TestReferenceException + */ + public function testInvalidSuiteTestPair(): void + { + // Mock Suite1 => Test1 and Suite2 => Test2 + $suiteDataArrayBuilder = new SuiteDataArrayBuilder(); + $mockData = $suiteDataArrayBuilder + ->withName('Suite1') + ->includeGroups(['group1']) + ->build(); + $suiteDataArrayBuilder = new SuiteDataArrayBuilder(); + $mockData2 = $suiteDataArrayBuilder + ->withName('Suite2') + ->includeGroups(['group2']) + ->build(); + $mockSuiteData = array_merge_recursive($mockData, $mockData2); + + $testDataArrayBuilder = new TestDataArrayBuilder(); + $mockSimpleTest = $testDataArrayBuilder + ->withName('Test1') + ->withAnnotations(['group' => [['value' => 'group1']]]) + ->withTestActions() + ->build(); + $testDataArrayBuilder = new TestDataArrayBuilder(); + $mockSimpleTest2 = $testDataArrayBuilder + ->withName('Test2') + ->withAnnotations(['group' => [['value' => 'group2']]]) + ->withTestActions() + ->build(); + $mockTestData = array_merge($mockSimpleTest, $mockSimpleTest2); + $this->setMockTestAndSuiteParserOutput($mockTestData, $mockSuiteData); + + // Make invalid manifest + $suiteConfig = ['Suite2' => ['Test1']]; + $manifest = TestManifestFactory::makeManifest('default', $suiteConfig); + + // parse and generate suite object with mocked data and manifest + $mockSuiteGenerator = SuiteGenerator::getInstance(); + $mockSuiteGenerator->generateAllSuites($manifest); + + // assert that no exception for generateAllSuites and suite generation error is stored in GenerationErrorHandler + $errMessage = 'Cannot reference tests which are not declared as part of suite (Suite: "Suite2" Tests: "Test1")'; + TestLoggingUtil::getInstance()->validateMockLogStatement('error', $errMessage, []); + $suiteErrors = GenerationErrorHandler::getInstance()->getErrorsByType('suite'); + $this->assertArrayHasKey('Suite2', $suiteErrors); + } + + /** + * Tests generating all suites with a non-existing suite. + * + * @return void + * @throws TestReferenceException + */ + public function testNonExistentSuiteTestPair(): void + { + $testDataArrayBuilder = new TestDataArrayBuilder(); + $mockSimpleTest = $testDataArrayBuilder + ->withName('Test1') + ->withAnnotations(['group' => [['value' => 'group1']]]) + ->withTestActions() + ->build(); + $mockTestData = array_merge($mockSimpleTest); + $this->setMockTestAndSuiteParserOutput($mockTestData, []); + + // Make invalid manifest + $suiteConfig = ['Suite3' => ['Test1']]; + $manifest = TestManifestFactory::makeManifest('default', $suiteConfig); + + // parse and generate suite object with mocked data and manifest + $mockSuiteGenerator = SuiteGenerator::getInstance(); + $mockSuiteGenerator->generateAllSuites($manifest); + + // assert that no exception for generateAllSuites and suite generation error is stored in GenerationErrorHandler + $errMessage = 'Suite Suite3 is not defined in xml or is invalid.'; + TestLoggingUtil::getInstance()->validateMockLogStatement('error', $errMessage, []); + $suiteErrors = GenerationErrorHandler::getInstance()->getErrorsByType('suite'); + $this->assertArrayHasKey('Suite3', $suiteErrors); + } + + /** + * Tests generating split suites for parallel test generation. + * + * @return void + * @throws TestReferenceException + */ + public function testGenerateSplitSuiteFromTest(): void + { + $suiteDataArrayBuilder = new SuiteDataArrayBuilder(); + $mockSuiteData = $suiteDataArrayBuilder + ->withName('mockSuite') + ->includeGroups(['group1']) + ->build(); + $testDataArrayBuilder = new TestDataArrayBuilder(); + $mockSimpleTest1 = $testDataArrayBuilder + ->withName('simpleTest1') + ->withAnnotations(['group' => [['value' => 'group1']]]) + ->withTestReference("NonExistantTest") + ->withTestActions() + ->build(); + $mockSimpleTest2 = $testDataArrayBuilder + ->withName('simpleTest2') + ->withAnnotations(['group' => [['value' => 'group1']]]) + ->withTestActions() + ->build(); + $mockSimpleTest3 = $testDataArrayBuilder + ->withName('simpleTest3') + ->withAnnotations(['group' => [['value' => 'group1']]]) + ->withTestActions() + ->build(); + $mockTestData = array_merge($mockSimpleTest1, $mockSimpleTest2, $mockSimpleTest3); + $this->setMockTestAndSuiteParserOutput($mockTestData, $mockSuiteData); + + // Make manifest for split suites + $suiteConfig = [ + 'mockSuite' => [ + 'mockSuite_0_G' => ['simpleTest1', 'simpleTest2'], + 'mockSuite_1_G' => ['simpleTest3'], + ], + ]; + $manifest = TestManifestFactory::makeManifest('default', $suiteConfig); + + // parse and generate suite object with mocked data and manifest + $mockSuiteGenerator = SuiteGenerator::getInstance(); + $mockSuiteGenerator->generateAllSuites($manifest); + + // assert last split suite group generated + TestLoggingUtil::getInstance()->validateMockLogStatement( + 'info', + 'suite generated', + ['suite' => 'mockSuite_1_G', 'relative_path' => '_generated' . DIRECTORY_SEPARATOR . 'mockSuite_1_G'] + ); } /** * Function used to set mock for parser return and force init method to run between tests. * * @param array $testData - * @throws \Exception + * @param array $suiteData + * + * @return void + * @throws Exception */ - private function setMockTestAndSuiteParserOutput($testData, $suiteData) + private function setMockTestAndSuiteParserOutput(array $testData, array $suiteData): void { - $property = new \ReflectionProperty(SuiteGenerator::class, 'instance'); + $this->clearMockResolverProperties(); + $mockSuiteGeneratorService = $this->createMock(SuiteGeneratorService::class); + $mockVoidReturnCallback = function () {};// phpcs:ignore + + $mockSuiteGeneratorService + ->method('clearPreviousSessionConfigEntries') + ->will($this->returnCallback($mockVoidReturnCallback)); + + $mockSuiteGeneratorService + ->method('appendEntriesToConfig') + ->will($this->returnCallback($mockVoidReturnCallback)); + + $mockSuiteGeneratorService + ->method('generateRelevantGroupTests') + ->will($this->returnCallback($mockVoidReturnCallback)); + + $suiteGeneratorServiceProperty = new ReflectionProperty(SuiteGeneratorService::class, 'INSTANCE'); + $suiteGeneratorServiceProperty->setAccessible(true); + $suiteGeneratorServiceProperty->setValue($mockSuiteGeneratorService); + + $mockDataParser = $this->createMock(TestDataParser::class); + $mockDataParser + ->method('readTestData') + ->willReturn($testData); + + $mockSuiteDataParser = $this->createMock(SuiteDataParser::class); + $mockSuiteDataParser + ->method('readSuiteData') + ->willReturn($suiteData); + + $mockGroupClass = $this->createMock(GroupClassGenerator::class); + $mockGroupClass + ->method('generateGroupClass') + ->willReturn('namespace'); + + $objectManager = ObjectManagerFactory::getObjectManager(); + $objectManagerMockInstance = $this->createMock(ObjectManager::class); + $objectManagerMockInstance + ->method('create') + ->will( + $this->returnCallback( + function ( + string $class, + array $arguments = [] + ) use ( + $mockDataParser, + $mockSuiteDataParser, + $mockGroupClass, + $objectManager + ) { + if ($class === TestDataParser::class) { + return $mockDataParser; + } + if ($class === SuiteDataParser::class) { + return $mockSuiteDataParser; + } + if ($class === GroupClassGenerator::class) { + return $mockGroupClass; + } + + return $objectManager->create($class, $arguments); + } + ) + ); + + $objectManagerProperty = new ReflectionProperty(ObjectManager::class, 'instance'); + $objectManagerProperty->setAccessible(true); + $objectManagerProperty->setValue($objectManagerMockInstance); + } + + /** + * Function used to clear mock properties. + * + * @return void + */ + private function clearMockResolverProperties(): void + { + $property = new ReflectionProperty(SuiteGenerator::class, 'instance'); $property->setAccessible(true); $property->setValue(null); // clear test object handler value to inject parsed content - $property = new \ReflectionProperty(TestObjectHandler::class, 'testObjectHandler'); + $property = new ReflectionProperty(TestObjectHandler::class, 'testObjectHandler'); $property->setAccessible(true); $property->setValue(null); // clear suite object handler value to inject parsed content - $property = new \ReflectionProperty(SuiteObjectHandler::class, 'instance'); + $property = new ReflectionProperty(SuiteObjectHandler::class, 'instance'); $property->setAccessible(true); $property->setValue(null); + } - $mockDataParser = AspectMock::double(TestDataParser::class, ['readTestData' => $testData])->make(); - $mockSuiteDataParser = AspectMock::double(SuiteDataParser::class, ['readSuiteData' => $suiteData])->make(); - $mockGroupClass = AspectMock::double( - GroupClassGenerator::class, - ['generateGroupClass' => 'namespace'] - )->make(); - $mockSuiteClass = AspectMock::double(SuiteGenerator::class, ['generateRelevantGroupTests' => null])->make(); - $instance = AspectMock::double( - ObjectManager::class, - ['create' => function ($clazz) use ( - $mockDataParser, - $mockSuiteDataParser, - $mockGroupClass, - $mockSuiteClass - ) { - if ($clazz == TestDataParser::class) { - return $mockDataParser; - } - if ($clazz == SuiteDataParser::class) { - return $mockSuiteDataParser; - } - if ($clazz == GroupClassGenerator::class) { - return $mockGroupClass; - } - if ($clazz == SuiteGenerator::class) { - return $mockSuiteClass; - } - }] - )->make(); - // bypass the private constructor - AspectMock::double(ObjectManagerFactory::class, ['getObjectManager' => $instance]); - - $property = new \ReflectionProperty(SuiteGenerator::class, 'groupClassGenerator'); - $property->setAccessible(true); - $property->setValue($instance, $instance); + /** + * @inheritDoc + */ + protected function tearDown(): void + { + GenerationErrorHandler::getInstance()->reset(); } /** - * clean up function runs after all tests + * @inheritDoc */ - public static function tearDownAfterClass() + public static function tearDownAfterClass(): void { - TestLoggingUtil::getInstance()->clearMockLoggingUtil(); parent::tearDownAfterClass(); + + $objectManagerProperty = new ReflectionProperty(ObjectManager::class, 'instance'); + $objectManagerProperty->setAccessible(true); + $objectManagerProperty->setValue(null); + + $suiteGeneratorServiceProperty = new ReflectionProperty(SuiteGeneratorService::class, 'INSTANCE'); + $suiteGeneratorServiceProperty->setAccessible(true); + $suiteGeneratorServiceProperty->setValue(null); + + TestLoggingUtil::getInstance()->clearMockLoggingUtil(); } } diff --git a/dev/tests/unit/Magento/FunctionalTestFramework/Test/Config/ActionGroupDomTest.php b/dev/tests/unit/Magento/FunctionalTestFramework/Test/Config/ActionGroupDomTest.php index 6a0c926be..cf29b62b5 100644 --- a/dev/tests/unit/Magento/FunctionalTestFramework/Test/Config/ActionGroupDomTest.php +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Test/Config/ActionGroupDomTest.php @@ -3,12 +3,12 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ -namespace Tests\unit\Magento\FunctionalTestFramework\Test\Config; +namespace tests\unit\Magento\FunctionalTestFramework\Test\Config; use Magento\FunctionalTestingFramework\Exceptions\Collector\ExceptionCollector; use Magento\FunctionalTestingFramework\Config\Dom\ValidationException; use Magento\FunctionalTestingFramework\Test\Config\ActionGroupDom; -use Magento\FunctionalTestingFramework\Util\MagentoTestCase; +use tests\unit\Util\MagentoTestCase; class ActionGroupDomTest extends MagentoTestCase { @@ -65,7 +65,7 @@ public function testActionGroupDomDuplicateActionGroupsValidation() $exceptionCollector = new ExceptionCollector(); new ActionGroupDom($sampleXml, 'dupeNameActionGroup.xml', $exceptionCollector); $this->expectException(\Exception::class); - $this->expectExceptionMessageRegExp("/name: actionGroupName is used more than once./"); + $this->expectExceptionMessageMatches("/name: actionGroupName is used more than once./"); $exceptionCollector->throwException(); } } diff --git a/dev/tests/unit/Magento/FunctionalTestFramework/Test/Config/DomTest.php b/dev/tests/unit/Magento/FunctionalTestFramework/Test/Config/DomTest.php index e00926978..5601dce10 100644 --- a/dev/tests/unit/Magento/FunctionalTestFramework/Test/Config/DomTest.php +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Test/Config/DomTest.php @@ -3,12 +3,12 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ -namespace Tests\unit\Magento\FunctionalTestFramework\Test\Config; +namespace tests\unit\Magento\FunctionalTestFramework\Test\Config; use Magento\FunctionalTestingFramework\Exceptions\Collector\ExceptionCollector; use Magento\FunctionalTestingFramework\Config\Dom\ValidationException; use Magento\FunctionalTestingFramework\Test\Config\ActionGroupDom; -use Magento\FunctionalTestingFramework\Util\MagentoTestCase; +use tests\unit\Util\MagentoTestCase; class DomTest extends MagentoTestCase { @@ -28,7 +28,7 @@ public function testTestStepKeyDuplicateValidation() new ActionGroupDom($sampleXml, 'dupeStepKeyTest.xml', $exceptionCollector); $this->expectException(\Exception::class); - $this->expectExceptionMessageRegExp("/stepKey: key1 is used more than once. \(Parent: testName\)/"); + $this->expectExceptionMessageMatches("/stepKey: key1 is used more than once. \(Parent: testName\)/"); $exceptionCollector->throwException(); } @@ -49,7 +49,7 @@ public function testTestNameDuplicateValidation() $exceptionCollector = new ExceptionCollector(); new ActionGroupDom($sampleXml, 'dupeTestsTest.xml', $exceptionCollector); $this->expectException(\Exception::class); - $this->expectExceptionMessageRegExp("/name: testName is used more than once./"); + $this->expectExceptionMessageMatches("/name: testName is used more than once./"); $exceptionCollector->throwException(); } } diff --git a/dev/tests/unit/Magento/FunctionalTestFramework/Test/Handlers/ActionGroupObjectHandlerTest.php b/dev/tests/unit/Magento/FunctionalTestFramework/Test/Handlers/ActionGroupObjectHandlerTest.php index 4a7c6101e..c91d12a29 100644 --- a/dev/tests/unit/Magento/FunctionalTestFramework/Test/Handlers/ActionGroupObjectHandlerTest.php +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Test/Handlers/ActionGroupObjectHandlerTest.php @@ -3,27 +3,32 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); -namespace Tests\unit\Magento\FunctionalTestFramework\Test\Handlers; +namespace tests\unit\Magento\FunctionalTestFramework\Test\Handlers; -use AspectMock\Test as AspectMock; - -use Go\Aop\Aspect; +use Exception; +use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; use Magento\FunctionalTestingFramework\ObjectManager; use Magento\FunctionalTestingFramework\ObjectManagerFactory; use Magento\FunctionalTestingFramework\Test\Handlers\ActionGroupObjectHandler; -use Magento\FunctionalTestingFramework\Util\MagentoTestCase; -use tests\unit\Util\ActionGroupArrayBuilder; use Magento\FunctionalTestingFramework\Test\Parsers\ActionGroupDataParser; +use ReflectionProperty; +use tests\unit\Util\ActionGroupArrayBuilder; +use tests\unit\Util\MagentoTestCase; +/** + * Class ActionGroupObjectHandlerTest + */ class ActionGroupObjectHandlerTest extends MagentoTestCase { /** - * getObject should throw exception if test extends from itself + * Validate getObject should throw exception if test extends from itself. * - * @throws \Exception + * @return void + * @throws Exception */ - public function testGetTestObjectWithInvalidExtends() + public function testGetTestObjectWithInvalidExtends(): void { // Set up action group data $nameOne = 'actionGroupOne'; @@ -34,21 +39,22 @@ public function testGetTestObjectWithInvalidExtends() ->withFilename() ->withActionObjects() ->build(); - $this->setMockParserOutput(['actionGroups' => $actionGroupOne]); + $this->mockActionGroupObjectHandlerWithData(['actionGroups' => $actionGroupOne]); $handler = ActionGroupObjectHandler::getInstance(); - $this->expectException(\Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException::class); - $this->expectExceptionMessage("Mftf Action Group can not extend from itself: " . $nameOne); + $this->expectException(TestFrameworkException::class); + $this->expectExceptionMessage('Mftf Action Group can not extend from itself: ' . $nameOne); $handler->getObject('actionGroupOne'); } /** - * getAllObjects should throw exception if test extends from itself + * Validate getAllObjects should throw exception if test extends from itself. * - * @throws \Exception + * @return void + * @throws Exception */ - public function testGetAllTestObjectsWithInvalidExtends() + public function testGetAllTestObjectsWithInvalidExtends(): void { // Set up action group data $nameOne = 'actionGroupOne'; @@ -68,31 +74,79 @@ public function testGetAllTestObjectsWithInvalidExtends() ->withActionObjects() ->build(); - $this->setMockParserOutput(['actionGroups' => array_merge($actionGroupOne, $actionGroupTwo)]); + $this->mockActionGroupObjectHandlerWithData( + [ + 'actionGroups' => array_merge( + $actionGroupOne, + $actionGroupTwo + ) + ] + ); $handler = ActionGroupObjectHandler::getInstance(); - $this->expectException(\Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException::class); - $this->expectExceptionMessage("Mftf Action Group can not extend from itself: " . $nameOne); + $this->expectException(TestFrameworkException::class); + $this->expectExceptionMessage('Mftf Action Group can not extend from itself: ' . $nameOne); $handler->getAllObjects(); } /** - * Function used to set mock for parser return and force init method to run between tests. + * Create mock action group object handler with data. + * + * @param array $mockData * - * @param array $data - * @throws \Exception + * @return void */ - private function setMockParserOutput($data) + private function mockActionGroupObjectHandlerWithData(array $mockData): void { - // Clear action group object handler value to inject parsed content - $property = new \ReflectionProperty(ActionGroupObjectHandler::class, 'instance'); + $actionGroupObjectHandlerProperty = new ReflectionProperty(ActionGroupObjectHandler::class, 'instance'); + $actionGroupObjectHandlerProperty->setAccessible(true); + $actionGroupObjectHandlerProperty->setValue(null); + + $mockOperationParser = $this->createMock(ActionGroupDataParser::class); + $mockOperationParser + ->method('readActionGroupData') + ->willReturn($mockData); + $objectManager = ObjectManagerFactory::getObjectManager(); + $mockObjectManagerInstance = $this->createMock(ObjectManager::class); + $mockObjectManagerInstance + ->method('create') + ->will( + $this->returnCallback( + function ( + string $class, + array $arguments = [] + ) use ( + $objectManager, + $mockOperationParser + ) { + if ($class === ActionGroupDataParser::class) { + return $mockOperationParser; + } + + return $objectManager->create($class, $arguments); + } + ) + ); + + $property = new ReflectionProperty(ObjectManager::class, 'instance'); $property->setAccessible(true); - $property->setValue(null); + $property->setValue($mockObjectManagerInstance); + } + + /** + * @inheritDoc + */ + public static function tearDownAfterClass(): void + { + parent::tearDownAfterClass(); + + $actionGroupObjectHandlerProperty = new ReflectionProperty(ActionGroupObjectHandler::class, 'instance'); + $actionGroupObjectHandlerProperty->setAccessible(true); + $actionGroupObjectHandlerProperty->setValue(null); - $mockDataParser = AspectMock::double(ActionGroupDataParser::class, ['readActionGroupData' => $data])->make(); - $instance = AspectMock::double(ObjectManager::class, ['create' => $mockDataParser]) - ->make(); // bypass the private constructor - AspectMock::double(ObjectManagerFactory::class, ['getObjectManager' => $instance]); + $objectManagerProperty = new ReflectionProperty(ObjectManager::class, 'instance'); + $objectManagerProperty->setAccessible(true); + $objectManagerProperty->setValue(null); } } diff --git a/dev/tests/unit/Magento/FunctionalTestFramework/Test/Handlers/TestObjectHandlerTest.php b/dev/tests/unit/Magento/FunctionalTestFramework/Test/Handlers/TestObjectHandlerTest.php index 39e80d556..a2779bdd1 100644 --- a/dev/tests/unit/Magento/FunctionalTestFramework/Test/Handlers/TestObjectHandlerTest.php +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Test/Handlers/TestObjectHandlerTest.php @@ -3,11 +3,13 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); -namespace Tests\unit\Magento\FunctionalTestFramework\Test\Handlers; - -use AspectMock\Test as AspectMock; +namespace tests\unit\Magento\FunctionalTestFramework\Test\Handlers; +use Exception; +use Magento\FunctionalTestingFramework\Config\MftfApplicationConfig; +use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; use Magento\FunctionalTestingFramework\ObjectManager; use Magento\FunctionalTestingFramework\ObjectManagerFactory; use Magento\FunctionalTestingFramework\Test\Handlers\TestObjectHandler; @@ -16,18 +18,33 @@ use Magento\FunctionalTestingFramework\Test\Objects\TestObject; use Magento\FunctionalTestingFramework\Test\Parsers\TestDataParser; use Magento\FunctionalTestingFramework\Test\Util\TestObjectExtractor; -use Magento\FunctionalTestingFramework\Util\MagentoTestCase; +use Magento\FunctionalTestingFramework\Util\GenerationErrorHandler; +use Magento\FunctionalTestingFramework\Util\ModuleResolver; +use ReflectionProperty; +use tests\unit\Util\MagentoTestCase; use tests\unit\Util\TestDataArrayBuilder; -use tests\unit\Util\MockModuleResolverBuilder; +use tests\unit\Util\TestLoggingUtil; class TestObjectHandlerTest extends MagentoTestCase { + /** + * Before test functionality. + * + * @return void + * @throws Exception + */ + protected function setUp(): void + { + TestLoggingUtil::getInstance()->setMockLoggingUtil(); + } + /** * Basic test to validate array => test object conversion. * - * @throws \Exception + * @return void + * @throws Exception */ - public function testGetTestObject() + public function testGetTestObject(): void { // set up mock data $testDataArrayBuilder = new TestDataArrayBuilder(); @@ -39,13 +56,10 @@ public function testGetTestObject() ->withTestActions() ->build(); - $resolverMock = new MockModuleResolverBuilder(); - $resolverMock->setup(); - $this->setMockParserOutput(['tests' => $mockData]); + $this->mockTestObjectHandler($mockData); // run object handler method $toh = TestObjectHandler::getInstance(); - $mockConfig = AspectMock::double(TestObjectHandler::class, ['initTestData' => false]); $actualTestObject = $toh->getObject($testDataArrayBuilder->testName); // perform asserts @@ -78,7 +92,7 @@ public function testGetTestObject() $expectedFailedHookObject = new TestHookObject( TestObjectExtractor::TEST_FAILED_HOOK, $testDataArrayBuilder->testName, - [$expectedFailedActionObject] + ["saveScreenshot" => $expectedFailedActionObject] ); $expectedTestActionObject = new ActionObject( @@ -92,7 +106,7 @@ public function testGetTestObject() [ 'features' => ['NO MODULE DETECTED'], 'group' => ['test'], - 'description' => ['<br><br><b><font size=+0.9>Test files</font></b><br><br>'] + 'description' => ['test_files' => '<h3>Test files</h3>', 'deprecated' => []] ], [ TestObjectExtractor::TEST_BEFORE_HOOK => $expectedBeforeHookObject, @@ -106,20 +120,22 @@ public function testGetTestObject() } /** - * Tests basic getting of a test that has a fileName + * Tests basic getting of a test that has a fileName. + * + * @return void */ - public function testGetTestWithFileName() + public function testGetTestWithFileName(): void { - $this->markTestIncomplete(); - //TODO + $this->markTestIncomplete('TODO'); } /** * Tests the function used to get a series of relevant tests by group. * - * @throws \Exception + * @return void + * @throws Exception */ - public function testGetTestsByGroup() + public function testGetTestsByGroup(): void { // set up mock data with Exclude Test $includeTest = (new TestDataArrayBuilder()) @@ -133,9 +149,7 @@ public function testGetTestsByGroup() ->withTestActions() ->build(); - $resolverMock = new MockModuleResolverBuilder(); - $resolverMock->setup(); - $this->setMockParserOutput(['tests' => array_merge($includeTest, $excludeTest)]); + $this->mockTestObjectHandler(array_merge($includeTest, $excludeTest)); // execute test method $toh = TestObjectHandler::getInstance(); @@ -148,11 +162,12 @@ public function testGetTestsByGroup() } /** - * Tests the function used to parse and determine a test's Module (used in allure Features annotation) + * Tests the function used to parse and determine a test's Module (used in allure Features annotation). * - * @throws \Exception + * @return void + * @throws Exception */ - public function testGetTestWithModuleName() + public function testGetTestWithModuleName(): void { // set up Test Data $moduleExpected = "SomeModuleName"; @@ -181,10 +196,8 @@ public function testGetTestWithModuleName() ->withFileName($file) ->build(); - $resolverMock = new MockModuleResolverBuilder(); - $resolverMock->setup(['Vendor_' . $moduleExpected => $filepath]); + $this->mockTestObjectHandler($mockData, ['Vendor_' . $moduleExpected => $filepath]); - $this->setMockParserOutput(['tests' => $mockData]); // Execute Test Method $toh = TestObjectHandler::getInstance(); $actualTestObject = $toh->getObject($testDataArrayBuilder->testName); @@ -194,11 +207,12 @@ public function testGetTestWithModuleName() } /** - * getObject should throw exception if test extends from itself + * getObject should throw exception if test extends from itself. * - * @throws \Exception + * @return void + * @throws Exception */ - public function testGetTestObjectWithInvalidExtends() + public function testGetTestObjectWithInvalidExtends(): void { // set up Test Data $testOne = (new TestDataArrayBuilder()) @@ -210,24 +224,23 @@ public function testGetTestObjectWithInvalidExtends() ->withBeforeHook() ->withTestActions() ->build(); - $resolverMock = new MockModuleResolverBuilder(); - $resolverMock->setup(); - $this->setMockParserOutput(['tests' => $testOne]); - $toh = TestObjectHandler::getInstance(); + $this->mockTestObjectHandler($testOne); - $this->expectException(\Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException::class); + $toh = TestObjectHandler::getInstance(); + $this->expectException(TestFrameworkException::class); $this->expectExceptionMessage("Mftf Test can not extend from itself: " . "testOne"); $toh->getObject('testOne'); } /** - * getAllObjects should throw exception if test extends from itself + * getAllObjects should throw exception if test extends from itself. * - * @throws \Exception + * @return void + * @throws Exception */ - public function testGetAllTestObjectsWithInvalidExtends() + public function testGetAllTestObjectsWithInvalidExtends(): void { // set up Test Data $testOne = (new TestDataArrayBuilder()) @@ -248,43 +261,190 @@ public function testGetAllTestObjectsWithInvalidExtends() ->withTestActions() ->build(); - $resolverMock = new MockModuleResolverBuilder(); - $resolverMock->setup(); - $this->setMockParserOutput(['tests' => array_merge($testOne, $testTwo)]); + $this->mockTestObjectHandler(array_merge($testOne, $testTwo)); $toh = TestObjectHandler::getInstance(); - - $this->expectException(\Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException::class); - $this->expectExceptionMessage("Mftf Test can not extend from itself: " . "testOne"); $toh->getAllObjects(); + + // assert that no exception for getAllObjects and test generation error is stored in GenerationErrorHandler + $errorMessage = '/' . preg_quote("Mftf Test can not extend from itself: " . "testOne") . '/'; + TestLoggingUtil::getInstance()->validateMockLogStatmentRegex('error', $errorMessage, []); + $testErrors = GenerationErrorHandler::getInstance()->getErrorsByType('test'); + $this->assertArrayHasKey('testOne', $testErrors); } /** - * Function used to set mock for parser return and force init method to run between tests. + * Validate test object when ENABLE_PAUSE is set to true. * - * @param array $data - * @throws \Exception + * @return void + * @throws Exception */ - private function setMockParserOutput($data) + public function testGetTestObjectWhenEnablePause(): void { - // clear test object handler value to inject parsed content - $property = new \ReflectionProperty(TestObjectHandler::class, 'testObjectHandler'); - $property->setAccessible(true); - $property->setValue(null); + // set up mock data + putenv('ENABLE_PAUSE=true'); + $testDataArrayBuilder = new TestDataArrayBuilder(); + $mockData = $testDataArrayBuilder + ->withAnnotations() + ->withFailedHook() + ->withAfterHook() + ->withBeforeHook() + ->withTestActions() + ->build(); + + $this->mockTestObjectHandler($mockData); + + // run object handler method + $toh = TestObjectHandler::getInstance(); + $actualTestObject = $toh->getObject($testDataArrayBuilder->testName); + + // perform asserts + $expectedBeforeActionObject = new ActionObject( + $testDataArrayBuilder->testActionBeforeName, + $testDataArrayBuilder->testActionType, + [] + ); + $expectedAfterActionObject = new ActionObject( + $testDataArrayBuilder->testActionAfterName, + $testDataArrayBuilder->testActionType, + [] + ); + $expectedFailedActionObject1 = new ActionObject( + 'saveScreenshot', + 'saveScreenshot', + [] + ); + $expectedFailedActionObject2 = new ActionObject( + 'pauseWhenFailed', + 'pause', + [ActionObject::PAUSE_ACTION_INTERNAL_ATTRIBUTE => true] + ); - $mockDataParser = AspectMock::double(TestDataParser::class, ['readTestData' => $data])->make(); - $instance = AspectMock::double(ObjectManager::class, ['create' => $mockDataParser]) - ->make(); // bypass the private constructor - AspectMock::double(ObjectManagerFactory::class, ['getObjectManager' => $instance]); + $expectedBeforeHookObject = new TestHookObject( + TestObjectExtractor::TEST_BEFORE_HOOK, + $testDataArrayBuilder->testName, + ["testActionBefore" => $expectedBeforeActionObject] + ); + $expectedAfterHookObject = new TestHookObject( + TestObjectExtractor::TEST_AFTER_HOOK, + $testDataArrayBuilder->testName, + ["testActionAfter" => $expectedAfterActionObject] + ); + $expectedFailedHookObject = new TestHookObject( + TestObjectExtractor::TEST_FAILED_HOOK, + $testDataArrayBuilder->testName, + [ + "saveScreenshot" => $expectedFailedActionObject1, + "pauseWhenFailed" => $expectedFailedActionObject2, + ] + ); + + $expectedTestActionObject = new ActionObject( + $testDataArrayBuilder->testTestActionName, + $testDataArrayBuilder->testActionType, + [] + ); + $expectedTestObject = new TestObject( + $testDataArrayBuilder->testName, + ["testActionInTest" => $expectedTestActionObject], + [ + 'features' => ['NO MODULE DETECTED'], + 'group' => ['test'], + 'description' => ['test_files' => '<h3>Test files</h3>', 'deprecated' => []] + ], + [ + TestObjectExtractor::TEST_BEFORE_HOOK => $expectedBeforeHookObject, + TestObjectExtractor::TEST_AFTER_HOOK => $expectedAfterHookObject, + TestObjectExtractor::TEST_FAILED_HOOK => $expectedFailedHookObject + ], + null + ); + + $this->assertEquals($expectedTestObject, $actualTestObject); + putenv('ENABLE_PAUSE'); + } + + /** + * After method functionality. + * + * @return void + */ + protected function tearDown(): void + { + TestLoggingUtil::getInstance()->clearMockLoggingUtil(); + parent::tearDownAfterClass(); } /** - * After method functionality + * Mock test object handler. + * + * @param array $data + * @param array|null $paths * * @return void */ - public function tearDown() + private function mockTestObjectHandler(array $data, ?array $paths = null): void { - AspectMock::clean(); + if (!$paths) { + $paths = ['Magento_Module' => '/base/path/some/other/path/Magento/Module']; + } + // clear test object handler value to inject parsed content + $property = new ReflectionProperty(TestObjectHandler::class, 'testObjectHandler'); + $property->setAccessible(true); + $property->setValue(null); + + $mockDataParser = $this->createMock(TestDataParser::class); + $mockDataParser + ->method('readTestData') + ->willReturn($data); + + $mockConfig = $this->createMock(MftfApplicationConfig::class); + $mockConfig + ->method('forceGenerateEnabled') + ->willReturn(false); + + $mockResolver = $this->createMock(ModuleResolver::class); + $mockResolver + ->method('getEnabledModules') + ->willReturn([]); + + $objectManager = ObjectManagerFactory::getObjectManager(); + $objectManagerMockInstance = $this->createMock(ObjectManager::class); + $objectManagerMockInstance + ->method('create') + ->will( + $this->returnCallback( + function ( + $class, + $arguments = [] + ) use ( + $objectManager, + $mockDataParser, + $mockConfig, + $mockResolver + ) { + if ($class === TestDataParser::class) { + return $mockDataParser; + } + if ($class === MftfApplicationConfig::class) { + return $mockConfig; + } + if ($class === ModuleResolver::class) { + return $mockResolver; + } + + return $objectManager->create($class, $arguments); + } + ) + ); + + $objectManagerProperty = new ReflectionProperty(ObjectManager::class, 'instance'); + $objectManagerProperty->setAccessible(true); + $objectManagerProperty->setValue($objectManagerMockInstance); + + $resolver = ModuleResolver::getInstance(); + $property = new ReflectionProperty(ModuleResolver::class, 'enabledModuleNameAndPaths'); + $property->setAccessible(true); + $property->setValue($resolver, $paths); } } diff --git a/dev/tests/unit/Magento/FunctionalTestFramework/Test/Objects/ActionGroupObjectTest.php b/dev/tests/unit/Magento/FunctionalTestFramework/Test/Objects/ActionGroupObjectTest.php index 796e8a28b..c66303ba6 100644 --- a/dev/tests/unit/Magento/FunctionalTestFramework/Test/Objects/ActionGroupObjectTest.php +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Test/Objects/ActionGroupObjectTest.php @@ -3,10 +3,10 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); namespace tests\unit\Magento\FunctionalTestFramework\Test\Objects; -use AspectMock\Test as AspectMock; use Magento\FunctionalTestingFramework\DataGenerator\Handlers\DataObjectHandler; use Magento\FunctionalTestingFramework\Exceptions\TestReferenceException; use Magento\FunctionalTestingFramework\Page\Handlers\SectionObjectHandler; @@ -15,9 +15,10 @@ use Magento\FunctionalTestingFramework\Test\Objects\ActionGroupObject; use Magento\FunctionalTestingFramework\Test\Objects\ActionObject; use Magento\FunctionalTestingFramework\Test\Objects\ArgumentObject; -use Magento\FunctionalTestingFramework\Util\MagentoTestCase; +use ReflectionProperty; use tests\unit\Util\ActionGroupObjectBuilder; use tests\unit\Util\EntityDataObjectBuilder; +use tests\unit\Util\MagentoTestCase; use tests\unit\Util\TestLoggingUtil; class ActionGroupObjectTest extends MagentoTestCase @@ -25,18 +26,22 @@ class ActionGroupObjectTest extends MagentoTestCase const ACTION_GROUP_MERGE_KEY = 'TestKey'; /** - * Before test functionality + * Before test functionality. + * * @return void */ - public function setUp() + protected function setUp(): void { TestLoggingUtil::getInstance()->setMockLoggingUtil(); } /** - * Tests a string literal in an action group + * Tests a string literal in an action group. + * + * @return void + * @throws TestReferenceException */ - public function testGetStepsWithDefaultCase() + public function testGetStepsWithDefaultCase(): void { $entity = (new EntityDataObjectBuilder()) ->withDataFields(['field1' => 'testValue']) @@ -44,126 +49,187 @@ public function testGetStepsWithDefaultCase() $this->setEntityObjectHandlerReturn($entity); $actionGroupUnderTest = (new ActionGroupObjectBuilder())->build(); $steps = $actionGroupUnderTest->getSteps(null, self::ACTION_GROUP_MERGE_KEY); - $this->assertOnMergeKeyAndActionValue($steps, ['userInput' => 'literal']); + $this->assertOnMergeKeyAndActionValue($steps, ['userInput' => 'literal', 'requiredCredentials' => '']); } /** - * Tests a data reference in an action group, replaced by the user + * Tests a data reference in an action group, replaced by the user. + * + * @return void + * @throws TestReferenceException */ - public function testGetStepsWithCustomArgs() + public function testGetStepsWithCustomArgs(): void { $this->setEntityObjectHandlerReturn(function ($entityName) { - if ($entityName == "data2") { + if ($entityName === 'data2') { return (new EntityDataObjectBuilder())->withDataFields(['field2' => 'testValue2'])->build(); } }); $actionGroupUnderTest = (new ActionGroupObjectBuilder()) - ->withActionObjects([new ActionObject('action1', 'testAction', ['userInput' => '{{arg1.field2}}'])]) - ->withArguments([new ArgumentObject('arg1', null, 'entity')]) - ->build(); + ->withActionObjects([new ActionObject( + 'action1', + 'testAction', + [ + 'userInput' => '{{arg1.field2}}','requiredCredentials' => '' + ] + )]) + ->withArguments([new ArgumentObject('arg1', null, 'entity')]) + ->build(); $steps = $actionGroupUnderTest->getSteps(['arg1' => 'data2'], self::ACTION_GROUP_MERGE_KEY); - $this->assertOnMergeKeyAndActionValue($steps, ['userInput' => 'testValue2']); + $this->assertOnMergeKeyAndActionValue($steps, ['userInput' => 'testValue2','requiredCredentials' => '']); // entity.field as argument $actionGroupUnderTest = (new ActionGroupObjectBuilder()) - ->withActionObjects([new ActionObject('action1', 'testAction', ['userInput' => '{{arg1}}'])]) + ->withActionObjects([new ActionObject( + 'action1', + 'testAction', + ['userInput' => '{{arg1}}', + 'requiredCredentials' => '' + ] + )]) ->withArguments([new ArgumentObject('arg1', null, 'entity')]) ->build(); $steps = $actionGroupUnderTest->getSteps(['arg1' => 'data2.field2'], self::ACTION_GROUP_MERGE_KEY); - $this->assertOnMergeKeyAndActionValue($steps, ['userInput' => 'testValue2']); + $this->assertOnMergeKeyAndActionValue($steps, ['userInput' => 'testValue2', 'requiredCredentials' => '']); // String Data $actionGroupUnderTest = (new ActionGroupObjectBuilder()) - ->withActionObjects([new ActionObject('action1', 'testAction', ['userInput' => '{{simple}}'])]) + ->withActionObjects([new ActionObject( + 'action1', + 'testAction', + ['userInput' => '{{simple}}', + 'requiredCredentials' => '' + ] + )]) ->withArguments([new ArgumentObject('simple', null, 'string')]) ->build(); $steps = $actionGroupUnderTest->getSteps(['simple' => 'override'], self::ACTION_GROUP_MERGE_KEY); - $this->assertOnMergeKeyAndActionValue($steps, ['userInput' => 'override']); + $this->assertOnMergeKeyAndActionValue($steps, ['userInput' => 'override', 'requiredCredentials' => '']); } /** * Tests a data reference in an action group replaced with a persisted reference. + * + * @return void + * @throws TestReferenceException */ - public function testGetStepsWithPersistedArgs() + public function testGetStepsWithPersistedArgs(): void { $actionGroupUnderTest = (new ActionGroupObjectBuilder()) - ->withActionObjects([new ActionObject('action1', 'testAction', ['userInput' => '{{arg1.field2}}'])]) + ->withActionObjects([new ActionObject( + 'action1', + 'testAction', + ['userInput' => '{{arg1.field2}}', + 'requiredCredentials' => ''] + )]) ->withArguments([new ArgumentObject('arg1', null, 'entity')]) ->build(); $steps = $actionGroupUnderTest->getSteps(['arg1' => '$data3$'], self::ACTION_GROUP_MERGE_KEY); - $this->assertOnMergeKeyAndActionValue($steps, ['userInput' => '$data3.field2$']); + $this->assertOnMergeKeyAndActionValue($steps, ['userInput' => '$data3.field2$','requiredCredentials' => '']); // Simple Data $actionGroupUnderTest = (new ActionGroupObjectBuilder()) - ->withActionObjects([new ActionObject('action1', 'testAction', ['userInput' => '{{simple}}'])]) + ->withActionObjects([new ActionObject( + 'action1', + 'testAction', + ['userInput' => '{{simple}}', + 'requiredCredentials' => '' + ] + )]) ->withArguments([new ArgumentObject('simple', null, 'string')]) ->build(); $steps = $actionGroupUnderTest->getSteps(['simple' => '$data3.field2$'], self::ACTION_GROUP_MERGE_KEY); - $this->assertOnMergeKeyAndActionValue($steps, ['userInput' => '$data3.field2$']); + $this->assertOnMergeKeyAndActionValue($steps, ['userInput' => '$data3.field2$','requiredCredentials' => '']); } /** * Tests a data reference in an action group replaced with a data.field reference. + * + * @return void + * @throws TestReferenceException */ - public function testGetStepsWithNoFieldArg() + public function testGetStepsWithNoFieldArg(): void { $this->setEntityObjectHandlerReturn(function ($entityName) { - if ($entityName == "data2") { + if ($entityName === 'data2') { return (new EntityDataObjectBuilder())->withDataFields(['field2' => 'testValue2'])->build(); } }); $actionGroupUnderTest = (new ActionGroupObjectBuilder()) - ->withActionObjects([new ActionObject('action1', 'testAction', ['userInput' => '{{arg1}}'])]) + ->withActionObjects([new ActionObject( + 'action1', + 'testAction', + ['userInput' => '{{arg1}}', + 'requiredCredentials' => '' + ] + )]) ->withArguments([new ArgumentObject('arg1', null, 'entity')]) ->build(); $steps = $actionGroupUnderTest->getSteps(['arg1' => 'data2.field2'], self::ACTION_GROUP_MERGE_KEY); - $this->assertOnMergeKeyAndActionValue($steps, ['userInput' => 'testValue2']); + $this->assertOnMergeKeyAndActionValue($steps, ['userInput' => 'testValue2','requiredCredentials' => '']); } /** * Tests a data reference in an action group resolved with its default state. + * + * @return void + * @throws TestReferenceException */ - public function testGetStepsWithNoArgs() + public function testGetStepsWithNoArgs(): void { $this->setEntityObjectHandlerReturn(function ($entityName) { - if ($entityName == "data1") { + if ($entityName === 'data1') { return (new EntityDataObjectBuilder())->withDataFields(['field1' => 'testValue'])->build(); } }); $actionGroupUnderTest = (new ActionGroupObjectBuilder()) - ->withActionObjects([new ActionObject('action1', 'testAction', ['userInput' => '{{data1.field1}}'])]) + ->withActionObjects([new ActionObject( + 'action1', + 'testAction', + ['userInput' => '{{data1.field1}}', + 'requiredCredentials' => '' + ] + )]) ->build(); $steps = $actionGroupUnderTest->getSteps(null, self::ACTION_GROUP_MERGE_KEY); - $this->assertOnMergeKeyAndActionValue($steps, ['userInput' => 'testValue']); + $this->assertOnMergeKeyAndActionValue($steps, ['userInput' => 'testValue','requiredCredentials' => '']); } /** * Tests a parameterized section reference in an action group resolved with user args. + * + * @return void + * @throws TestReferenceException */ - public function testGetStepsWithParameterizedArg() + public function testGetStepsWithParameterizedArg(): void { // Mock Entity Object Handler $this->setEntityObjectHandlerReturn(function ($entityName) { - if ($entityName == "data2") { + if ($entityName === 'data2') { return (new EntityDataObjectBuilder())->withDataFields(['field2' => 'testValue2'])->build(); } }); // mock the section object handler response $element = new ElementObject("element1", "textArea", ".selector {{var1}}", null, null, true); $section = new SectionObject("testSection", ["element1" => $element]); + $sectionInstance = $this->createMock(SectionObjectHandler::class); + $sectionInstance + ->method('getObject') + ->willReturn($section); // bypass the private constructor - $sectionInstance = AspectMock::double(SectionObjectHandler::class, ['getObject' => $section])->make(); - AspectMock::double(SectionObjectHandler::class, ['getInstance' => $sectionInstance]); + $property = new ReflectionProperty(SectionObjectHandler::class, 'INSTANCE'); + $property->setAccessible(true); + $property->setValue($sectionInstance); $actionGroupUnderTest = (new ActionGroupObjectBuilder()) ->withActionObjects( @@ -174,30 +240,50 @@ public function testGetStepsWithParameterizedArg() // XML Data $steps = $actionGroupUnderTest->getSteps(['arg1' => 'data2'], self::ACTION_GROUP_MERGE_KEY); - $this->assertOnMergeKeyAndActionValue($steps, ['selector' => '.selector testValue2']); + $this->assertOnMergeKeyAndActionValue($steps, [ + 'selector' => '.selector testValue2', + 'requiredCredentials' => '' + ]); // Persisted Data - $steps = $actionGroupUnderTest->getSteps(['arg1' => '$data2$'], self::ACTION_GROUP_MERGE_KEY); - $this->assertOnMergeKeyAndActionValue($steps, ['selector' => '.selector $data2.field2$']); + $steps = $actionGroupUnderTest->getSteps( + ['arg1' => '$data2$'], + self::ACTION_GROUP_MERGE_KEY + ); + $this->assertOnMergeKeyAndActionValue( + $steps, + ['selector' => '.selector $data2.field2$', + 'requiredCredentials' => '' + ] + ); } /** * Tests a parameterized section reference in an action group resolved with user simpleArgs. + * + * @return void + * @throws TestReferenceException */ - public function testGetStepsWithParameterizedSimpleArg() + public function testGetStepsWithParameterizedSimpleArg(): void { // Mock Entity Object Handler $this->setEntityObjectHandlerReturn(function ($entityName) { - if ($entityName == "data2") { + if ($entityName === 'data2') { return (new EntityDataObjectBuilder())->withDataFields(['field2' => 'testValue2'])->build(); } }); // mock the section object handler response $element = new ElementObject("element1", "textArea", ".selector {{var1}}", null, null, true); $section = new SectionObject("testSection", ["element1" => $element]); + + $sectionInstance = $this->createMock(SectionObjectHandler::class); + $sectionInstance + ->method('getObject') + ->willReturn($section); // bypass the private constructor - $sectionInstance = AspectMock::double(SectionObjectHandler::class, ['getObject' => $section])->make(); - AspectMock::double(SectionObjectHandler::class, ['getInstance' => $sectionInstance]); + $property = new ReflectionProperty(SectionObjectHandler::class, 'INSTANCE'); + $property->setAccessible(true); + $property->setValue($sectionInstance); $actionGroupUnderTest = (new ActionGroupObjectBuilder()) ->withActionObjects( @@ -208,21 +294,37 @@ public function testGetStepsWithParameterizedSimpleArg() // String Literal $steps = $actionGroupUnderTest->getSteps(['simple' => 'stringLiteral'], self::ACTION_GROUP_MERGE_KEY); - $this->assertOnMergeKeyAndActionValue($steps, ['selector' => '.selector stringLiteral']); + $this->assertOnMergeKeyAndActionValue($steps, [ + 'selector' => '.selector stringLiteral', + 'requiredCredentials' => '' + ]); // String Literal w/ data-like structure $steps = $actionGroupUnderTest->getSteps(['simple' => 'data2.field2'], self::ACTION_GROUP_MERGE_KEY); - $this->assertOnMergeKeyAndActionValue($steps, ['selector' => '.selector data2.field2']); + $this->assertOnMergeKeyAndActionValue( + $steps, + ['selector' => '.selector data2.field2', + 'requiredCredentials' => '' + ] + ); // Persisted Data $steps = $actionGroupUnderTest->getSteps(['simple' => '$someData.field1$'], self::ACTION_GROUP_MERGE_KEY); - $this->assertOnMergeKeyAndActionValue($steps, ['selector' => '.selector $someData.field1$']); + $this->assertOnMergeKeyAndActionValue( + $steps, + ['selector' => '.selector $someData.field1$', + 'requiredCredentials' => '' + ] + ); } /** * Tests a data reference in an action group resolved with a persisted reference used in another function. + * + * @return void + * @throws TestReferenceException */ - public function testGetStepsWithOuterScopePersistence() + public function testGetStepsWithOuterScopePersistence(): void { $actionGroupUnderTest = (new ActionGroupObjectBuilder()) ->withActionObjects([new ActionObject('action1', 'testAction', ['userInput' => '{{arg1.field1}}'])]) @@ -230,42 +332,53 @@ public function testGetStepsWithOuterScopePersistence() ->build(); $steps = $actionGroupUnderTest->getSteps(['arg1' => '$$someData$$'], self::ACTION_GROUP_MERGE_KEY); - $this->assertOnMergeKeyAndActionValue($steps, ['userInput' => '$$someData.field1$$']); + $this->assertOnMergeKeyAndActionValue( + $steps, + ['userInput' => '$$someData.field1$$', + 'requiredCredentials' => '' + ] + ); } /** * Tests an action group with mismatching args. + * + * @return void */ - public function testExceptionOnMissingActions() + public function testExceptionOnMissingActions(): void { $actionGroupUnderTest = (new ActionGroupObjectBuilder()) ->withArguments([new ArgumentObject('arg1', null, 'entity')]) ->build(); $this->expectException(TestReferenceException::class); - $this->expectExceptionMessageRegExp('/Arguments missed .* for actionGroup/'); + $this->expectExceptionMessageMatches('/Arguments missed .* for actionGroup/'); $actionGroupUnderTest->getSteps(['arg2' => 'data1'], self::ACTION_GROUP_MERGE_KEY); } /** * Tests an action group with missing args. + * + * @return void */ - public function testExceptionOnMissingArguments() + public function testExceptionOnMissingArguments(): void { $actionGroupUnderTest = (new ActionGroupObjectBuilder()) ->withArguments([new ArgumentObject('arg1', null, 'entity')]) ->build(); $this->expectException(TestReferenceException::class); - $this->expectExceptionMessageRegExp('/Arguments missed .* for actionGroup/'); + $this->expectExceptionMessageMatches('/Arguments missed .* for actionGroup/'); $actionGroupUnderTest->getSteps(null, self::ACTION_GROUP_MERGE_KEY); } /** - * Tests the stepKey replacement with "stepKey + invocationKey" process filter - * Specific to actions that make it past a "require stepKey replacement" filter + * Tests the stepKey replacement with "stepKey + invocationKey" process filter. + * Specific to actions that make it past a "require stepKey replacement" filter. + * + * @return void */ - public function testStepKeyReplacementFilteredIn() + public function testStepKeyReplacementFilteredIn(): void { $createStepKey = "createDataStepKey"; $updateStepKey = "updateDataStepKey"; @@ -293,10 +406,12 @@ public function testStepKeyReplacementFilteredIn() } /** - * Tests the stepKey replacement with "stepKey + invocationKey" process filter - * Specific to actions that make are removed by a "require stepKey replacement" filter + * Tests the stepKey replacement with "stepKey + invocationKey" process filter. + * Specific to actions that make are removed by a "require stepKey replacement" filter. + * + * @return void */ - public function testStepKeyReplacementFilteredOut() + public function testStepKeyReplacementFilteredOut(): void { $clickStepKey = "clickStepKey"; $fillFieldStepKey = "fillFieldStepKey"; @@ -322,13 +437,26 @@ public function testStepKeyReplacementFilteredOut() * duration of a single test case. * * @param mixed $return + * * @return void */ - private function setEntityObjectHandlerReturn($return) + private function setEntityObjectHandlerReturn($return): void { - $instance = AspectMock::double(DataObjectHandler::class, ['getObject' => $return]) - ->make(); // bypass the private constructor - AspectMock::double(DataObjectHandler::class, ['getInstance' => $instance]); + $instance = $this->createMock(DataObjectHandler::class); + + if (is_callable($return)) { + $instance + ->method('getObject') + ->will($this->returnCallback($return)); + } else { + $instance + ->method('getObject') + ->willReturn($return); + } + // bypass the private constructor + $property = new ReflectionProperty(DataObjectHandler::class, 'INSTANCE'); + $property->setAccessible(true); + $property->setValue($instance); } /** @@ -337,11 +465,15 @@ private function setEntityObjectHandlerReturn($return) * * @param array $actions * @param array $expectedValue - * @param string $expectedMergeKey + * @param string|null $expectedMergeKey + * * @return void */ - private function assertOnMergeKeyAndActionValue($actions, $expectedValue, $expectedMergeKey = null) - { + private function assertOnMergeKeyAndActionValue( + array $actions, + array $expectedValue, + ?string $expectedMergeKey = null + ): void { $expectedMergeKey = $expectedMergeKey ?? ActionGroupObjectBuilder::DEFAULT_ACTION_OBJECT_NAME . self::ACTION_GROUP_MERGE_KEY; $this->assertArrayHasKey($expectedMergeKey, $actions); @@ -352,10 +484,11 @@ private function assertOnMergeKeyAndActionValue($actions, $expectedValue, $expec } /** - * After class functionality + * After class functionality. + * * @return void */ - public static function tearDownAfterClass() + public static function tearDownAfterClass(): void { TestLoggingUtil::getInstance()->clearMockLoggingUtil(); } diff --git a/dev/tests/unit/Magento/FunctionalTestFramework/Test/Objects/ActionObjectTest.php b/dev/tests/unit/Magento/FunctionalTestFramework/Test/Objects/ActionObjectTest.php index 511c9748f..d64de1cac 100644 --- a/dev/tests/unit/Magento/FunctionalTestFramework/Test/Objects/ActionObjectTest.php +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Test/Objects/ActionObjectTest.php @@ -3,21 +3,24 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); namespace tests\unit\Magento\FunctionalTestFramework\Test\Objects; -use AspectMock\Test as AspectMock; +use Exception; use Magento\FunctionalTestingFramework\DataGenerator\Handlers\DataObjectHandler; use Magento\FunctionalTestingFramework\DataGenerator\Objects\EntityDataObject; +use Magento\FunctionalTestingFramework\Exceptions\TestReferenceException; +use Magento\FunctionalTestingFramework\Exceptions\XmlException; use Magento\FunctionalTestingFramework\Page\Handlers\PageObjectHandler; +use Magento\FunctionalTestingFramework\Page\Handlers\SectionObjectHandler; use Magento\FunctionalTestingFramework\Page\Objects\ElementObject; use Magento\FunctionalTestingFramework\Page\Objects\PageObject; -use Magento\FunctionalTestingFramework\Test\Objects\ActionObject; -use Magento\FunctionalTestingFramework\Page\Handlers\SectionObjectHandler; use Magento\FunctionalTestingFramework\Page\Objects\SectionObject; -use Magento\FunctionalTestingFramework\Exceptions\TestReferenceException; +use Magento\FunctionalTestingFramework\Test\Objects\ActionObject; +use ReflectionProperty; +use tests\unit\Util\MagentoTestCase; use tests\unit\Util\TestLoggingUtil; -use Magento\FunctionalTestingFramework\Util\MagentoTestCase; /** * Class ActionObjectTest @@ -25,41 +28,51 @@ class ActionObjectTest extends MagentoTestCase { /** - * Before test functionality + * Before test functionality. + * * @return void */ - public function setUp() + protected function setUp(): void { TestLoggingUtil::getInstance()->setMockLoggingUtil(); } /** - * The order offset should be 0 when the action is instantiated with 'before' + * The order offset should be 0 when the action is instantiated with 'before'. + * + * @return void */ - public function testConstructOrderBefore() + public function testConstructOrderBefore(): void { $actionObject = new ActionObject('stepKey', 'type', [], null, 'before'); $this->assertEquals(0, $actionObject->getOrderOffset()); } /** - * The order offset should be 1 when the action is instantiated with 'after' + * The order offset should be 1 when the action is instantiated with 'after'. + * + * @return void */ - public function testConstructOrderAfter() + public function testConstructOrderAfter(): void { $actionObject = new ActionObject('stepKey', 'type', [], null, 'after'); $this->assertEquals(1, $actionObject->getOrderOffset()); } /** - * {{Section.element}} should be replaced with #theElementSelector + * {{Section.element}} should be replaced with #theElementSelector. + * + * @return void + * @throws TestReferenceException + * @throws XmlException */ - public function testResolveElementInSelector() + public function testResolveElementInSelector(): void { // Set up mocks $actionObject = new ActionObject('merge123', 'fillField', [ 'selector' => '{{SectionObject.elementObject}}', - 'userInput' => 'Hello world' + 'userInput' => 'Hello world', + 'requiredCredentials' => '' ]); $elementObject = new ElementObject('elementObject', 'button', '#replacementSelector', null, '42', false); $this->mockSectionHandlerWithElement($elementObject); @@ -70,19 +83,25 @@ public function testResolveElementInSelector() // Verify $expected = [ 'selector' => '#replacementSelector', - 'userInput' => 'Hello world' + 'userInput' => 'Hello world', + 'requiredCredentials' => '' ]; $this->assertEquals($expected, $actionObject->getCustomActionAttributes()); } /** - * {{Section.element(param)}} should replace correctly with 'stringLiterals' + * {{Section.element(param)}} should replace correctly with 'stringLiterals'. + * + * @return void + * @throws TestReferenceException + * @throws XmlException */ - public function testResolveSelectorWithOneStringLiteral() + public function testResolveSelectorWithOneStringLiteral(): void { $actionObject = new ActionObject('key123', 'fillField', [ 'selector' => "{{SectionObject.elementObject('stringliteral')}}", - 'userInput' => 'Input' + 'userInput' => 'Input', + 'requiredCredentials' => '' ]); $elementObject = new ElementObject('elementObject', 'button', '#{{var1}}', null, '42', true); $this->mockSectionHandlerWithElement($elementObject); @@ -93,19 +112,25 @@ public function testResolveSelectorWithOneStringLiteral() // Verify $expected = [ 'selector' => '#stringliteral', - 'userInput' => 'Input' + 'userInput' => 'Input', + 'requiredCredentials' => '' ]; $this->assertEquals($expected, $actionObject->getCustomActionAttributes()); } /** - * {{Section.element(param)}} should replace correctly with {{data.key}} references + * {{Section.element(param)}} should replace correctly with {{data.key}} references. + * + * @return void + * @throws TestReferenceException + * @throws XmlException */ - public function testResolveSelectorWithOneDataReference() + public function testResolveSelectorWithOneDataReference(): void { $actionObject = new ActionObject('key123', 'fillField', [ 'selector' => "{{SectionObject.elementObject(dataObject.key)}}", - 'userInput' => 'Input' + 'userInput' => 'Input', + 'requiredCredentials' => '' ]); // Mock SectionHandler @@ -122,19 +147,25 @@ public function testResolveSelectorWithOneDataReference() // Verify $expected = [ 'selector' => '#myValue', - 'userInput' => 'Input' + 'userInput' => 'Input', + 'requiredCredentials' => '' ]; $this->assertEquals($expected, $actionObject->getCustomActionAttributes()); } /** - * {{Section.element(param)}} should replace correctly with $data.key$ references + * {{Section.element(param)}} should replace correctly with $data.key$ references. + * + * @return void + * @throws TestReferenceException + * @throws XmlException */ - public function testResolveSelectorWithOnePersistedReference() + public function testResolveSelectorWithOnePersistedReference(): void { $actionObject = new ActionObject('key123', 'fillField', [ 'selector' => '{{SectionObject.elementObject($data.key$)}}', - 'userInput' => 'Input' + 'userInput' => 'Input', + 'requiredCredentials' => '' ]); // Mock SectionHandler @@ -147,15 +178,20 @@ public function testResolveSelectorWithOnePersistedReference() // Verify $expected = [ 'selector' => '#$data.key$', - 'userInput' => 'Input' + 'userInput' => 'Input', + 'requiredCredentials' => '' ]; $this->assertEquals($expected, $actionObject->getCustomActionAttributes()); } /** * {{Section.element(param1,param2,param3)}} should replace correctly with all 3 data types. + * + * @return void + * @throws TestReferenceException + * @throws XmlException */ - public function testResolveSelectorWithManyParams() + public function testResolveSelectorWithManyParams(): void { $actionObject = new ActionObject('key123', 'fillField', [ 'selector' => "{{SectionObject.elementObject('stringLiteral', data.key, \$data.key\$)}}", @@ -176,15 +212,20 @@ public function testResolveSelectorWithManyParams() // Verify $expected = [ 'selector' => '#stringLiteral[myValue,$data.key$]', - 'userInput' => 'Input' + 'userInput' => 'Input', + 'requiredCredentials' => '' ]; $this->assertEquals($expected, $actionObject->getCustomActionAttributes()); } /** - * Timeout property on the ActionObject should be set if the ElementObject has a timeout + * Timeout property on the ActionObject should be set if the ElementObject has a timeout. + * + * @return void + * @throws TestReferenceException + * @throws XmlException */ - public function testTimeoutFromElement() + public function testTimeoutFromElement(): void { // Set up mocks $actionObject = new ActionObject('merge123', 'click', [ @@ -201,92 +242,105 @@ public function testTimeoutFromElement() } /** - * {{PageObject.url}} should be replaced with someUrl.html + * {{PageObject.url}} should be replaced with someUrl.html. * - * @throws /Exception + * @return void + * @throws Exception */ - public function testResolveUrl() + public function testResolveUrl(): void { // Set up mocks $actionObject = new ActionObject('merge123', 'amOnPage', [ 'url' => '{{PageObject.url}}' ]); $pageObject = new PageObject('PageObject', '/replacement/url.html', 'Test', [], false, "test"); - $instance = AspectMock::double(PageObjectHandler::class, ['getObject' => $pageObject]) - ->make(); // bypass the private constructor - AspectMock::double(PageObjectHandler::class, ['getInstance' => $instance]); + + $instance = $this->createMock(PageObjectHandler::class); + $instance + ->method('getObject') + ->willReturn($pageObject); + // bypass the private constructor + $property = new ReflectionProperty(PageObjectHandler::class, 'INSTANCE'); + $property->setAccessible(true); + $property->setValue($instance); // Call the method under test $actionObject->resolveReferences(); // Verify $expected = [ - 'url' => '/replacement/url.html' + 'url' => '/replacement/url.html','requiredCredentials' => '' ]; $this->assertEquals($expected, $actionObject->getCustomActionAttributes()); } /** - * {{PageObject}} should not be replaced and should elicit a warning in console + * {{PageObject}} should not be replaced and should elicit a warning in console. * - * @throws /Exception + * @return void + * @throws Exception */ - public function testResolveUrlWithNoAttribute() + public function testResolveUrlWithNoAttribute(): void { + $this->expectException(TestReferenceException::class); + // Set up mocks $actionObject = new ActionObject('merge123', 'amOnPage', [ 'url' => '{{PageObject}}' ]); $pageObject = new PageObject('PageObject', '/replacement/url.html', 'Test', [], false, "test"); $pageObjectList = ["PageObject" => $pageObject]; - $instance = AspectMock::double( - PageObjectHandler::class, - ['getObject' => $pageObject, 'getAllObjects' => $pageObjectList] - )->make(); // bypass the private constructor - AspectMock::double(PageObjectHandler::class, ['getInstance' => $instance]); + + $instance = $this->createMock(PageObjectHandler::class); + $instance + ->method('getObject') + ->willReturn($pageObject); + $instance + ->method('getAllObjects') + ->willReturn($pageObjectList); + // bypass the private constructor + $property = new ReflectionProperty(PageObjectHandler::class, 'INSTANCE'); + $property->setAccessible(true); + $property->setValue($instance); // Call the method under test $actionObject->resolveReferences(); - - // Expect this warning to get generated - TestLoggingUtil::getInstance()->validateMockLogStatement( - "warning", - "page url attribute not found and is required", - ['action' => $actionObject->getType(), 'url' => '{{PageObject}}', 'stepKey' => $actionObject->getStepKey()] - ); - - // Verify - $expected = [ - 'url' => '{{PageObject}}' - ]; - $this->assertEquals($expected, $actionObject->getCustomActionAttributes()); } /** - * {{PageObject.url(param)}} should be replaced + * {{PageObject.url(param)}} should be replaced. + * + * @return void */ - public function testResolveUrlWithOneParam() + public function testResolveUrlWithOneParam(): void { $this->markTestIncomplete('TODO'); } /** - * {{PageObject.url(param1,param2,param3)}} should be replaced + * {{PageObject.url(param1,param2,param3)}} should be replaced. + * + * @return void */ - public function testResolveUrlWithManyParams() + public function testResolveUrlWithManyParams(): void { $this->markTestIncomplete('TODO'); } /** - * {{EntityDataObject.key}} should be replaced with someDataValue + * {{EntityDataObject.key}} should be replaced with someDataValue. + * + * @return void + * @throws TestReferenceException + * @throws XmlException */ - public function testResolveDataInUserInput() + public function testResolveDataInUserInput(): void { // Set up mocks $actionObject = new ActionObject('merge123', 'fillField', [ 'selector' => '#selector', - 'userInput' => '{{EntityDataObject.key}}' + 'userInput' => '{{EntityDataObject.key}}', + 'requiredCredentials' => '' ]); $entityDataObject = new EntityDataObject('EntityDataObject', 'test', [ 'key' => 'replacementData' @@ -299,20 +353,26 @@ public function testResolveDataInUserInput() // Verify $expected = [ 'selector' => '#selector', - 'userInput' => 'replacementData' + 'userInput' => 'replacementData', + 'requiredCredentials' => '' ]; $this->assertEquals($expected, $actionObject->getCustomActionAttributes()); } /** - * {{EntityDataObject.values}} should be replaced with ["value1","value2"] + * {{EntityDataObject.values}} should be replaced with ["value1","value2"]. + * + * @return void + * @throws TestReferenceException + * @throws XmlException */ - public function testResolveArrayData() + public function testResolveArrayData(): void { // Set up mocks $actionObject = new ActionObject('merge123', 'fillField', [ 'selector' => '#selector', - 'userInput' => '{{EntityDataObject.values}}' + 'userInput' => '{{EntityDataObject.values}}', + 'requiredCredentials' => '' ]); $entityDataObject = new EntityDataObject('EntityDataObject', 'test', [ 'values' => [ @@ -325,25 +385,30 @@ public function testResolveArrayData() // Call the method under test $actionObject->resolveReferences(); - - // Verify + //Verify $expected = [ 'selector' => '#selector', - 'userInput' => '["value1","value2","\"My\" Value"]' + 'userInput' => '["value1","value2","\"My\" Value"]', + 'requiredCredentials' => '' ]; $this->assertEquals($expected, $actionObject->getCustomActionAttributes()); } /** * Action object should throw an exception if a reference to a parameterized selector has too few given args. + * + * @return void + * @throws TestReferenceException + * @throws XmlException */ - public function testTooFewArgumentException() + public function testTooFewArgumentException(): void { $this->expectException(TestReferenceException::class); $actionObject = new ActionObject('key123', 'fillField', [ 'selector' => "{{SectionObject.elementObject('arg1')}}", - 'userInput' => 'Input' + 'userInput' => 'Input', + 'requiredCredentials' => '' ]); $elementObject = new ElementObject('elementObject', 'button', '#{{var1}} {{var2}}', null, '42', true); $this->mockSectionHandlerWithElement($elementObject); @@ -354,14 +419,19 @@ public function testTooFewArgumentException() /** * Action object should throw an exception if a reference to a parameterized selector has too many given args. + * + * @return void + * @throws TestReferenceException + * @throws XmlException */ - public function testTooManyArgumentException() + public function testTooManyArgumentException(): void { $this->expectException(TestReferenceException::class); $actionObject = new ActionObject('key123', 'fillField', [ 'selector' => "{{SectionObject.elementObject('arg1', 'arg2', 'arg3')}}", - 'userInput' => 'Input' + 'userInput' => 'Input', + 'requiredCredentials' => '' ]); $elementObject = new ElementObject('elementObject', 'button', '#{{var1}}', null, '42', true); $this->mockSectionHandlerWithElement($elementObject); @@ -372,39 +442,71 @@ public function testTooManyArgumentException() /** * Action object should throw an exception if the timezone provided is not valid. + * + * @return void + * @throws TestReferenceException + * @throws XmlException */ - public function testInvalidTimezoneException() + public function testInvalidTimezoneException(): void { $this->expectException(TestReferenceException::class); $actionObject = new ActionObject('key123', 'generateDate', [ - 'timezone' => "INVALID_TIMEZONE" + 'timezone' => "INVALID_TIMEZONE", + 'requiredCredentials' => '' ]); // Call the method under test $actionObject->resolveReferences(); } - private function mockSectionHandlerWithElement($elementObject) + /** + * Mock section handler with the specified ElementObject. + * + * @param ElementObject $elementObject + * + * @return void + * @throws Exception + */ + private function mockSectionHandlerWithElement(ElementObject $elementObject): void { $sectionObject = new SectionObject('SectionObject', ['elementObject' => $elementObject]); - $instance = AspectMock::double(SectionObjectHandler::class, ['getObject' => $sectionObject]) - ->make(); // bypass the private constructor - AspectMock::double(SectionObjectHandler::class, ['getInstance' => $instance]); + $instance = $this->createMock(SectionObjectHandler::class); + $instance + ->method('getObject') + ->willReturn($sectionObject); + // bypass the private constructor + $property = new ReflectionProperty(SectionObjectHandler::class, 'INSTANCE'); + $property->setAccessible(true); + $property->setValue($instance); } - private function mockDataHandlerWithData($dataObject) + /** + * Mock data handler with the specified EntityDataObject. + * + * @param EntityDataObject $dataObject + * + * @return void + * @throws Exception + */ + private function mockDataHandlerWithData(EntityDataObject $dataObject): void { - $dataInstance = AspectMock::double(DataObjectHandler::class, ['getObject' => $dataObject]) - ->make(); - AspectMock::double(DataObjectHandler::class, ['getInstance' => $dataInstance]); + $dataInstance = $this->createMock(DataObjectHandler::class); + $dataInstance + ->method('getObject') + ->willReturn($dataObject); + // bypass the private constructor + $property = new ReflectionProperty(DataObjectHandler::class, 'INSTANCE'); + $property->setAccessible(true); + $property->setValue($dataInstance); } /** - * After class functionality + * After class functionality. + * * @return void */ - public static function tearDownAfterClass() + public static function tearDownAfterClass(): void { TestLoggingUtil::getInstance()->clearMockLoggingUtil(); } diff --git a/dev/tests/unit/Magento/FunctionalTestFramework/Test/Util/ActionGroupAnnotationExtractorTest.php b/dev/tests/unit/Magento/FunctionalTestFramework/Test/Util/ActionGroupAnnotationExtractorTest.php index af39298b1..23c97fd4a 100644 --- a/dev/tests/unit/Magento/FunctionalTestFramework/Test/Util/ActionGroupAnnotationExtractorTest.php +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Test/Util/ActionGroupAnnotationExtractorTest.php @@ -3,95 +3,56 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace tests\unit\Magento\FunctionalTestFramework\Test\Util; -use AspectMock\Test as AspectMock; +use Exception; use Magento\FunctionalTestingFramework\Test\Util\ActionGroupAnnotationExtractor; use PHPUnit\Framework\TestCase; use tests\unit\Util\TestLoggingUtil; +/** + * Class ActionGroupAnnotationExtractorTest + */ class ActionGroupAnnotationExtractorTest extends TestCase { /** - * Before test functionality - * @return void + * @inheritDoc */ - public function setUp() + protected function setUp(): void { TestLoggingUtil::getInstance()->setMockLoggingUtil(); } /** - * Annotation extractor takes in raw array and condenses it to expected format + * Annotation extractor takes in raw array and condenses it to expected format. * - * @throws \Exception + * @return void + * @throws Exception */ - public function testActionGroupExtractAnnotations() + public function testActionGroupExtractAnnotations(): void { // Test Data $actionGroupAnnotations = [ - "nodeName" => "annotations", - "description" => [ - "nodeName" => "description", - "value" => "someDescription" + 'nodeName' => 'annotations', + 'description' => [ + 'nodeName' => 'description', + 'value' => 'someDescription' ] ]; // Perform Test $extractor = new ActionGroupAnnotationExtractor(); - $returnedAnnotations = $extractor->extractAnnotations($actionGroupAnnotations, "fileName"); + $returnedAnnotations = $extractor->extractAnnotations($actionGroupAnnotations, 'fileName'); // Asserts - $this->assertEquals("someDescription", $returnedAnnotations['description']); + $this->assertEquals('someDescription', $returnedAnnotations['description']); } /** - * Annotation extractor should throw warning when required annotations are missing - * - * @throws \Exception - */ - public function testActionGroupMissingAnnotations() - { - // Action Group Data, missing description - $testAnnotations = []; - // Perform Test - $extractor = new ActionGroupAnnotationExtractor(); - AspectMock::double($extractor, ['isCommandDefined' => true]); - $extractor->extractAnnotations($testAnnotations, "fileName"); - - // Asserts - TestLoggingUtil::getInstance()->validateMockLogStatement( - 'warning', - 'DEPRECATION: Action Group File fileName is missing required annotations.', - [ - 'actionGroup' => 'fileName', - 'missingAnnotations' => "description" - ] - ); - } - - /** - * Annotation extractor should not throw warning when required - * annotations are missing if command is not generate:docs - * - * @throws \Exception - */ - public function testActionGroupMissingAnnotationsNoWarning() - { - // Action Group Data, missing description - $testAnnotations = []; - // Perform Test - $extractor = new ActionGroupAnnotationExtractor(); - $extractor->extractAnnotations($testAnnotations, "fileName"); - - // Asserts - TestLoggingUtil::getInstance()->validateMockLogEmpty(); - } - - /** - * After class functionality - * @return void + * @inheritDoc */ - public static function tearDownAfterClass() + public static function tearDownAfterClass(): void { TestLoggingUtil::getInstance()->clearMockLoggingUtil(); } diff --git a/dev/tests/unit/Magento/FunctionalTestFramework/Test/Util/ActionGroupObjectExtractorTest.php b/dev/tests/unit/Magento/FunctionalTestFramework/Test/Util/ActionGroupObjectExtractorTest.php index 47b797c1c..1fea7e848 100644 --- a/dev/tests/unit/Magento/FunctionalTestFramework/Test/Util/ActionGroupObjectExtractorTest.php +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Test/Util/ActionGroupObjectExtractorTest.php @@ -6,7 +6,7 @@ namespace tests\unit\Magento\FunctionalTestFramework\Test\Util; use Magento\FunctionalTestingFramework\Test\Util\ActionGroupObjectExtractor; -use Magento\FunctionalTestingFramework\Util\MagentoTestCase; +use tests\unit\Util\MagentoTestCase; use tests\unit\Util\TestLoggingUtil; class ActionGroupObjectExtractorTest extends MagentoTestCase @@ -17,7 +17,7 @@ class ActionGroupObjectExtractorTest extends MagentoTestCase /** * Setup method */ - public function setUp() + public function setUp(): void { $this->testActionGroupObjectExtractor = new ActionGroupObjectExtractor(); TestLoggingUtil::getInstance()->setMockLoggingUtil(); @@ -34,23 +34,47 @@ public function testEmptyStepKey() $this->testActionGroupObjectExtractor->extractActionGroup($this->createBasicActionObjectArray("")); } + /** + * Tests deprecation message for an action group + */ + public function testDeprecationMessage() + { + $this->testActionGroupObjectExtractor->extractActionGroup( + $this->createBasicActionObjectArray( + "testDeprecatedAction1", + "actionGroup", + "filename1.xml", + "message" + ) + ); + + TestLoggingUtil::getInstance()->validateMockLogStatement( + 'warning', + "DEPRECATION: The action group 'actionGroup' is deprecated.", + ["fileName" => "filename1.xml", "deprecatedMessage" => "message"] + ); + } + /** * Utility function to return mock parser output for testing extraction into ActionObjects. * * @param string $stepKey * @param string $actionGroup * @param string $filename + * @param string $deprecated * @return array */ private function createBasicActionObjectArray( $stepKey = 'testAction1', $actionGroup = "actionGroup", - $filename = "filename.xml" + $filename = "filename.xml", + $deprecated = null ) { $baseArray = [ 'nodeName' => 'actionGroup', 'name' => $actionGroup, 'filename' => $filename, + 'deprecated' => $deprecated, $stepKey => [ "nodeName" => "sampleAction", "stepKey" => $stepKey, @@ -63,7 +87,7 @@ private function createBasicActionObjectArray( /** * clean up function runs after all tests */ - public static function tearDownAfterClass() + public static function tearDownAfterClass(): void { TestLoggingUtil::getInstance()->clearMockLoggingUtil(); } diff --git a/dev/tests/unit/Magento/FunctionalTestFramework/Test/Util/ActionMergeUtilTest.php b/dev/tests/unit/Magento/FunctionalTestFramework/Test/Util/ActionMergeUtilTest.php index 7917d663b..99152b8f0 100644 --- a/dev/tests/unit/Magento/FunctionalTestFramework/Test/Util/ActionMergeUtilTest.php +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Test/Util/ActionMergeUtilTest.php @@ -3,9 +3,10 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace tests\unit\Magento\FunctionalTestFramework\Test\Util; -use AspectMock\Test as AspectMock; use Magento\FunctionalTestingFramework\DataGenerator\Handlers\DataObjectHandler; use Magento\FunctionalTestingFramework\DataGenerator\Objects\EntityDataObject; use Magento\FunctionalTestingFramework\Exceptions\TestReferenceException; @@ -13,17 +14,18 @@ use Magento\FunctionalTestingFramework\Test\Objects\ActionObject; use Magento\FunctionalTestingFramework\Test\Util\ActionMergeUtil; use Magento\FunctionalTestingFramework\Test\Util\ActionObjectExtractor; -use Magento\FunctionalTestingFramework\Util\MagentoTestCase; -use tests\unit\Util\DataObjectHandlerReflectionUtil; +use ReflectionProperty; +use tests\unit\Util\MagentoTestCase; use tests\unit\Util\TestLoggingUtil; class ActionMergeUtilTest extends MagentoTestCase { /** - * Before test functionality + * Before test functionality. + * * @return void */ - public function setUp() + protected function setUp(): void { TestLoggingUtil::getInstance()->setMockLoggingUtil(); } @@ -32,8 +34,10 @@ public function setUp() * Test to validate actions are properly ordered during a merge. * * @return void + * @throws TestReferenceException + * @throws XmlException */ - public function testResolveActionStepOrdering() + public function testResolveActionStepOrdering(): void { $actions = []; $actionsLength = 11; @@ -47,7 +51,6 @@ public function testResolveActionStepOrdering() $stepKey = 'stepKey'. $i; $type = 'testType'; $actionAttributes = []; - $actions[] = new ActionObject($stepKey, $type, $actionAttributes); } @@ -94,8 +97,10 @@ public function testResolveActionStepOrdering() * Test to validate action steps properly resolve entity data references. * * @return void + * @throws TestReferenceException + * @throws XmlException */ - public function testResolveActionStepEntityData() + public function testResolveActionStepEntityData(): void { $dataObjectName = 'myObject'; $dataObjectType = 'testObject'; @@ -111,36 +116,35 @@ public function testResolveActionStepEntityData() $mockDataObject = new EntityDataObject($dataObjectName, $dataObjectType, $mockData, null, null, null); // Set up mock DataObject Handler - $mockDOHInstance = AspectMock::double(DataObjectHandler::class, ['getObject' => $mockDataObject])->make(); - AspectMock::double(DataObjectHandler::class, ['getInstance' => $mockDOHInstance]); + $mockDOHInstance = $this->createMock(DataObjectHandler::class); + $mockDOHInstance + ->expects($this->any()) + ->method('getObject') + ->willReturn($mockDataObject); + $property = new ReflectionProperty(DataObjectHandler::class, 'INSTANCE'); + $property->setAccessible(true); + $property->setValue($mockDOHInstance, $mockDOHInstance); // Create test object and action object - $actionAttributes = [$userInputKey => $userInputValue]; + $actionAttributes = [$userInputKey => $userInputValue,'requiredCredentials'=>'']; $actions[$actionName] = new ActionObject($actionName, $actionType, $actionAttributes); - $this->assertEquals($userInputValue, $actions[$actionName]->getCustomActionAttributes()[$userInputKey]); $mergeUtil = new ActionMergeUtil("test", "TestCase"); $resolvedActions = $mergeUtil->resolveActionSteps($actions); - $this->assertEquals($dataFieldValue, $resolvedActions[$actionName]->getCustomActionAttributes()[$userInputKey]); } /** * Verify that an XmlException is thrown when an action references a non-existant action. * - * @throws TestReferenceException - * @throws XmlException * @return void - */ - /** * @throws TestReferenceException * @throws XmlException */ - public function testNoActionException() + public function testNoActionException(): void { $actionObjects = []; - $actionObjects[] = new ActionObject('actionKey1', 'bogusType', []); $actionObjects[] = new ActionObject( 'actionKey2', @@ -150,8 +154,7 @@ public function testNoActionException() ActionObject::MERGE_ACTION_ORDER_BEFORE ); - $this->expectException(\Magento\FunctionalTestingFramework\Exceptions\XmlException::class); - + $this->expectException(XmlException::class); $actionMergeUtil = new ActionMergeUtil("actionMergeUtilTest", "TestCase"); $actionMergeUtil->resolveActionSteps($actionObjects); } @@ -159,11 +162,11 @@ public function testNoActionException() /** * Verify that a <waitForPageLoad> action is added after actions that have a wait (timeout property). * + * @return void * @throws TestReferenceException * @throws XmlException - * @return void */ - public function testInsertWait() + public function testInsertWait(): void { $actionObjectOne = new ActionObject('actionKey1', 'bogusType', []); $actionObjectOne->setTimeout(42); @@ -186,26 +189,26 @@ public function testInsertWait() /** * Verify that a <fillField> action is replaced by <fillSecretField> when secret _CREDS are referenced. * + * @return void * @throws TestReferenceException * @throws XmlException */ - public function testValidFillFieldSecretFunction() + public function testValidFillFieldSecretFunction(): void { $actionObjectOne = new ActionObject( 'actionKey1', 'fillField', - ['userInput' => '{{_CREDS.username}}'] + ['userInput' => '{{_CREDS.username}}', 'requiredCredentials' => 'username'] ); $actionObject = [$actionObjectOne]; $actionMergeUtil = new ActionMergeUtil('actionMergeUtilTest', 'TestCase'); - $result = $actionMergeUtil->resolveActionSteps($actionObject); $expectedValue = new ActionObject( 'actionKey1', 'fillSecretField', - ['userInput' => '{{_CREDS.username}}'] + ['userInput' => '{{_CREDS.username}}','requiredCredentials' => 'username'] ); $this->assertEquals($expectedValue, $result['actionKey1']); } @@ -213,26 +216,32 @@ public function testValidFillFieldSecretFunction() /** * Verify that a <magentoCLI> action uses <magentoCLI> when secret _CREDS are referenced. * + * @return void * @throws TestReferenceException * @throws XmlException */ - public function testValidMagentoCLISecretFunction() + public function testValidMagentoCLISecretFunction(): void { $actionObjectOne = new ActionObject( 'actionKey1', 'magentoCLI', - ['command' => 'config:set cms/wysiwyg/enabled {{_CREDS.payment_authorizenet_login}}'] + ['command' => + 'config:set cms/wysiwyg/enabled {{_CREDS.payment_authorizenet_login}}', + 'requiredCredentials' => '' + ] ); $actionObject = [$actionObjectOne]; $actionMergeUtil = new ActionMergeUtil('actionMergeUtilTest', 'TestCase'); - $result = $actionMergeUtil->resolveActionSteps($actionObject); $expectedValue = new ActionObject( 'actionKey1', 'magentoCLISecret', - ['command' => 'config:set cms/wysiwyg/enabled {{_CREDS.payment_authorizenet_login}}'] + ['command' => + 'config:set cms/wysiwyg/enabled {{_CREDS.payment_authorizenet_login}}', + 'requiredCredentials' => '' + ] ); $this->assertEquals($expectedValue, $result['actionKey1']); } @@ -240,26 +249,26 @@ public function testValidMagentoCLISecretFunction() /** * Verify that a <field> override in a <createData> action uses <field> when secret _CREDS are referenced. * + * @return void * @throws TestReferenceException * @throws XmlException */ - public function testValidCreateDataSecretFunction() + public function testValidCreateDataSecretFunction(): void { $actionObjectOne = new ActionObject( 'actionKey1', 'field', - ['value' => '{{_CREDS.payment_authorizenet_login}}'] + ['value' => '{{_CREDS.payment_authorizenet_login}}','requiredCredentials' => ''] ); $actionObject = [$actionObjectOne]; $actionMergeUtil = new ActionMergeUtil('actionMergeUtilTest', 'TestCase'); - $result = $actionMergeUtil->resolveActionSteps($actionObject); $expectedValue = new ActionObject( 'actionKey1', 'field', - ['value' => '{{_CREDS.payment_authorizenet_login}}'] + ['value' => '{{_CREDS.payment_authorizenet_login}}','requiredCredentials' => ''] ); $this->assertEquals($expectedValue, $result['actionKey1']); } @@ -267,10 +276,11 @@ public function testValidCreateDataSecretFunction() /** * Verify that a <click> action throws an exception when secret _CREDS are referenced. * + * @return void * @throws TestReferenceException * @throws XmlException */ - public function testInvalidSecretFunctions() + public function testInvalidSecretFunctions(): void { $this->expectException(TestReferenceException::class); $this->expectExceptionMessage( @@ -280,7 +290,7 @@ public function testInvalidSecretFunctions() $actionObjectOne = new ActionObject( 'actionKey1', 'click', - ['userInput' => '{{_CREDS.username}}'] + ['userInput' => '{{_CREDS.username}}','requiredCredentials' => 'username'] ); $actionObject = [$actionObjectOne]; @@ -289,10 +299,11 @@ public function testInvalidSecretFunctions() } /** - * After class functionality + * After class functionality. + * * @return void */ - public static function tearDownAfterClass() + public static function tearDownAfterClass(): void { TestLoggingUtil::getInstance()->clearMockLoggingUtil(); } diff --git a/dev/tests/unit/Magento/FunctionalTestFramework/Test/Util/ActionObjectExtractorTest.php b/dev/tests/unit/Magento/FunctionalTestFramework/Test/Util/ActionObjectExtractorTest.php index f584adea9..6f464b41e 100644 --- a/dev/tests/unit/Magento/FunctionalTestFramework/Test/Util/ActionObjectExtractorTest.php +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Test/Util/ActionObjectExtractorTest.php @@ -7,7 +7,7 @@ use Magento\FunctionalTestingFramework\Test\Objects\ActionObject; use Magento\FunctionalTestingFramework\Test\Util\ActionObjectExtractor; -use Magento\FunctionalTestingFramework\Util\MagentoTestCase; +use tests\unit\Util\MagentoTestCase; use tests\unit\Util\TestLoggingUtil; class ActionObjectExtractorTest extends MagentoTestCase @@ -18,7 +18,7 @@ class ActionObjectExtractorTest extends MagentoTestCase /** * Setup method */ - public function setUp() + public function setUp(): void { $this->testActionObjectExtractor = new ActionObjectExtractor(); TestLoggingUtil::getInstance()->setMockLoggingUtil(); @@ -130,7 +130,7 @@ private function createBasicActionObjectArray($stepKey = 'testAction1', $before /** * clean up function runs after all tests */ - public static function tearDownAfterClass() + public static function tearDownAfterClass(): void { TestLoggingUtil::getInstance()->clearMockLoggingUtil(); } diff --git a/dev/tests/unit/Magento/FunctionalTestFramework/Test/Util/AnnotationExtractorTest.php b/dev/tests/unit/Magento/FunctionalTestFramework/Test/Util/AnnotationExtractorTest.php index fb93cbb88..44ec3a7aa 100644 --- a/dev/tests/unit/Magento/FunctionalTestFramework/Test/Util/AnnotationExtractorTest.php +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Test/Util/AnnotationExtractorTest.php @@ -3,113 +3,182 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace tests\unit\Magento\FunctionalTestFramework\Test\Util; -use AspectMock\Proxy\Verifier; -use AspectMock\Test as AspectMock; +use Exception; +use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; +use Magento\FunctionalTestingFramework\Exceptions\XmlException; use Magento\FunctionalTestingFramework\Test\Util\AnnotationExtractor; -use Monolog\Handler\TestHandler; -use Monolog\Logger; +use Magento\FunctionalTestingFramework\Util\GenerationErrorHandler; use PHPUnit\Framework\TestCase; use tests\unit\Util\TestLoggingUtil; +/** + * Class AnnotationExtractorTest + */ class AnnotationExtractorTest extends TestCase { /** - * Before test functionality - * @return void + * @inheritDoc */ - public function setUp() + protected function setUp(): void { TestLoggingUtil::getInstance()->setMockLoggingUtil(); } /** - * Annotation extractor takes in raw array and condenses it to expected format + * Annotation extractor takes in raw array and condenses it to expected format. * - * @throws \Exception + * @return void + * @throws Exception */ - public function testExtractAnnotations() + public function testExtractAnnotations(): void { // Test Data $testAnnotations = [ - "nodeName" => "annotations", - "features" => [ + 'nodeName' => 'annotations', + 'features' => [ [ - "nodeName" => "features", - "value" => "TestFeatures" + 'nodeName' => 'features', + 'value' => 'TestFeatures' ] ], - "stories" => [ + 'stories' => [ [ - "nodeName" => "stories", - "value" => "TestStories" + 'nodeName' => 'stories', + 'value' => 'TestStories' ] ], - "description" => [ + 'description' => [ [ - "nodeName" => "description", - "value" => "TestDescription" + 'nodeName' => 'description', + 'value' => 'TestDescription' ] ], - "severity" => [ + 'severity' => [ [ - "nodeName" => "severity", - "value" => "CRITICAL" + 'nodeName' => 'severity', + 'value' => 'CRITICAL' ] ], - "group" => [ + 'group' => [ [ - "nodeName" => "group", - "value" => "TestGroup" + 'nodeName' => 'group', + 'value' => 'TestGroup' ] ], ]; // Perform Test $extractor = new AnnotationExtractor(); - $returnedAnnotations = $extractor->extractAnnotations($testAnnotations, "testFileName"); + $returnedAnnotations = $extractor->extractAnnotations($testAnnotations, 'testFileName'); // Asserts - $this->assertEquals("TestFeatures", $returnedAnnotations['features'][0]); - $this->assertEquals("TestStories", $returnedAnnotations['stories'][0]); - $this->assertEquals("TestDescription", $returnedAnnotations['description'][0]); - $this->assertEquals("CRITICAL", $returnedAnnotations['severity'][0]); - $this->assertEquals("TestGroup", $returnedAnnotations['group'][0]); + $this->assertEquals('TestFeatures', $returnedAnnotations['features'][0]); + $this->assertEquals('TestStories', $returnedAnnotations['stories'][0]); + $this->assertEquals('TestDescription', $returnedAnnotations['description'][0]); + $this->assertEquals('CRITICAL', $returnedAnnotations['severity'][0]); + $this->assertEquals('TestGroup', $returnedAnnotations['group'][0]); + } + + /** + * Annotation extractor should throw warning when required annotations are missing. + * + * @return void + * @throws Exception + */ + public function testMissingAnnotations(): void + { + // Test Data, missing title, description, and severity + $testAnnotations = [ + 'nodeName' => 'annotations', + 'features' => [ + [ + 'nodeName' => 'features', + 'value' => 'TestFeatures' + ] + ], + 'stories' => [ + [ + 'nodeName' => 'stories', + 'value' => 'TestStories' + ] + ], + 'group' => [ + [ + 'nodeName' => 'group', + 'value' => 'TestGroup' + ] + ], + ]; + // Perform Test + $extractor = new AnnotationExtractor(); + $extractor->extractAnnotations($testAnnotations, 'testFileName'); + + // Asserts + TestLoggingUtil::getInstance()->validateMockLogStatement( + 'warning', + 'DEPRECATION: Test testFileName is missing required annotations.', + [ + 'testName' => 'testFileName', + 'missingAnnotations' => 'title, description, severity' + ] + ); } /** - * Annotation extractor should throw warning when required annotations are missing + * Annotation extractor should throw warning when required annotations are empty. * - * @throws \Exception + * @return void + * @throws Exception */ - public function testMissingAnnotations() + public function testEmptyRequiredAnnotations(): void { // Test Data, missing title, description, and severity $testAnnotations = [ - "nodeName" => "annotations", - "features" => [ + 'nodeName' => 'annotations', + 'features' => [ + [ + 'nodeName' => 'features', + 'value' => '' + ] + ], + 'stories' => [ + [ + 'nodeName' => 'stories', + 'value' => 'TestStories' + ] + ], + 'title' => [ [ - "nodeName" => "features", - "value" => "TestFeatures" + 'nodeName' => 'title', + 'value' => ' ' ] ], - "stories" => [ + 'description' => [ [ - "nodeName" => "stories", - "value" => "TestStories" + 'nodeName' => 'description', + 'value' => "\t" ] ], - "group" => [ + 'severity' => [ [ - "nodeName" => "group", - "value" => "TestGroup" + 'nodeName' => 'severity', + 'value' => '' + ] + ], + 'group' => [ + [ + 'nodeName' => 'group', + 'value' => 'TestGroup' ] ], ]; // Perform Test $extractor = new AnnotationExtractor(); - $returnedAnnotations = $extractor->extractAnnotations($testAnnotations, "testFileName"); + $returnedAnnotations = $extractor->extractAnnotations($testAnnotations, 'testFileName'); // Asserts TestLoggingUtil::getInstance()->validateMockLogStatement( @@ -117,99 +186,115 @@ public function testMissingAnnotations() 'DEPRECATION: Test testFileName is missing required annotations.', [ 'testName' => 'testFileName', - 'missingAnnotations' => "title, description, severity" + 'missingAnnotations' => 'title, description, severity' ] ); } - public function testTestCaseIdUniqueness() + /** + * Validate testTestCaseIdUniqueness. + * + * @return void + * @throws TestFrameworkException|XmlException + */ + public function testTestCaseIdUniqueness(): void { // Test Data $firstTestAnnotation = [ - "nodeName" => "annotations", - "features" => [ + 'nodeName' => 'annotations', + 'features' => [ [ - "nodeName" => "features", - "value" => "TestFeatures" + 'nodeName' => 'features', + 'value' => 'TestFeatures' ] ], - "stories" => [ + 'stories' => [ [ - "nodeName" => "stories", - "value" => "TestStories" + 'nodeName' => 'stories', + 'value' => 'TestStories' ] ], - "title" => [ + 'title' => [ [ - "nodeName" => "title", - "value" => "TEST TITLE" + 'nodeName' => 'title', + 'value' => 'TEST TITLE' ] ], - "severity" => [ + 'severity' => [ [ - "nodeName" => "severity", - "value" => "CRITICAL" + 'nodeName' => 'severity', + 'value' => 'CRITICAL' ] ], - "testCaseId" => [ + 'testCaseId' => [ [ - "nodeName" => "testCaseId", - "value" => "MQE-0001" + 'nodeName' => 'testCaseId', + 'value' => 'MQE-0001' ] ], ]; $secondTestannotation = [ - "nodeName" => "annotations", - "features" => [ + 'nodeName' => 'annotations', + 'features' => [ [ - "nodeName" => "features", - "value" => "TestFeatures" + 'nodeName' => 'features', + 'value' => 'TestFeatures' ] ], - "stories" => [ + 'stories' => [ [ - "nodeName" => "stories", - "value" => "TestStories" + 'nodeName' => 'stories', + 'value' => 'TestStories' ] ], - "title" => [ + 'title' => [ [ - "nodeName" => "title", - "value" => "TEST TITLE" + 'nodeName' => 'title', + 'value' => 'TEST TITLE' ] ], - "severity" => [ + 'severity' => [ [ - "nodeName" => "severity", - "value" => "CRITICAL" + 'nodeName' => 'severity', + 'value' => 'CRITICAL' ] ], - "testCaseId" => [ + 'testCaseId' => [ [ - "nodeName" => "testCaseId", - "value" => "MQE-0001" + 'nodeName' => 'testCaseId', + 'value' => 'MQE-0001' ] ], ]; // Perform Test $extractor = new AnnotationExtractor(); - $extractor->extractAnnotations($firstTestAnnotation, "firstTest"); - $extractor->extractAnnotations($secondTestannotation, "secondTest"); + $extractor->extractAnnotations($firstTestAnnotation, 'firstTest'); + $extractor->extractAnnotations($secondTestannotation, 'secondTest'); + $extractor->validateTestCaseIdTitleUniqueness(); - //Expect Exception - $this->expectException(\Magento\FunctionalTestingFramework\Exceptions\XmlException::class); - $this->expectExceptionMessage("TestCaseId and Title pairs must be unique:\n\n" . - "TestCaseId: 'MQE-0001' Title: 'TEST TITLE' in Tests 'firstTest', 'secondTest'"); + // assert that no exception for validateTestCaseIdTitleUniqueness + // and validation error is stored in GenerationErrorHandler + $errorMessage = '/' + . preg_quote('TestCaseId and Title pairs is not unique in Tests \'firstTest\', \'secondTest\'') + . '/'; + TestLoggingUtil::getInstance()->validateMockLogStatmentRegex('error', $errorMessage, []); + $testErrors = GenerationErrorHandler::getInstance()->getErrorsByType('test'); + $this->assertArrayHasKey('firstTest', $testErrors); + $this->assertArrayHasKey('secondTest', $testErrors); + } - //Trigger Exception - $extractor->validateTestCaseIdTitleUniqueness(); + /** + * @inheritDoc + */ + protected function tearDown(): void + { + GenerationErrorHandler::getInstance()->reset(); } /** - * After class functionality - * @return void + * @inheritDoc */ - public static function tearDownAfterClass() + public static function tearDownAfterClass(): void { TestLoggingUtil::getInstance()->clearMockLoggingUtil(); } diff --git a/dev/tests/unit/Magento/FunctionalTestFramework/Test/Util/ObjectExtensionUtilTest.php b/dev/tests/unit/Magento/FunctionalTestFramework/Test/Util/ObjectExtensionUtilTest.php index d86185b83..b30e4b50e 100644 --- a/dev/tests/unit/Magento/FunctionalTestFramework/Test/Util/ObjectExtensionUtilTest.php +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Test/Util/ObjectExtensionUtilTest.php @@ -3,55 +3,54 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); namespace tests\unit\Magento\FunctionalTestFramework\Test\Util; -use AspectMock\Proxy\Verifier; -use AspectMock\Test as AspectMock; -use Magento\FunctionalTestingFramework\ObjectManager\ObjectManager; -use Magento\FunctionalTestingFramework\ObjectManagerFactory; +use Exception; +use Magento\FunctionalTestingFramework\ObjectManager; use Magento\FunctionalTestingFramework\Test\Handlers\ActionGroupObjectHandler; use Magento\FunctionalTestingFramework\Test\Handlers\TestObjectHandler; use Magento\FunctionalTestingFramework\Test\Parsers\ActionGroupDataParser; use Magento\FunctionalTestingFramework\Test\Parsers\TestDataParser; -use Magento\FunctionalTestingFramework\Util\Logger\LoggingUtil; -use Monolog\Handler\TestHandler; -use Monolog\Logger; use PHPUnit\Framework\TestCase; +use ReflectionProperty; use tests\unit\Util\TestDataArrayBuilder; use tests\unit\Util\TestLoggingUtil; -use tests\unit\Util\MockModuleResolverBuilder; class ObjectExtensionUtilTest extends TestCase { /** - * Before test functionality + * Before test functionality. + * * @return void + * @throws Exception */ - public function setUp() + protected function setUp(): void { TestLoggingUtil::getInstance()->setMockLoggingUtil(); - $resolverMock = new MockModuleResolverBuilder(); - $resolverMock->setup(); } /** - * After class functionality + * After class functionality. + * * @return void */ - public static function tearDownAfterClass() + public static function tearDownAfterClass(): void { TestLoggingUtil::getInstance()->clearMockLoggingUtil(); } /** - * Tests generating a test that extends another test - * @throws \Exception + * Tests generating a test that extends another test. + * + * @return void + * @throws Exception */ - public function testGenerateExtendedTest() + public function testGenerateExtendedTest(): void { $mockActions = [ - "mockStep" => ["nodeName" => "mockNode", "stepKey" => "mockStep"] + 'mockStep' => ['nodeName' => 'mockNode', 'stepKey' => 'mockStep'] ]; $testDataArrayBuilder = new TestDataArrayBuilder(); @@ -64,10 +63,10 @@ public function testGenerateExtendedTest() $mockExtendedTest = $testDataArrayBuilder ->withName('extendedTest') ->withAnnotations(['title' => [['value' => 'extendedTest']]]) - ->withTestReference("simpleTest") + ->withTestReference('simpleTest') ->build(); - $mockTestData = ['tests' => array_merge($mockSimpleTest, $mockExtendedTest)]; + $mockTestData = array_merge($mockSimpleTest, $mockExtendedTest); $this->setMockTestOutput($mockTestData); // parse and generate test object with mocked data @@ -81,21 +80,23 @@ public function testGenerateExtendedTest() ); // assert that expected test is generated - $this->assertEquals($testObject->getParentName(), "simpleTest"); - $this->assertArrayHasKey("mockStep", $testObject->getOrderedActions()); + $this->assertEquals($testObject->getParentName(), 'simpleTest'); + $this->assertArrayHasKey('mockStep', $testObject->getOrderedActions()); } /** - * Tests generating a test that extends another test - * @throws \Exception + * Tests generating a test that extends another test. + * + * @return void + * @throws Exception */ - public function testGenerateExtendedWithHooks() + public function testGenerateExtendedWithHooks(): void { $mockBeforeHooks = [ - "beforeHookAction" => ["nodeName" => "mockNodeBefore", "stepKey" => "mockStepBefore"] + 'beforeHookAction' => ['nodeName' => 'mockNodeBefore', 'stepKey' => 'mockStepBefore'] ]; $mockAfterHooks = [ - "afterHookAction" => ["nodeName" => "mockNodeAfter", "stepKey" => "mockStepAfter"] + 'afterHookAction' => ['nodeName' => 'mockNodeAfter', 'stepKey' => 'mockStepAfter'] ]; $testDataArrayBuilder = new TestDataArrayBuilder(); @@ -109,10 +110,10 @@ public function testGenerateExtendedWithHooks() $mockExtendedTest = $testDataArrayBuilder ->withName('extendedTest') ->withAnnotations(['title' => [['value' => 'extendedTest']]]) - ->withTestReference("simpleTest") + ->withTestReference('simpleTest') ->build(); - $mockTestData = ['tests' => array_merge($mockSimpleTest, $mockExtendedTest)]; + $mockTestData = array_merge($mockSimpleTest, $mockExtendedTest); $this->setMockTestOutput($mockTestData); // parse and generate test object with mocked data @@ -126,24 +127,26 @@ public function testGenerateExtendedWithHooks() ); // assert that expected test is generated - $this->assertEquals($testObject->getParentName(), "simpleTest"); - $this->assertArrayHasKey("mockStepBefore", $testObject->getHooks()['before']->getActions()); - $this->assertArrayHasKey("mockStepAfter", $testObject->getHooks()['after']->getActions()); + $this->assertEquals($testObject->getParentName(), 'simpleTest'); + $this->assertArrayHasKey('mockStepBefore', $testObject->getHooks()['before']->getActions()); + $this->assertArrayHasKey('mockStepAfter', $testObject->getHooks()['after']->getActions()); } /** - * Tests generating a test that extends another test - * @throws \Exception + * Tests generating a test that extends another test. + * + * @return void + * @throws Exception */ - public function testExtendedTestNoParent() + public function testExtendedTestNoParent(): void { $testDataArrayBuilder = new TestDataArrayBuilder(); $mockExtendedTest = $testDataArrayBuilder ->withName('extendedTest') - ->withTestReference("simpleTest") + ->withTestReference('simpleTest') ->build(); - $mockTestData = ['tests' => array_merge($mockExtendedTest)]; + $mockTestData = array_merge($mockExtendedTest); $this->setMockTestOutput($mockTestData); // parse and generate test object with mocked data @@ -152,16 +155,18 @@ public function testExtendedTestNoParent() // validate log statement TestLoggingUtil::getInstance()->validateMockLogStatement( 'debug', - "parent test not defined. test will be skipped", + 'parent test not defined. test will be skipped', ['parent' => 'simpleTest', 'test' => 'extendedTest'] ); } /** - * Tests generating a test that extends another test - * @throws \Exception + * Tests generating a test that extends another test. + * + * @return void + * @throws Exception */ - public function testExtendingExtendedTest() + public function testExtendingExtendedTest(): void { $testDataArrayBuilder = new TestDataArrayBuilder(); $mockParentTest = $testDataArrayBuilder @@ -173,19 +178,19 @@ public function testExtendingExtendedTest() ->withName('simpleTest') ->withAnnotations(['title' => [['value' => 'simpleTest']]]) ->withTestActions() - ->withTestReference("anotherTest") + ->withTestReference('anotherTest') ->build(); $mockExtendedTest = $testDataArrayBuilder ->withName('extendedTest') ->withAnnotations(['title' => [['value' => 'extendedTest']]]) - ->withTestReference("simpleTest") + ->withTestReference('simpleTest') ->build(); - $mockTestData = ['tests' => array_merge($mockParentTest, $mockSimpleTest, $mockExtendedTest)]; + $mockTestData = array_merge($mockParentTest, $mockSimpleTest, $mockExtendedTest); $this->setMockTestOutput($mockTestData); - $this->expectExceptionMessage("Cannot extend a test that already extends another test. Test: simpleTest"); + $this->expectExceptionMessage('Cannot extend a test that already extends another test. Test: simpleTest'); // parse and generate test object with mocked data TestObjectHandler::getInstance()->getObject('extendedTest'); @@ -193,43 +198,45 @@ public function testExtendingExtendedTest() // validate log statement TestLoggingUtil::getInstance()->validateMockLogStatement( 'debug', - "parent test not defined. test will be skipped", + 'parent test not defined. test will be skipped', ['parent' => 'simpleTest', 'test' => 'extendedTest'] ); - $this->expectOutputString("Extending Test: anotherTest => simpleTest" . PHP_EOL); + $this->expectOutputString('Extending Test: anotherTest => simpleTest' . PHP_EOL); } /** - * Tests generating an action group that extends another action group - * @throws \Exception + * Tests generating an action group that extends another action group. + * + * @return void + * @throws Exception */ - public function testGenerateExtendedActionGroup() + public function testGenerateExtendedActionGroup(): void { $mockSimpleActionGroup = [ - "nodeName" => "actionGroup", - "name" => "mockSimpleActionGroup", - "filename" => "someFile", - "commentHere" => [ - "nodeName" => "comment", - "selector" => "selector", - "stepKey" => "commentHere" + 'nodeName' => 'actionGroup', + 'name' => 'mockSimpleActionGroup', + 'filename' => 'someFile', + 'commentHere' => [ + 'nodeName' => 'comment', + 'selector' => 'selector', + 'stepKey' => 'commentHere' ], - "parentComment" => [ - "nodeName" => "comment", - "selector" => "parentSelector", - "stepKey" => "parentComment" + 'parentComment' => [ + 'nodeName' => 'comment', + 'selector' => 'parentSelector', + 'stepKey' => 'parentComment' ], ]; $mockExtendedActionGroup = [ - "nodeName" => "actionGroup", - "name" => "mockExtendedActionGroup", - "filename" => "someFile", - "extends" => "mockSimpleActionGroup", - "commentHere" => [ - "nodeName" => "comment", - "selector" => "otherSelector", - "stepKey" => "commentHere" + 'nodeName' => 'actionGroup', + 'name' => 'mockExtendedActionGroup', + 'filename' => 'someFile', + 'extends' => 'mockSimpleActionGroup', + 'commentHere' => [ + 'nodeName' => 'comment', + 'selector' => 'otherSelector', + 'stepKey' => 'commentHere' ], ]; @@ -252,27 +259,29 @@ public function testGenerateExtendedActionGroup() ); // assert that expected test is generated - $this->assertEquals("mockSimpleActionGroup", $actionGroupObject->getParentName()); + $this->assertEquals('mockSimpleActionGroup', $actionGroupObject->getParentName()); $actions = $actionGroupObject->getActions(); - $this->assertEquals("otherSelector", $actions["commentHere"]->getCustomActionAttributes()["selector"]); - $this->assertEquals("parentSelector", $actions["parentComment"]->getCustomActionAttributes()["selector"]); + $this->assertEquals('otherSelector', $actions['commentHere']->getCustomActionAttributes()['selector']); + $this->assertEquals('parentSelector', $actions['parentComment']->getCustomActionAttributes()['selector']); } /** - * Tests generating an action group that extends an action group that does not exist - * @throws \Exception + * Tests generating an action group that extends an action group that does not exist. + * + * @return void + * @throws Exception */ - public function testGenerateExtendedActionGroupNoParent() + public function testGenerateExtendedActionGroupNoParent(): void { $mockExtendedActionGroup = [ - "nodeName" => "actionGroup", - "name" => "mockExtendedActionGroup", - "filename" => "someFile", - "extends" => "mockSimpleActionGroup", - "commentHere" => [ - "nodeName" => "comment", - "selector" => "otherSelector", - "stepKey" => "commentHere" + 'nodeName' => 'actionGroup', + 'name' => 'mockExtendedActionGroup', + 'filename' => 'someFile', + 'extends' => 'mockSimpleActionGroup', + 'commentHere' => [ + 'nodeName' => 'comment', + 'selector' => 'otherSelector', + 'stepKey' => 'commentHere' ], ]; @@ -284,7 +293,7 @@ public function testGenerateExtendedActionGroupNoParent() $this->setMockTestOutput(null, $mockActionGroupData); $this->expectExceptionMessage( - "Parent Action Group mockSimpleActionGroup not defined for Test " . $mockExtendedActionGroup['name'] + 'Parent Action Group mockSimpleActionGroup not defined for Test ' . $mockExtendedActionGroup['name'] ); // parse and generate test object with mocked data @@ -292,29 +301,31 @@ public function testGenerateExtendedActionGroupNoParent() } /** - * Tests generating an action group that extends another action group that is already extended - * @throws \Exception + * Tests generating an action group that extends another action group that is already extended. + * + * @return void + * @throws Exception */ - public function testExtendingExtendedActionGroup() + public function testExtendingExtendedActionGroup(): void { $mockParentActionGroup = [ - "nodeName" => "actionGroup", - "name" => "mockParentActionGroup", - "filename" => "someFile" + 'nodeName' => 'actionGroup', + 'name' => 'mockParentActionGroup', + 'filename' => 'someFile' ]; $mockSimpleActionGroup = [ - "nodeName" => "actionGroup", - "name" => "mockSimpleActionGroup", - "filename" => "someFile", - "extends" => "mockParentActionGroup", + 'nodeName' => 'actionGroup', + 'name' => 'mockSimpleActionGroup', + 'filename' => 'someFile', + 'extends' => 'mockParentActionGroup' ]; $mockExtendedActionGroup = [ - "nodeName" => "actionGroup", - "name" => "mockExtendedActionGroup", - "filename" => "someFile", - "extends" => "mockSimpleActionGroup", + 'nodeName' => 'actionGroup', + 'name' => 'mockExtendedActionGroup', + 'filename' => 'someFile', + 'extends' => 'mockSimpleActionGroup' ]; $mockActionGroupData = [ @@ -327,65 +338,113 @@ public function testExtendingExtendedActionGroup() $this->setMockTestOutput(null, $mockActionGroupData); $this->expectExceptionMessage( - "Cannot extend an action group that already extends another action group. " . $mockSimpleActionGroup['name'] + 'Cannot extend an action group that already extends another action group. ' . $mockSimpleActionGroup['name'] ); // parse and generate test object with mocked data try { ActionGroupObjectHandler::getInstance()->getObject('mockExtendedActionGroup'); - } catch (\Exception $e) { + } catch (Exception $exception) { // validate log statement TestLoggingUtil::getInstance()->validateMockLogStatement( 'error', - "Cannot extend an action group that already extends another action group. " . + 'Cannot extend an action group that already extends another action group. ' . $mockSimpleActionGroup['name'], ['parent' => $mockSimpleActionGroup['name'], 'actionGroup' => $mockExtendedActionGroup['name']] ); - throw $e; + throw $exception; } } + /** + * Tests generating a test that extends a skipped parent test. + * + * @return void + * @throws Exception + */ + public function testExtendedTestSkippedParent(): void + { + $testDataArrayBuilder = new TestDataArrayBuilder(); + $mockParentTest = $testDataArrayBuilder + ->withName('baseTest') + ->withAnnotations([ + 'skip' => ['nodeName' => 'skip', 'issueId' => [['nodeName' => 'issueId', 'value' => 'someIssue']]] + ]) + ->build(); + + $testDataArrayBuilder->reset(); + $mockExtendedTest = $testDataArrayBuilder + ->withName('extendTest') + ->withTestReference('baseTest') + ->build(); + + $mockTestData = array_merge($mockParentTest, $mockExtendedTest); + $this->setMockTestOutput($mockTestData); + + // parse and generate test object with mocked data + TestObjectHandler::getInstance()->getObject('extendTest'); + + // validate log statement + TestLoggingUtil::getInstance()->validateMockLogStatement( + 'debug', + 'extendTest is skipped due to ParentTestIsSkipped', + [] + ); + } + /** * Function used to set mock for parser return and force init method to run between tests. * - * @param array $testData - * @throws \Exception + * @param array|null $testData + * @param array|null $actionGroupData + * + * @return void + * @throws Exception */ - private function setMockTestOutput($testData = null, $actionGroupData = null) + private function setMockTestOutput(array $testData = null, array $actionGroupData = null): void { // clear test object handler value to inject parsed content - $property = new \ReflectionProperty(TestObjectHandler::class, 'testObjectHandler'); + $property = new ReflectionProperty(TestObjectHandler::class, 'testObjectHandler'); $property->setAccessible(true); $property->setValue(null); // clear test object handler value to inject parsed content - $property = new \ReflectionProperty(ActionGroupObjectHandler::class, 'instance'); + $property = new ReflectionProperty(ActionGroupObjectHandler::class, 'instance'); $property->setAccessible(true); $property->setValue(null); - $mockDataParser = AspectMock::double(TestDataParser::class, ['readTestData' => $testData])->make(); - $mockActionGroupParser = AspectMock::double( - ActionGroupDataParser::class, - ['readActionGroupData' => $actionGroupData] - )->make(); - $instance = AspectMock::double( - ObjectManager::class, - [ - 'create' => function ($className) use ( - $mockDataParser, - $mockActionGroupParser - ) { - if ($className == TestDataParser::class) { - return $mockDataParser; - } - if ($className == ActionGroupDataParser::class) { - return $mockActionGroupParser; + $mockDataParser = $this->createMock(TestDataParser::class); + $mockDataParser + ->method('readTestData') + ->willReturn($testData); + + $mockActionGroupParser = $this->createMock(ActionGroupDataParser::class); + $mockActionGroupParser + ->method('readActionGroupData') + ->willReturn($actionGroupData); + + $instance = $this->createMock(ObjectManager::class); + $instance + ->method('create') + ->will( + $this->returnCallback( + function ($className) use ($mockDataParser, $mockActionGroupParser) { + if ($className === TestDataParser::class) { + return $mockDataParser; + } + + if ($className === ActionGroupDataParser::class) { + return $mockActionGroupParser; + } + + return null; } - } - ] - )->make(); - // bypass the private constructor - AspectMock::double(ObjectManagerFactory::class, ['getObjectManager' => $instance]); + ) + ); + // clear object manager value to inject expected instance + $property = new ReflectionProperty(ObjectManager::class, 'instance'); + $property->setAccessible(true); + $property->setValue($instance); } } diff --git a/dev/tests/unit/Magento/FunctionalTestFramework/Util/ActionMergeUtilTest.php b/dev/tests/unit/Magento/FunctionalTestFramework/Util/ActionMergeUtilTest.php index 913b106a7..864149b2f 100644 --- a/dev/tests/unit/Magento/FunctionalTestFramework/Util/ActionMergeUtilTest.php +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Util/ActionMergeUtilTest.php @@ -4,11 +4,11 @@ * See COPYING.txt for license details. */ -namespace Tests\unit\Magento\FunctionalTestFramework\Test\Handlers; +namespace tests\unit\Magento\FunctionalTestFramework\Util; use Magento\FunctionalTestingFramework\Test\Objects\ActionObject; use Magento\FunctionalTestingFramework\Test\Util\ActionMergeUtil; -use Magento\FunctionalTestingFramework\Util\MagentoTestCase; +use tests\unit\Util\MagentoTestCase; class ActionMergeUtilTest extends MagentoTestCase { diff --git a/dev/tests/unit/Magento/FunctionalTestFramework/Util/ComposerModuleResolverTest.php b/dev/tests/unit/Magento/FunctionalTestFramework/Util/ComposerModuleResolverTest.php index 3173730b3..cec5f097e 100644 --- a/dev/tests/unit/Magento/FunctionalTestFramework/Util/ComposerModuleResolverTest.php +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Util/ComposerModuleResolverTest.php @@ -4,11 +4,11 @@ * See COPYING.txt for license details. */ -namespace tests\unit\Magento\FunctionalTestFramework\Test\Util; +namespace tests\unit\Magento\FunctionalTestFramework\Util; use ReflectionClass; use Magento\FunctionalTestingFramework\Util\ComposerModuleResolver; -use Magento\FunctionalTestingFramework\Util\MagentoTestCase; +use tests\unit\Util\MagentoTestCase; class ComposerModuleResolverTest extends MagentoTestCase { diff --git a/dev/tests/unit/Magento/FunctionalTestFramework/Util/DocGeneratorTest.php b/dev/tests/unit/Magento/FunctionalTestFramework/Util/DocGeneratorTest.php deleted file mode 100644 index 0fb73e53f..000000000 --- a/dev/tests/unit/Magento/FunctionalTestFramework/Util/DocGeneratorTest.php +++ /dev/null @@ -1,127 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ - -namespace Tests\unit\Magento\FunctionalTestFramework\Test\Handlers; - -use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; -use Magento\FunctionalTestingFramework\Util\MagentoTestCase; -use Magento\FunctionalTestingFramework\Util\DocGenerator; -use tests\unit\Util\ActionGroupObjectBuilder; - -class DocGeneratorTest extends MagentoTestCase -{ - const DOC_FILENAME = "documentation"; - - /** - * Basic test to check creation of documentation - * - * @throws \Exception - */ - public function testBasicCreateDocumentation() - { - $annotations = [ - "description" => "someDescription" - ]; - $actionGroupUnderTest = (new ActionGroupObjectBuilder()) - ->withAnnotations($annotations) - ->withFilename("filename") - ->build(); - $docGenerator = new DocGenerator(); - $docGenerator->createDocumentation( - [$actionGroupUnderTest->getName() => $actionGroupUnderTest], - DOCS_OUTPUT_DIR, - true - ); - - $docFile = DOCS_OUTPUT_DIR . DIRECTORY_SEPARATOR . self::DOC_FILENAME . ".md"; - - $this->assertTrue(file_exists($docFile)); - - $this->assertFileEquals( - RESOURCE_DIR . DIRECTORY_SEPARATOR . "basicDocumentation.txt", - $docFile - ); - } - - /** - * Test to check creation of documentation when overwriting previous - * - * @throws \Exception - */ - public function testCreateDocumentationWithOverwrite() - { - $annotations = [ - "description" => "someDescription" - ]; - $actionGroupUnderTest = (new ActionGroupObjectBuilder()) - ->withAnnotations($annotations) - ->withFilename("filename") - ->build(); - $docGenerator = new DocGenerator(); - $docGenerator->createDocumentation( - [$actionGroupUnderTest->getName() => $actionGroupUnderTest], - DOCS_OUTPUT_DIR, - true - ); - - $annotations = [ - "description" => "alteredDescription" - ]; - $actionGroupUnderTest = (new ActionGroupObjectBuilder()) - ->withAnnotations($annotations) - ->withFilename("filename") - ->build(); - $docGenerator = new DocGenerator(); - $docGenerator->createDocumentation( - [$actionGroupUnderTest->getName() => $actionGroupUnderTest], - DOCS_OUTPUT_DIR, - true - ); - - $docFile = DOCS_OUTPUT_DIR . DIRECTORY_SEPARATOR . self::DOC_FILENAME . ".md"; - - $this->assertTrue(file_exists($docFile)); - - $this->assertFileEquals( - RESOURCE_DIR . DIRECTORY_SEPARATOR . "alteredDocumentation.txt", - $docFile - ); - } - - /** - * Test for existing documentation without clean - * - * @throws \Exception - */ - public function testCreateDocumentationNotCleanException() - { - $annotations = [ - "description" => "someDescription" - ]; - $actionGroupUnderTest = (new ActionGroupObjectBuilder()) - ->withAnnotations($annotations) - ->withFilename("filename") - ->build(); - $docGenerator = new DocGenerator(); - $docGenerator->createDocumentation( - [$actionGroupUnderTest->getName() => $actionGroupUnderTest], - DOCS_OUTPUT_DIR, - true - ); - - $docFile = DOCS_OUTPUT_DIR . DIRECTORY_SEPARATOR . self::DOC_FILENAME . ".md"; - - $this->expectException(TestFrameworkException::class); - $this->expectExceptionMessage("$docFile already exists, please add --clean if you want to overwrite it."); - - $docGenerator = new DocGenerator(); - $docGenerator->createDocumentation( - [$actionGroupUnderTest->getName() => $actionGroupUnderTest], - DOCS_OUTPUT_DIR, - false - ); - } -} diff --git a/dev/tests/unit/Magento/FunctionalTestFramework/Util/GenerationErrorHandlerTest.php b/dev/tests/unit/Magento/FunctionalTestFramework/Util/GenerationErrorHandlerTest.php new file mode 100644 index 000000000..35ffd6d3b --- /dev/null +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Util/GenerationErrorHandlerTest.php @@ -0,0 +1,345 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace tests\unit\Magento\FunctionalTestFramework\Util; + +use ReflectionProperty; +use tests\unit\Util\MagentoTestCase; +use Magento\FunctionalTestingFramework\Util\GenerationErrorHandler; + +/** + * Class GenerationErrorHandlerTest + */ +class GenerationErrorHandlerTest extends MagentoTestCase +{ + /** + * Test get errors when all errors are distinct + */ + public function testGetDistinctErrors():void + { + $expectedAllErrors = [ + 'test' => [ + 'Sameple1Test' => [ + 'message' => 'TestError1', + 'generated' => false, + ], + 'Sameple2Test' => [ + 'message' => 'TestError2', + 'generated' => true, + ] + ], + 'suite' => [ + 'Sameple1Suite' => [ + 'message' => 'SuiteError1', + 'generated' => false, + ], + ] + ]; + + $expectedTestErrors = [ + 'Sameple1Test' => [ + 'message' => 'TestError1', + 'generated' => false, + ], + 'Sameple2Test' => [ + 'message' => 'TestError2', + 'generated' => true, + ] + ]; + + $expectedSuiteErrors = [ + 'Sameple1Suite' => [ + 'message' => 'SuiteError1', + 'generated' => false, + ], + ]; + + GenerationErrorHandler::getInstance()->addError('test', 'Sameple1Test', 'TestError1'); + GenerationErrorHandler::getInstance()->addError('test', 'Sameple2Test', 'TestError2', true); + GenerationErrorHandler::getInstance()->addError('suite', 'Sameple1Suite', 'SuiteError1'); + + // Assert getAllErrors + $this->assertEquals($expectedAllErrors, GenerationErrorHandler::getInstance()->getAllErrors()); + // Assert getErrorsByType + $this->assertEquals($expectedTestErrors, GenerationErrorHandler::getInstance()->getErrorsByType('test')); + $this->assertEquals($expectedSuiteErrors, GenerationErrorHandler::getInstance()->getErrorsByType('suite')); + } + + /** + * Test get errors when some errors have the same key + */ + public function testGetErrorsWithSameKey(): void + { + $expectedAllErrors = [ + 'test' => [ + 'Sameple1Test' => [ + 'message' => [ + 0 => 'TestError1', + 1 => 'TestError3' + ], + 'generated' => [ + 0 => false, + 1 => true + ], + ], + 'Sameple2Test' => [ + 'message' => 'TestError2', + 'generated' => true, + ], + ], + 'suite' => [ + 'Sameple1Suite' => [ + 'message' => [ + 0 => 'SuiteError1', + 1 => 'SuiteError2', + ], + 'generated' => [ + 0 => false, + 1 => false, + ], + ], + ], + ]; + + $expectedTestErrors = [ + 'Sameple1Test' => [ + 'message' => [ + 0 => 'TestError1', + 1 => 'TestError3' + ], + 'generated' => [ + 0 => false, + 1 => true + ], + ], + 'Sameple2Test' => [ + 'message' => 'TestError2', + 'generated' => true, + ], + ]; + + $expectedSuiteErrors = [ + 'Sameple1Suite' => [ + 'message' => [ + 0 => 'SuiteError1', + 1 => 'SuiteError2', + ], + 'generated' => [ + 0 => false, + 1 => false, + ], + ], + ]; + + GenerationErrorHandler::getInstance()->addError('test', 'Sameple1Test', 'TestError1'); + GenerationErrorHandler::getInstance()->addError('suite', 'Sameple1Suite', 'SuiteError1'); + GenerationErrorHandler::getInstance()->addError('test', 'Sameple2Test', 'TestError2', true); + GenerationErrorHandler::getInstance()->addError('suite', 'Sameple1Suite', 'SuiteError2'); + GenerationErrorHandler::getInstance()->addError('test', 'Sameple1Test', 'TestError3', true); + + // Assert getAllErrors + $this->assertEquals($expectedAllErrors, GenerationErrorHandler::getInstance()->getAllErrors()); + // Assert getErrorsByType + $this->assertEquals($expectedTestErrors, GenerationErrorHandler::getInstance()->getErrorsByType('test')); + $this->assertEquals($expectedSuiteErrors, GenerationErrorHandler::getInstance()->getErrorsByType('suite')); + } + + /** + * Test get errors when some errors are duplicate + */ + public function testGetAllErrorsDuplicate(): void + { + $expectedAllErrors = [ + 'test' => [ + 'Sameple1Test' => [ + 'message' => [ + 0 => 'TestError1', + 1 => 'TestError1' + ], + 'generated' => [ + 0 => false, + 1 => false + ], + ], + 'Sameple2Test' => [ + 'message' => 'TestError2', + 'generated' => true, + ], + ], + 'suite' => [ + 'Sameple1Suite' => [ + 'message' => [ + 0 => 'SuiteError1', + 1 => 'SuiteError2', + ], + 'generated' => [ + 0 => false, + 1 => false, + ], + ], + ], + ]; + + $expectedTestErrors = [ + 'Sameple1Test' => [ + 'message' => [ + 0 => 'TestError1', + 1 => 'TestError1' + ], + 'generated' => [ + 0 => false, + 1 => false + ], + ], + 'Sameple2Test' => [ + 'message' => 'TestError2', + 'generated' => true, + ], + ]; + + $expectedSuiteErrors = [ + 'Sameple1Suite' => [ + 'message' => [ + 0 => 'SuiteError1', + 1 => 'SuiteError2', + ], + 'generated' => [ + 0 => false, + 1 => false, + ], + ], + ]; + + GenerationErrorHandler::getInstance()->addError('test', 'Sameple1Test', 'TestError1'); + GenerationErrorHandler::getInstance()->addError('suite', 'Sameple1Suite', 'SuiteError1'); + GenerationErrorHandler::getInstance()->addError('test', 'Sameple2Test', 'TestError2', true); + GenerationErrorHandler::getInstance()->addError('suite', 'Sameple1Suite', 'SuiteError2'); + GenerationErrorHandler::getInstance()->addError('test', 'Sameple1Test', 'TestError1'); + + // Assert getAllErrors + $this->assertEquals($expectedAllErrors, GenerationErrorHandler::getInstance()->getAllErrors()); + // Assert getErrorsByType + $this->assertEquals($expectedTestErrors, GenerationErrorHandler::getInstance()->getErrorsByType('test')); + $this->assertEquals($expectedSuiteErrors, GenerationErrorHandler::getInstance()->getErrorsByType('suite')); + } + + /** + * Test get all error messages + * + * @param string $expectedErrMessages + * @param array $errors + * + * @return void + * @dataProvider getAllErrorMessagesDataProvider + */ + public function testGetAllErrorMessages(string $expectedErrMessages, array $errors): void + { + $handler = GenerationErrorHandler::getInstance(); + $handler->reset(); + + $property = new ReflectionProperty(GenerationErrorHandler::class, 'errors'); + $property->setAccessible(true); + $property->setValue($handler, $errors); + + // Assert getAllErrorMessages + $this->assertEquals($expectedErrMessages, GenerationErrorHandler::getInstance()->getAllErrorMessages()); + } + + /** + * Data provider for testGetAllErrorMessages() + * + * @return array + */ + public function getAllErrorMessagesDataProvider(): array + { + return [ + ['', []], + ['', [ + 'test' => [], + 'suite' => [], + ] + ], + ['TestError1' + . PHP_EOL + . 'TestError2' + . PHP_EOL + . 'TestError3' + . PHP_EOL + . 'SuiteError1' + . PHP_EOL + . 'SuiteError2' + . PHP_EOL + . 'SuiteError3' + . PHP_EOL + . 'SuiteError4', + [ + 'test' => [ + 'Sameple1Test' => [ + 'message' => [ + 0 => 'TestError1', + 1 => 'TestError2' + ], + 'generated' => [ + 0 => false, + 1 => false + ], + ], + 'Sameple2Test' => [ + 'message' => 'TestError3', + 'generated' => true, + ], + ], + 'suite' => [ + 'Sameple1Suite' => [ + 'message' => 'SuiteError1', + 'generated' => true, + ], + 'Sameple2Suite' => [ + 'message' => [ + 0 => 'SuiteError2', + 1 => 'SuiteError3', + 2 => 'SuiteError4', + ], + 'generated' => [ + 0 => false, + 1 => true, + 2 => false, + ], + ], + ], + ] + ], + ]; + } + + /** + * Test reset + */ + public function testResetError(): void + { + GenerationErrorHandler::getInstance()->addError('something', 'some', 'error'); + GenerationErrorHandler::getInstance()->addError('otherthing', 'other', 'error'); + GenerationErrorHandler::getInstance()->reset(); + + // Assert getAllErrors + $this->assertEquals([], GenerationErrorHandler::getInstance()->getAllErrors()); + // Assert getErrorsByType + $this->assertEquals([], GenerationErrorHandler::getInstance()->getErrorsByType('something')); + $this->assertEquals([], GenerationErrorHandler::getInstance()->getErrorsByType('otherthing')); + $this->assertEquals([], GenerationErrorHandler::getInstance()->getErrorsByType('nothing')); + } + + /** + * @inheritdoc + */ + public function tearDown(): void + { + $property = new ReflectionProperty(GenerationErrorHandler::class, 'instance'); + $property->setAccessible(true); + $property->setValue(null); + } +} diff --git a/dev/tests/unit/Magento/FunctionalTestFramework/Util/ModulePathExtractorTest.php b/dev/tests/unit/Magento/FunctionalTestFramework/Util/ModulePathExtractorTest.php index 05bc99b89..00be96582 100644 --- a/dev/tests/unit/Magento/FunctionalTestFramework/Util/ModulePathExtractorTest.php +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Util/ModulePathExtractorTest.php @@ -3,12 +3,15 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); -namespace tests\unit\Magento\FunctionalTestFramework\Test\Util; +namespace tests\unit\Magento\FunctionalTestFramework\Util; +use Magento\FunctionalTestingFramework\ObjectManager; use Magento\FunctionalTestingFramework\Util\ModulePathExtractor; -use Magento\FunctionalTestingFramework\Util\MagentoTestCase; -use tests\unit\Util\MockModuleResolverBuilder; +use Magento\FunctionalTestingFramework\Util\ModuleResolver; +use ReflectionProperty; +use tests\unit\Util\MagentoTestCase; class ModulePathExtractorTest extends MagentoTestCase { @@ -36,8 +39,7 @@ public function testGetModuleAppCode() { $mockPath = '/base/path/app/code/Magento/ModuleA/Test/Mftf/Test/SomeTest.xml'; - $resolverMock = new MockModuleResolverBuilder(); - $resolverMock->setup($this->mockTestModulePaths); + $this->mockModuleResolver($this->mockTestModulePaths); $extractor = new ModulePathExtractor(); $this->assertEquals('ModuleA', $extractor->extractModuleName($mockPath)); } @@ -51,8 +53,7 @@ public function testGetVendorAppCode() { $mockPath = '/base/path/app/code/VendorB/ModuleB/Test/Mftf/Test/SomeTest.xml'; - $resolverMock = new MockModuleResolverBuilder(); - $resolverMock->setup($this->mockTestModulePaths); + $this->mockModuleResolver($this->mockTestModulePaths); $extractor = new ModulePathExtractor(); $this->assertEquals('VendorB', $extractor->getExtensionPath($mockPath)); } @@ -66,8 +67,7 @@ public function testGetModuleDevTests() { $mockPath = '/base/path/dev/tests/acceptance/tests/functional/Magento/ModuleCTest/Test/SomeTest.xml'; - $resolverMock = new MockModuleResolverBuilder(); - $resolverMock->setup($this->mockTestModulePaths); + $this->mockModuleResolver($this->mockTestModulePaths); $extractor = new ModulePathExtractor(); $this->assertEquals('ModuleC', $extractor->extractModuleName($mockPath)); } @@ -81,8 +81,7 @@ public function testGetVendorDevTests() { $mockPath = '/base/path/dev/tests/acceptance/tests/functional/VendorD/ModuleDTest/Test/SomeTest.xml'; - $resolverMock = new MockModuleResolverBuilder(); - $resolverMock->setup($this->mockTestModulePaths); + $this->mockModuleResolver($this->mockTestModulePaths); $extractor = new ModulePathExtractor(); $this->assertEquals('VendorD', $extractor->getExtensionPath($mockPath)); } @@ -96,8 +95,7 @@ public function testGetModule() { $mockPath = '/base/path/dev/tests/acceptance/tests/functional/FunctionalTest/SomeModuleE/Test/SomeTest.xml'; - $resolverMock = new MockModuleResolverBuilder(); - $resolverMock->setup($this->mockTestModulePaths); + $this->mockModuleResolver($this->mockTestModulePaths); $extractor = new ModulePathExtractor(); $this->assertEquals('NO MODULE DETECTED', $extractor->extractModuleName($mockPath)); } @@ -111,8 +109,7 @@ public function testGetModuleVendorDir() { $mockPath = '/base/path/vendor/magento/module-modulef/Test/Mftf/Test/SomeTest.xml'; - $resolverMock = new MockModuleResolverBuilder(); - $resolverMock->setup($this->mockTestModulePaths); + $this->mockModuleResolver($this->mockTestModulePaths); $extractor = new ModulePathExtractor(); $this->assertEquals('ModuleF', $extractor->extractModuleName($mockPath)); } @@ -126,9 +123,47 @@ public function testGetVendorVendorDir() { $mockPath = '/base/path/vendor/vendorg/module-moduleg-test/Test/SomeTest.xml'; - $resolverMock = new MockModuleResolverBuilder(); - $resolverMock->setup($this->mockTestModulePaths); + $this->mockModuleResolver($this->mockTestModulePaths); $extractor = new ModulePathExtractor(); $this->assertEquals('VendorG', $extractor->getExtensionPath($mockPath)); } + + /** + * Mock module resolver. + * + * @param array $paths + * + * @return void + */ + private function mockModuleResolver(array $paths): void + { + $mockResolver = $this->createMock(ModuleResolver::class); + $mockResolver + ->method('getEnabledModules') + ->willReturn([]); + + $objectManagerMockInstance = $this->createMock(ObjectManager::class); + $objectManagerMockInstance + ->method('create') + ->will( + $this->returnCallback( + function ($class) use ($mockResolver) { + if ($class === ModuleResolver::class) { + return $mockResolver; + } + + return null; + } + ) + ); + + $objectManagerProperty = new ReflectionProperty(ObjectManager::class, 'instance'); + $objectManagerProperty->setAccessible(true); + $objectManagerProperty->setValue($objectManagerMockInstance); + + $resolver = ModuleResolver::getInstance(); + $property = new ReflectionProperty(ModuleResolver::class, 'enabledModuleNameAndPaths'); + $property->setAccessible(true); + $property->setValue($resolver, $paths); + } } diff --git a/dev/tests/unit/Magento/FunctionalTestFramework/Util/ModuleResolverTest.php b/dev/tests/unit/Magento/FunctionalTestFramework/Util/ModuleResolverTest.php index 07e1ee7fa..25fd20d73 100644 --- a/dev/tests/unit/Magento/FunctionalTestFramework/Util/ModuleResolverTest.php +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Util/ModuleResolverTest.php @@ -3,81 +3,94 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); -namespace tests\unit\Magento\FunctionalTestFramework\Test\Util; - -use AspectMock\Proxy\Verifier; -use AspectMock\Test as AspectMock; +namespace tests\unit\Magento\FunctionalTestFramework\Util; +use Exception; use Magento\FunctionalTestingFramework\Config\MftfApplicationConfig; -use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; -use Magento\FunctionalTestingFramework\ObjectManager; -use Magento\FunctionalTestingFramework\ObjectManagerFactory; -use Magento\FunctionalTestingFramework\Util\Logger\LoggingUtil; +use Magento\FunctionalTestingFramework\Exceptions\FastFailException; +use Magento\FunctionalTestingFramework\Util\ModuleResolver\ModuleResolverService; use Magento\FunctionalTestingFramework\Util\ModuleResolver; -use Magento\FunctionalTestingFramework\Util\MagentoTestCase; -use PHPUnit\Runner\Exception; +use PHPUnit\Framework\MockObject\MockObject; +use ReflectionProperty; +use tests\unit\Util\MagentoTestCase; use tests\unit\Util\TestLoggingUtil; +/** + * Class ModuleResolverTest + */ class ModuleResolverTest extends MagentoTestCase { /** - * Before test functionality - * @return void + * @inheritDoc */ - public function setUp() + protected function setUp(): void { TestLoggingUtil::getInstance()->setMockLoggingUtil(); } /** - * After class functionality - * @return void + * @inheritDoc */ - public static function tearDownAfterClass() + public static function tearDownAfterClass(): void { TestLoggingUtil::getInstance()->clearMockLoggingUtil(); + + $moduleResolverServiceInstance = new ReflectionProperty(ModuleResolverService::class, 'INSTANCE'); + $moduleResolverServiceInstance->setAccessible(true); + $moduleResolverServiceInstance->setValue(null); + + $mftfAppConfigInstance = new ReflectionProperty(MftfApplicationConfig::class, 'MFTF_APPLICATION_CONTEXT'); + $mftfAppConfigInstance->setAccessible(true); + $mftfAppConfigInstance->setValue(null); } /** - * Validate that Paths that are already set are returned - * @throws \Exception + * Validate that Paths that are already set are returned. + * + * @return void + * @throws Exception */ - public function testGetModulePathsAlreadySet() + public function testGetModulePathsAlreadySet(): void { - $this->setMockResolverClass(); $resolver = ModuleResolver::getInstance(); - $this->setMockResolverProperties($resolver, ["example" . DIRECTORY_SEPARATOR . "paths"]); - $this->assertEquals(["example" . DIRECTORY_SEPARATOR . "paths"], $resolver->getModulesPath()); + $this->setMockResolverProperties($resolver, ['example' . DIRECTORY_SEPARATOR . 'paths']); + $this->assertEquals(['example' . DIRECTORY_SEPARATOR . 'paths'], $resolver->getModulesPath()); } /** - * Validate paths are aggregated correctly - * @throws \Exception + * Validate paths are aggregated correctly. + * + * @return void + * @throws Exception */ - public function testGetModulePathsAggregate() + public function testGetModulePathsAggregate(): void { - $this->mockForceGenerate(false); - $this->setMockResolverClass( - false, - null, - null, - null, - [ - 'some' . DIRECTORY_SEPARATOR . 'path' . DIRECTORY_SEPARATOR . 'example' => ['example'], - 'other' . DIRECTORY_SEPARATOR . 'path' . DIRECTORY_SEPARATOR . 'sample' => ['sample'], - ], - null, - [ - 'Magento_example' => 'some' . DIRECTORY_SEPARATOR . 'path' . DIRECTORY_SEPARATOR . 'example', - 'Magento_sample' => 'other' . DIRECTORY_SEPARATOR . 'path' . DIRECTORY_SEPARATOR . 'sample', - ], - null, - null, - [], - [] - ); + + $moduleResolverService = $this->createPartialMock( + ModuleResolverService::class, + ['getRegisteredModuleList', 'aggregateTestModulePaths'] + ); + $moduleResolverService->expects($this->any()) + ->method('getRegisteredModuleList') + ->willReturn( + [ + 'Magento_example' => 'some' . DIRECTORY_SEPARATOR . 'path' . DIRECTORY_SEPARATOR . 'example', + 'Magento_sample' => 'other' . DIRECTORY_SEPARATOR . 'path' . DIRECTORY_SEPARATOR . 'sample' + ] + ); + $moduleResolverService->expects($this->any()) + ->method('aggregateTestModulePaths') + ->willReturn( + [ + 'some' . DIRECTORY_SEPARATOR . 'path' . DIRECTORY_SEPARATOR . 'example' => ['example'], + 'other' . DIRECTORY_SEPARATOR . 'path' . DIRECTORY_SEPARATOR . 'sample' => ['sample'] + ] + ); + + $this->setMockResolverCreatorProperties($moduleResolverService); $resolver = ModuleResolver::getInstance(); $this->setMockResolverProperties($resolver, null, [0 => 'Magento_example', 1 => 'Magento_sample']); $this->assertEquals( @@ -90,111 +103,149 @@ public function testGetModulePathsAggregate() } /** - * Validate correct path locations are fed into globRelevantPaths - * @throws \Exception + * Validate aggregateTestModulePaths() when module path part of DEV_TESTS. + * + * @return void + * @throws Exception */ - public function testGetModulePathsLocations() + public function testAggregateTestModulePathsDevTests(): void { - // clear test object handler value to inject parsed content - $property = new \ReflectionProperty(ModuleResolver::class, 'instance'); - $property->setAccessible(true); - $property->setValue(null); + $origin = TESTS_MODULE_PATH; + $modulePath = ModuleResolver::DEV_TESTS . DIRECTORY_SEPARATOR . "Magento"; + putenv("TESTS_MODULE_PATH=$modulePath"); $this->mockForceGenerate(false); - $mockResolver = $this->setMockResolverClass( - true, - [], - null, - null, - [], - [], - [], - null, - null, - [], - [], - null, - function ($arg) { - return $arg; - }, - function ($arg) { - return $arg; - } - ); + $moduleResolverService = $this->createPartialMock(ModuleResolverService::class, ['globRelevantPaths']); + $moduleResolverService + ->method('globRelevantPaths') + ->will( + $this->returnCallback( + function ($codePath, $pattern) use ($modulePath) { + if ($codePath === $modulePath && $pattern === '') { + $this->fail(sprintf( + 'Not expected parameter: \'%s\' when invoked method globRelevantPaths().', + $modulePath + )); + } + + return []; + } + ) + ); + $this->setMockResolverCreatorProperties($moduleResolverService); $resolver = ModuleResolver::getInstance(); - $this->setMockResolverProperties($resolver, null, null); - $this->assertEquals( - [], - $resolver->getModulesPath() - ); + $this->setMockResolverProperties($resolver, null, []); + $this->assertEquals([], $resolver->getModulesPath()); - // Define the Module paths from app/code - $magentoBaseCodePath = MAGENTO_BP; + putenv("TESTS_MODULE_PATH=$origin"); + } + /** + * Validate correct path locations are fed into globRelevantPaths. + * + * @return void + * @throws Exception + */ + public function testGetModulePathsLocations(): void + { + // clear test object handler value to inject parsed content + $property = new ReflectionProperty(ModuleResolver::class, 'instance'); + $property->setAccessible(true); + $property->setValue(null); + + $this->mockForceGenerate(false); // Define the Module paths from default TESTS_MODULE_PATH $modulePath = defined('TESTS_MODULE_PATH') ? TESTS_MODULE_PATH : TESTS_BP; - - $mockResolver->verifyInvoked('globRelevantPaths', [$modulePath, '']); - $mockResolver->verifyInvoked( - 'globRelevantPaths', - [$magentoBaseCodePath . DIRECTORY_SEPARATOR . "vendor" , 'Test' . DIRECTORY_SEPARATOR .'Mftf'] - ); - $mockResolver->verifyInvoked( - 'globRelevantPaths', + $invokedWithParams = $expectedParams = [ [ - $magentoBaseCodePath . DIRECTORY_SEPARATOR . "app" . DIRECTORY_SEPARATOR . "code", - 'Test' . DIRECTORY_SEPARATOR .'Mftf' - ] - ); - $mockResolver->verifyInvoked( - 'globRelevantPaths', + $modulePath, + '' + ], + [ + MAGENTO_BP . '/vendor', + 'Test/Mftf' + ], [ - $magentoBaseCodePath - . DIRECTORY_SEPARATOR . "dev" - . DIRECTORY_SEPARATOR . "tests" - . DIRECTORY_SEPARATOR . "acceptance" - . DIRECTORY_SEPARATOR . "tests" - . DIRECTORY_SEPARATOR . "functional" - . DIRECTORY_SEPARATOR . "Magento" - . DIRECTORY_SEPARATOR . "FunctionalTest" - , '' + MAGENTO_BP . '/app/code', + 'Test/Mftf' ] - ); + ]; + + $moduleResolverService = $this->createPartialMock(ModuleResolverService::class, ['globRelevantPaths']); + $moduleResolverService + ->method('globRelevantPaths') + ->will( + $this->returnCallback( + function ($codePath, $pattern) use (&$invokedWithParams, $expectedParams) { + foreach ($expectedParams as $key => $parameter) { + list($expectedCodePath, $expectedPattern) = $parameter; + + if ($codePath === $expectedCodePath && $pattern === $expectedPattern) { + if (isset($invokedWithParams[$key])) { + unset($invokedWithParams[$key]); + } + + return []; + } + } + + $this->fail(sprintf( + 'Not expected parameter: [%s] when invoked method globRelevantPaths().', + $codePath . ';' . $pattern + )); + } + ) + ); + + $this->setMockResolverCreatorProperties($moduleResolverService); + $resolver = ModuleResolver::getInstance(); + $this->setMockResolverProperties($resolver, null, []); + $this->assertEquals([], $resolver->getModulesPath()); + + if ($invokedWithParams) { + $parameters = ''; + + foreach ($invokedWithParams as $parameter) { + $parameters .= sprintf('[%s]', implode(';', $parameter)); + } + + $this->fail('The method globRelevantPaths() was not called with expected parameters:' . $parameters); + } } /** - * Validate aggregateTestModulePathsFromComposerJson + * Validate aggregateTestModulePathsFromComposerJson. * - * @throws \Exception + * @return void + * @throws Exception */ - public function testAggregateTestModulePathsFromComposerJson() + public function testAggregateTestModulePathsFromComposerJson(): void { $this->mockForceGenerate(false); - $this->setMockResolverClass( - false, - null, // getEnabledModules - null, // applyCustomMethods - null, // globRelevantWrapper - [], // relevantPath - null, // getCustomModulePaths - null, // getRegisteredModuleList - null, // aggregateTestModulePathsFromComposerJson - [], // aggregateTestModulePathsFromComposerInstaller - [ - 'composer' . DIRECTORY_SEPARATOR . 'json' . DIRECTORY_SEPARATOR . 'pathA' => - [ - 'Magento_ModuleA', - 'Magento_ModuleB' - ], - 'composer' . DIRECTORY_SEPARATOR . 'json' . DIRECTORY_SEPARATOR . 'pathB' => - [ - 'Magento_ModuleB', - 'Magento_ModuleC' - ], - ], // getComposerJsonTestModulePaths - [] // getComposerInstalledTestModulePaths - ); + $moduleResolverService = $this->createPartialMock( + ModuleResolverService::class, + ['getComposerJsonTestModulePaths'] + ); + $moduleResolverService + ->expects($this->any()) + ->method('getComposerJsonTestModulePaths') + ->willReturn( + [ + 'composer' . DIRECTORY_SEPARATOR . 'json' . DIRECTORY_SEPARATOR . 'pathA' => + [ + 'Magento_ModuleA', + 'Magento_ModuleB' + ], + 'composer' . DIRECTORY_SEPARATOR . 'json' . DIRECTORY_SEPARATOR . 'pathB' => + [ + 'Magento_ModuleB', + 'Magento_ModuleC' + ] + ] + ); + + $this->setMockResolverCreatorProperties($moduleResolverService); $resolver = ModuleResolver::getInstance(); $this->setMockResolverProperties($resolver, null, [0 => 'Magento_ModuleB', 1 => 'Magento_ModuleC']); $this->assertEquals( @@ -206,33 +257,14 @@ public function testAggregateTestModulePathsFromComposerJson() } /** - * Validate getComposerJsonTestModulePaths with paths invocation + * Validate getComposerJsonTestModulePaths with paths invocation. * - * @throws \Exception + * @return void + * @throws Exception */ - public function testGetComposerJsonTestModulePathsForPathInvocation() + public function testGetComposerJsonTestModulePathsForPathInvocation(): void { $this->mockForceGenerate(false); - $mockResolver = $this->setMockResolverClass( - false, - [], - null, - null, - [], - null, - null, - null, - null, - [], - [] - ); - - $resolver = ModuleResolver::getInstance(); - $this->setMockResolverProperties($resolver, null, null); - $this->assertEquals( - [], - $resolver->getModulesPath() - ); // Expected dev tests path $expectedSearchPaths[] = MAGENTO_BP @@ -254,42 +286,64 @@ public function testGetComposerJsonTestModulePathsForPathInvocation() $expectedSearchPaths[] = $testModulePath; } - $mockResolver->verifyInvoked('getComposerJsonTestModulePaths', [$expectedSearchPaths]); + $moduleResolverService = $this->createPartialMock( + ModuleResolverService::class, + ['getComposerJsonTestModulePaths'] + ); + $moduleResolverService + ->method('getComposerJsonTestModulePaths') + ->will( + $this->returnCallback( + function ($codePaths) use ($expectedSearchPaths) { + if ($codePaths === $expectedSearchPaths) { + return []; + } + + $this->fail(sprintf( + 'Not expected parameter: \'%s\' when invoked method getComposerJsonTestModulePaths().', + $codePaths + )); + } + ) + ); + + $this->setMockResolverCreatorProperties($moduleResolverService); + $resolver = ModuleResolver::getInstance(); + $this->setMockResolverProperties($resolver, null, []); + $this->assertEquals([], $resolver->getModulesPath()); } /** - * Validate aggregateTestModulePathsFromComposerInstaller + * Validate aggregateTestModulePathsFromComposerInstaller. * - * @throws \Exception + * @return void + * @throws Exception */ - public function testAggregateTestModulePathsFromComposerInstaller() + public function testAggregateTestModulePathsFromComposerInstaller(): void { $this->mockForceGenerate(false); - $this->setMockResolverClass( - false, - null, - null, - null, - [], - null, - null, - null, - null, - [], - [ - 'composer' . DIRECTORY_SEPARATOR . 'install' . DIRECTORY_SEPARATOR . 'pathA' => - [ - 'Magento_ModuleA', - 'Magento_ModuleB' - ], - 'composer' . DIRECTORY_SEPARATOR . 'install' . DIRECTORY_SEPARATOR . 'pathB' => - [ - 'Magento_ModuleB', - 'Magento_ModuleC' - ], - ] - ); - + $moduleResolverService = $this->createPartialMock( + ModuleResolverService::class, + ['getComposerInstalledTestModulePaths'] + ); + $moduleResolverService->expects($this->any()) + ->method('getComposerInstalledTestModulePaths') + ->willReturn( + [ + 'composer' . DIRECTORY_SEPARATOR . 'install' . DIRECTORY_SEPARATOR . 'pathA' => + [ + 'Magento_ModuleA', + 'Magento_ModuleB' + ], + 'composer' . DIRECTORY_SEPARATOR . 'install' . DIRECTORY_SEPARATOR . 'pathB' => + [ + 'Magento_ModuleB', + 'Magento_ModuleC' + ] + ] + ); + + $this->setMockResolverCreatorProperties($moduleResolverService); $resolver = ModuleResolver::getInstance(); $this->setMockResolverProperties( $resolver, @@ -306,93 +360,104 @@ public function testAggregateTestModulePathsFromComposerInstaller() } /** - * Validate getComposerInstalledTestModulePaths with paths invocation + * Validate getComposerInstalledTestModulePaths with paths invocation. * - * @throws \Exception + * @return void + * @throws Exception */ - public function testGetComposerInstalledTestModulePathsForPathInvocation() + public function testGetComposerInstalledTestModulePathsForPathInvocation(): void { $this->mockForceGenerate(false); - $mockResolver = $this->setMockResolverClass( - false, - [], - null, - null, - [], - null, - null, - null, - null, - [], - [] - ); - - $resolver = ModuleResolver::getInstance(); - $this->setMockResolverProperties($resolver, null, null); - $this->assertEquals( - [], - $resolver->getModulesPath() - ); // Expected file path $expectedSearchPath = MAGENTO_BP . DIRECTORY_SEPARATOR . 'composer.json'; - - $mockResolver->verifyInvoked('getComposerInstalledTestModulePaths', [$expectedSearchPath]); + $moduleResolverService = $this->createPartialMock( + ModuleResolverService::class, + ['getComposerInstalledTestModulePaths'] + ); + $moduleResolverService + ->method('getComposerInstalledTestModulePaths') + ->will( + $this->returnCallback( + function ($composerFile) use ($expectedSearchPath) { + if ($composerFile === $expectedSearchPath) { + return []; + } + + $this->fail(sprintf( + 'Not expected parameter: \'%s\' when invoked method getComposerInstalledTestModulePaths().', + $composerFile + )); + } + ) + ); + + $this->setMockResolverCreatorProperties($moduleResolverService); + $resolver = ModuleResolver::getInstance(); + $this->setMockResolverProperties($resolver, null, []); + $this->assertEquals([], $resolver->getModulesPath()); } /** - * Validate mergeModulePaths() and flipAndFilterModulePathsArray() + * Validate mergeModulePaths() and flipAndFilterModulePathsArray(). * - * @throws \Exception + * @return void + * @throws Exception */ - public function testMergeFlipAndFilterModulePathsNoForceGenerate() + public function testMergeFlipAndFilterModulePathsNoForceGenerate(): void { $this->mockForceGenerate(false); - $this->setMockResolverClass( - false, - null, - null, - null, - null, - null, - null, - null, - null, - [ - 'composer' . DIRECTORY_SEPARATOR . 'json' . DIRECTORY_SEPARATOR . 'pathA' => - [ - 'Magento_ModuleA' - ], - 'composer' . DIRECTORY_SEPARATOR . 'json' . DIRECTORY_SEPARATOR . 'pathB' => - [ - 'Magento_ModuleB' - ], - ], - [ - 'composer' . DIRECTORY_SEPARATOR . 'install' . DIRECTORY_SEPARATOR . 'pathD' => - [ - 'Magento_ModuleD' - ], - 'composer' . DIRECTORY_SEPARATOR . 'install' . DIRECTORY_SEPARATOR . 'pathE' => - [ - 'Magento_ModuleE' - ], - 'composer' . DIRECTORY_SEPARATOR . 'install' . DIRECTORY_SEPARATOR . 'pathC' => - [ - 'Magento_ModuleC', - 'Magento_ModuleB', - ], - ], - [ - 'some' . DIRECTORY_SEPARATOR . 'path' . DIRECTORY_SEPARATOR . 'example' => ['Magento_Example'], - 'other' . DIRECTORY_SEPARATOR . 'path' . DIRECTORY_SEPARATOR . 'sample' => ['Magento_Sample'], - 'some' . DIRECTORY_SEPARATOR . 'path' . DIRECTORY_SEPARATOR . 'path1' => ['Magento_Path1'], - 'other' . DIRECTORY_SEPARATOR . 'path' . DIRECTORY_SEPARATOR . 'path2' => ['Magento_Path2'], - 'some' . DIRECTORY_SEPARATOR . 'path' . DIRECTORY_SEPARATOR . 'path3' => ['Magento_Path3'], - 'other' . DIRECTORY_SEPARATOR . 'path' . DIRECTORY_SEPARATOR . 'path4' => ['Magento_Path4'], - ] - ); - + $moduleResolverService = $this->createPartialMock( + ModuleResolverService::class, + ['getComposerJsonTestModulePaths', 'getComposerInstalledTestModulePaths', 'aggregateTestModulePaths'] + ); + $moduleResolverService->expects($this->any()) + ->method('getComposerJsonTestModulePaths') + ->willReturn( + [ + 'composer' . DIRECTORY_SEPARATOR . 'json' . DIRECTORY_SEPARATOR . 'pathA' => + [ + 'Magento_ModuleA' + ], + 'composer' . DIRECTORY_SEPARATOR . 'json' . DIRECTORY_SEPARATOR . 'pathB' => + [ + 'Magento_ModuleB' + ] + ] + ); + $moduleResolverService->expects($this->any()) + ->method('getComposerInstalledTestModulePaths') + ->willReturn( + [ + 'composer' . DIRECTORY_SEPARATOR . 'install' . DIRECTORY_SEPARATOR . 'pathD' => + [ + 'Magento_ModuleD' + ], + 'composer' . DIRECTORY_SEPARATOR . 'install' . DIRECTORY_SEPARATOR . 'pathE' => + [ + 'Magento_ModuleE' + ], + 'composer' . DIRECTORY_SEPARATOR . 'install' . DIRECTORY_SEPARATOR . 'pathC' => + [ + 'Magento_ModuleC', + 'Magento_ModuleB', + ] + ] + ); + $moduleResolverService->expects($this->any()) + ->method('aggregateTestModulePaths') + ->willReturn( + [ + 'some' . DIRECTORY_SEPARATOR . 'path' . DIRECTORY_SEPARATOR . 'example' => ['Magento_Example'], + 'other' . DIRECTORY_SEPARATOR . 'path' . DIRECTORY_SEPARATOR . 'sample' => ['Magento_Sample'], + 'some' . DIRECTORY_SEPARATOR . 'path' . DIRECTORY_SEPARATOR . 'path1' => ['Magento_Path1'], + 'other' . DIRECTORY_SEPARATOR . 'path' . DIRECTORY_SEPARATOR . 'path2' => ['Magento_Path2'], + 'some' . DIRECTORY_SEPARATOR . 'path' . DIRECTORY_SEPARATOR . 'path3' => ['Magento_Path3'], + 'other' . DIRECTORY_SEPARATOR . 'path' . DIRECTORY_SEPARATOR . 'path4' => ['Magento_Path4'] + ] + ); + + $this->setMockResolverCreatorProperties($moduleResolverService); $resolver = ModuleResolver::getInstance(); $this->setMockResolverProperties( $resolver, @@ -405,7 +470,7 @@ public function testMergeFlipAndFilterModulePathsNoForceGenerate() 4 => 'Magento_ModuleB', 5 => 'Magento_ModuleD', 6 => 'Magento_Otherexample', - 7 => 'Magento_ModuleC', + 7 => 'Magento_ModuleC' ] ); $this->assertEquals( @@ -416,7 +481,7 @@ public function testMergeFlipAndFilterModulePathsNoForceGenerate() 'some' . DIRECTORY_SEPARATOR . 'path' . DIRECTORY_SEPARATOR . 'example', 'composer' . DIRECTORY_SEPARATOR . 'json' . DIRECTORY_SEPARATOR . 'pathB', 'composer' . DIRECTORY_SEPARATOR . 'install' . DIRECTORY_SEPARATOR . 'pathD', - 'composer' . DIRECTORY_SEPARATOR . 'install' . DIRECTORY_SEPARATOR . 'pathC', + 'composer' . DIRECTORY_SEPARATOR . 'install' . DIRECTORY_SEPARATOR . 'pathC' ], $resolver->getModulesPath() @@ -424,51 +489,144 @@ public function testMergeFlipAndFilterModulePathsNoForceGenerate() } /** - * Validate mergeModulePaths() and flipAndSortModulePathsArray() + * Validate mergeModulePaths() and flipAndSortModulePathsArray(). * - * @throws \Exception + * @return void + * @throws Exception */ - public function testMergeFlipAndSortModulePathsForceGenerate() + public function testMergeFlipNoSortModulePathsNoForceGenerate(): void { - $this->mockForceGenerate(true); - $this->setMockResolverClass( - false, - null, - null, - null, - null, - null, - null, - null, + $this->mockForceGenerate(false); + $moduleResolverService = $this->createPartialMock( + ModuleResolverService::class, + ['getComposerJsonTestModulePaths', 'getComposerInstalledTestModulePaths', 'aggregateTestModulePaths'] + ); + $moduleResolverService->expects($this->any()) + ->method('getComposerJsonTestModulePaths') + ->willReturn( + [ + 'composer' . DIRECTORY_SEPARATOR . 'json' . DIRECTORY_SEPARATOR + . 'Magento' . DIRECTORY_SEPARATOR . 'ModuleA' => + [ + 'Magento_ModuleA' + ], + 'composer' . DIRECTORY_SEPARATOR . 'json' . DIRECTORY_SEPARATOR + . 'Magento' . DIRECTORY_SEPARATOR . 'ModuleBC' => + [ + 'Magento_ModuleB', + 'Magento_ModuleC' + ] + ] + ); + $moduleResolverService->expects($this->any()) + ->method('getComposerInstalledTestModulePaths') + ->willReturn( + [ + 'composer' . DIRECTORY_SEPARATOR . 'install' . DIRECTORY_SEPARATOR + . 'Magento' . DIRECTORY_SEPARATOR . 'ModuleCD' => + [ + 'Magento_ModuleC', + 'Magento_ModuleD' + ], + 'composer' . DIRECTORY_SEPARATOR . 'install' . DIRECTORY_SEPARATOR + . 'Magento' . DIRECTORY_SEPARATOR . 'ModuleE' => + [ + 'Magento_ModuleE' + ] + ] + ); + $moduleResolverService->expects($this->any()) + ->method('aggregateTestModulePaths') + ->willReturn( + [ + 'some' . DIRECTORY_SEPARATOR . 'path' . DIRECTORY_SEPARATOR . 'example' => ['Magento_Example'], + 'other' . DIRECTORY_SEPARATOR . 'path' . DIRECTORY_SEPARATOR . 'sample' => ['Magento_Sample'] + ] + ); + + $this->setMockResolverCreatorProperties($moduleResolverService); + $resolver = ModuleResolver::getInstance(); + $this->setMockResolverProperties( + $resolver, null, [ - 'composer' . DIRECTORY_SEPARATOR . 'json' . DIRECTORY_SEPARATOR . 'pathA' => - [ - 'Magento_ModuleA' - ], - 'composer' . DIRECTORY_SEPARATOR . 'json' . DIRECTORY_SEPARATOR . 'pathB' => - [ - 'Magento_ModuleB', - 'Magento_ModuleC', - ], - ], + 0 => 'Magento_ModuleB', + 1 => 'Magento_ModuleC', + 2 => 'Magento_ModuleE', + 3 => 'Magento_Example', + 4 => 'Magento_Otherexample' + ] + ); + + $this->assertEquals( [ - 'composer' . DIRECTORY_SEPARATOR . 'install' . DIRECTORY_SEPARATOR . 'pathA' => - [ - 'Magento_ModuleC', - 'Magento_ModuleD' - ], - 'composer' . DIRECTORY_SEPARATOR . 'install' . DIRECTORY_SEPARATOR . 'pathB' => - [ - 'Magento_ModuleD' - ], + 'composer' . DIRECTORY_SEPARATOR . 'install' . DIRECTORY_SEPARATOR . 'Magento' . DIRECTORY_SEPARATOR + . 'ModuleE', + 'some' . DIRECTORY_SEPARATOR . 'path' . DIRECTORY_SEPARATOR . 'example', + 'composer' . DIRECTORY_SEPARATOR . 'json' . DIRECTORY_SEPARATOR . 'Magento' . DIRECTORY_SEPARATOR + . 'ModuleBC' ], - [ - 'some' . DIRECTORY_SEPARATOR . 'path' . DIRECTORY_SEPARATOR . 'example' => ['Magento_Example'], - 'other' . DIRECTORY_SEPARATOR . 'path' . DIRECTORY_SEPARATOR . 'sample' => ['Magento_Sample'], - ] + $resolver->getModulesPath() ); + } + /** + * Validate mergeModulePaths() and flipAndSortModulePathsArray(). + * + * @return void + * @throws Exception + */ + public function testMergeFlipAndSortModulePathsForceGenerate(): void + { + $this->mockForceGenerate(true); + $moduleResolverService = $this->createPartialMock( + ModuleResolverService::class, + ['getComposerJsonTestModulePaths', 'getComposerInstalledTestModulePaths', 'aggregateTestModulePaths'] + ); + $moduleResolverService->expects($this->any()) + ->method('getComposerJsonTestModulePaths') + ->willReturn( + [ + 'composer' . DIRECTORY_SEPARATOR . 'json' . DIRECTORY_SEPARATOR + . 'Magento' . DIRECTORY_SEPARATOR . 'ModuleA' => + [ + 'Magento_ModuleA' + ], + 'composer' . DIRECTORY_SEPARATOR . 'json' . DIRECTORY_SEPARATOR + . 'Magento' . DIRECTORY_SEPARATOR . 'ModuleBC' => + [ + 'Magento_ModuleB', + 'Magento_ModuleC' + ] + ] + ); + $moduleResolverService->expects($this->any()) + ->method('getComposerInstalledTestModulePaths') + ->willReturn( + [ + 'composer' . DIRECTORY_SEPARATOR . 'install' . DIRECTORY_SEPARATOR + . 'Magento' . DIRECTORY_SEPARATOR . 'ModuleCD' => + [ + 'Magento_ModuleC', + 'Magento_ModuleD' + ], + 'composer' . DIRECTORY_SEPARATOR . 'install' . DIRECTORY_SEPARATOR + . 'Magento' . DIRECTORY_SEPARATOR . 'ModuleD' => + [ + 'Magento_ModuleD' + ] + ] + ); + $moduleResolverService->expects($this->any()) + ->method('aggregateTestModulePaths') + ->willReturn( + [ + 'some' . DIRECTORY_SEPARATOR . 'path' . DIRECTORY_SEPARATOR . 'example' => ['Magento_Example'], + 'other' . DIRECTORY_SEPARATOR . 'path' . DIRECTORY_SEPARATOR . 'sample' => ['Magento_Sample'] + ] + ); + + $this->setMockResolverCreatorProperties($moduleResolverService); $resolver = ModuleResolver::getInstance(); $this->setMockResolverProperties( $resolver, @@ -481,59 +639,67 @@ public function testMergeFlipAndSortModulePathsForceGenerate() 4 => 'Magento_Otherexample' ] ); + $this->assertEquals( [ 'some' . DIRECTORY_SEPARATOR . 'path' . DIRECTORY_SEPARATOR . 'example', - 'composer' . DIRECTORY_SEPARATOR . 'json' . DIRECTORY_SEPARATOR . 'pathA', - 'composer' . DIRECTORY_SEPARATOR . 'json' . DIRECTORY_SEPARATOR . 'pathB', - 'composer' . DIRECTORY_SEPARATOR . 'install' . DIRECTORY_SEPARATOR . 'pathA', - 'composer' . DIRECTORY_SEPARATOR . 'install' . DIRECTORY_SEPARATOR . 'pathB', - 'other' . DIRECTORY_SEPARATOR . 'path' . DIRECTORY_SEPARATOR . 'sample' + 'composer' . DIRECTORY_SEPARATOR . 'json' . DIRECTORY_SEPARATOR . 'Magento' . DIRECTORY_SEPARATOR + . 'ModuleA', + 'composer' . DIRECTORY_SEPARATOR . 'install' . DIRECTORY_SEPARATOR . 'Magento' . DIRECTORY_SEPARATOR + . 'ModuleD', + 'other' . DIRECTORY_SEPARATOR . 'path' . DIRECTORY_SEPARATOR . 'sample', + 'composer' . DIRECTORY_SEPARATOR . 'json' . DIRECTORY_SEPARATOR . 'Magento' . DIRECTORY_SEPARATOR + . 'ModuleBC', + 'composer' . DIRECTORY_SEPARATOR . 'install' . DIRECTORY_SEPARATOR . 'Magento' . DIRECTORY_SEPARATOR + . 'ModuleCD' ], $resolver->getModulesPath() ); } /** - * Validate logging warning in flipAndFilterModulePathsArray() + * Validate logging warning in flipAndFilterModulePathsArray(). * - * @throws \Exception + * @return void + * @throws Exception */ - public function testMergeFlipAndFilterModulePathsWithLogging() + public function testMergeFlipAndFilterModulePathsWithLogging(): void { $this->mockForceGenerate(false); - $this->setMockResolverClass( - false, - null, - null, - null, - [], - null, - null, - null, - null, - [ - 'composer' . DIRECTORY_SEPARATOR . 'json' . DIRECTORY_SEPARATOR . 'pathA' => - [ - 'Magento_ModuleA' - ], - 'composer' . DIRECTORY_SEPARATOR . 'json' . DIRECTORY_SEPARATOR . 'pathB' => - [ - 'Magento_ModuleB' - ], - ], - [ - 'composer' . DIRECTORY_SEPARATOR . 'install' . DIRECTORY_SEPARATOR . 'pathA' => - [ - 'Magento_ModuleA' - ], - 'composer' . DIRECTORY_SEPARATOR . 'install' . DIRECTORY_SEPARATOR . 'pathB' => - [ - 'Magento_ModuleC' - ], - ] - ); - + $moduleResolverService = $this->createPartialMock( + ModuleResolverService::class, + ['getComposerJsonTestModulePaths', 'getComposerInstalledTestModulePaths'] + ); + $moduleResolverService->expects($this->any()) + ->method('getComposerJsonTestModulePaths') + ->willReturn( + [ + 'composer' . DIRECTORY_SEPARATOR . 'json' . DIRECTORY_SEPARATOR . 'pathA' => + [ + 'Magento_ModuleA' + ], + 'composer' . DIRECTORY_SEPARATOR . 'json' . DIRECTORY_SEPARATOR . 'pathB' => + [ + 'Magento_ModuleB' + ] + ] + ); + $moduleResolverService->expects($this->any()) + ->method('getComposerInstalledTestModulePaths') + ->willReturn( + [ + 'composer' . DIRECTORY_SEPARATOR . 'install' . DIRECTORY_SEPARATOR . 'pathA' => + [ + 'Magento_ModuleA' + ], + 'composer' . DIRECTORY_SEPARATOR . 'install' . DIRECTORY_SEPARATOR . 'pathB' => + [ + 'Magento_ModuleC' + ] + ] + ); + + $this->setMockResolverCreatorProperties($moduleResolverService); $resolver = ModuleResolver::getInstance(); $this->setMockResolverProperties( $resolver, @@ -552,37 +718,37 @@ public function testMergeFlipAndFilterModulePathsWithLogging() ], $resolver->getModulesPath() ); + $warnMsg = 'Path: composer' . DIRECTORY_SEPARATOR . 'install' . DIRECTORY_SEPARATOR; $warnMsg .= 'pathA is ignored by ModuleResolver. ' . PHP_EOL . 'Path: composer' . DIRECTORY_SEPARATOR; $warnMsg .= 'json' . DIRECTORY_SEPARATOR . 'pathA is set for Module: Magento_ModuleA' . PHP_EOL; - TestLoggingUtil::getInstance()->validateMockLogStatement( - 'warning', - $warnMsg, - [] - ); + TestLoggingUtil::getInstance()->validateMockLogStatement('warning', $warnMsg, []); } /** - * Validate custom modules are added - * @throws \Exception + * Validate custom modules are added. + * + * @return void + * @throws Exception */ - public function testApplyCustomModuleMethods() + public function testApplyCustomModuleMethods(): void { - $this->setMockResolverClass( - false, - null, - null, - null, - [], - [ 'Magento_Module' => 'otherPath'], - null, - null, - null, - [], - [] + $this->mockForceGenerate(true); + $moduleResolverService = $this->createPartialMock( + ModuleResolverService::class, + ['getCustomModulePaths', 'aggregateTestModulePaths'] ); + $moduleResolverService->expects($this->any()) + ->method('getCustomModulePaths') + ->willReturn(['Magento_Module' => 'otherPath']); + + $moduleResolverService + ->method('aggregateTestModulePaths') + ->willReturn([]); + + $this->setMockResolverCreatorProperties($moduleResolverService); $resolver = ModuleResolver::getInstance(); - $this->setMockResolverProperties($resolver, null, null, null); + $this->setMockResolverProperties($resolver); $this->assertEquals(['otherPath'], $resolver->getModulesPath()); TestLoggingUtil::getInstance()->validateMockLogStatement( 'info', @@ -592,38 +758,27 @@ public function testApplyCustomModuleMethods() } /** - * Validate blacklisted modules are removed + * Validate blocklisted modules are removed * Module paths are sorted according to module name in alphabetically ascending order * - * @throws \Exception + * @throws Exception */ - public function testGetModulePathsBlacklist() + public function testGetModulePathsBlocklist(): void { - $this->setMockResolverClass( - false, - null, - null, - null, - [], - null, - null, - null, - null, - [], - [], - [ - 'thisPath/some/path4' => ['Some_Module4'], - 'devTests/Magento/path3' => ['Magento_Module3'], - 'appCode/Magento/path2' => ['Magento_Module2'], - 'vendor/amazon/path1' => ['Amazon_Module1'], - ], - function ($arg) { - return $arg; - }, - function ($arg) { - return $arg; - } - ); + $this->mockForceGenerate(true); + $moduleResolverService = $this->createPartialMock(ModuleResolverService::class, ['aggregateTestModulePaths']); + $moduleResolverService->expects($this->any()) + ->method('aggregateTestModulePaths') + ->willReturn( + [ + 'thisPath/some/path4' => ['Some_Module4'], + 'devTests/Magento/path3' => ['Magento_Module3'], + 'appCode/Magento/path2' => ['Magento_Module2'], + 'vendor/amazon/path1' => ['Amazon_Module1'] + ] + ); + + $this->setMockResolverCreatorProperties($moduleResolverService); $resolver = ModuleResolver::getInstance(); $this->setMockResolverProperties($resolver, null, null, ['Magento_Module3']); $this->assertEquals( @@ -638,249 +793,168 @@ function ($arg) { } /** - * Validate that getEnabledModules errors out when no Admin Token is returned and --force is false - * @throws \Exception + * Validate that getEnabledModules errors out when no Admin Token is returned and --force is false. + * + * @return void + * @throws Exception */ - public function testGetModulePathsNoAdminToken() + public function testGetModulePathsNoAdminToken(): void { // Set --force to false $this->mockForceGenerate(false); - // Mock ModuleResolver and $enabledModulesPath - $this->setMockResolverClass( - false, - null, - ["example" . DIRECTORY_SEPARATOR . "paths"], - [], - null, - null, - null, - null, - null, - [], - [] - ); $resolver = ModuleResolver::getInstance(); - $this->setMockResolverProperties($resolver, null, null); + $this->setMockResolverProperties($resolver); - // Cannot Generate if no --force was passed in and no Admin Token is returned succesfully - $this->expectException(TestFrameworkException::class); + // Cannot Generate if no --force was passed in and no Admin Token is returned successfully + $this->expectException(FastFailException::class); $resolver->getModulesPath(); } /** - * Validates that getAdminToken is not called when --force is enabled + * Validates that getAdminToken is not called when --force is enabled. + * + * @return void + * @throws Exception */ - public function testGetAdminTokenNotCalledWhenForce() + public function testGetAdminTokenNotCalledWhenForce(): void { // Set --force to true $this->mockForceGenerate(true); // Mock ModuleResolver and applyCustomModuleMethods() - $mockResolver = $this->setMockResolverClass(); + $moduleResolverService = $this->createMock(ModuleResolverService::class); + $moduleResolverService + ->method('getAdminToken') + ->with( + $this->returnCallback( + function () { + $this->fail('Not expected to call method \'getAdminToken()\'.'); + } + ) + ); + + $this->setMockResolverCreatorProperties($moduleResolverService); $resolver = ModuleResolver::getInstance(); - $this->setMockResolverProperties($resolver, null, null); + $this->setMockResolverProperties($resolver, null, []); $resolver->getModulesPath(); - $mockResolver->verifyNeverInvoked("getAdminToken"); - - // verifyNeverInvoked does not add to assertion count $this->addToAssertionCount(1); } /** * Verify the getAdminToken method returns throws an exception if ENV is not fully loaded. + * + * @return void + * @throws Exception */ - public function testGetAdminTokenWithMissingEnv() + public function testGetAdminTokenWithMissingEnv(): void { - // Set --force to true + // Set --force to false $this->mockForceGenerate(false); + $this->setMockResolverCreatorProperties(null); // Unset env unset($_ENV['MAGENTO_ADMIN_USERNAME']); - - // Mock ModuleResolver and applyCustomModuleMethods() - $mockResolver = $this->setMockResolverClass(); $resolver = ModuleResolver::getInstance(); + $this->setMockResolverProperties($resolver); // Expect exception - $this->expectException(TestFrameworkException::class); + $this->expectException(FastFailException::class); $resolver->getModulesPath(); } /** * Verify the getAdminToken method returns throws an exception if Token was bad. + * + * @return void + * @throws Exception */ - public function testGetAdminTokenWithBadResponse() + public function testGetAdminTokenWithBadResponse(): void { - // Set --force to true + // Set --force to false $this->mockForceGenerate(false); + $this->setMockResolverCreatorProperties(null); - // Mock ModuleResolver and applyCustomModuleMethods() - $mockResolver = $this->setMockResolverClass(); $resolver = ModuleResolver::getInstance(); + $this->setMockResolverProperties($resolver); // Expect exception - $this->expectException(TestFrameworkException::class); + $this->expectException(FastFailException::class); $resolver->getModulesPath(); } /** - * Function used to set mock for parser return and force init method to run between tests. + * Function used to set mock for Resolver properties. + * + * @param ModuleResolver $instance + * @param null $mockPaths + * @param null $mockModules + * @param array $mockBlockList * - * @param string $mockToken - * @param array $mockGetEnabledModules - * @param string[] $mockApplyCustomMethods - * @param string[] $mockGlobRelevantWrapper - * @param string[] $mockRelevantPaths - * @param string[] $mockGetCustomModulePaths - * @param string[] $mockGetRegisteredModuleList - * @param string[] $mockAggregateTestModulePathsFromComposerJson - * @param string[] $mockAggregateTestModulePathsFromComposerInstaller - * @param string[] $mockGetComposerJsonTestModulePaths - * @param string[] $mockGetComposerInstalledTestModulePaths - * @param string[] $mockAggregateTestModulePaths - * @param string[] $mockNormalizeModuleNames - * @param string[] $mockFlipAndFilterModulePathsArray - * @param string[] $mockFlipAndSortModulePathsArray - * @throws \Exception - * @return Verifier ModuleResolver double + * @return void */ - private function setMockResolverClass( - $mockToken = null, - $mockGetEnabledModules = null, - $mockApplyCustomMethods = null, - $mockGlobRelevantWrapper = null, - $mockRelevantPaths = null, - $mockGetCustomModulePaths = null, - $mockGetRegisteredModuleList = null, - $mockAggregateTestModulePathsFromComposerJson = null, - $mockAggregateTestModulePathsFromComposerInstaller = null, - $mockGetComposerJsonTestModulePaths = null, - $mockGetComposerInstalledTestModulePaths = null, - $mockAggregateTestModulePaths = null, - $mockNormalizeModuleNames = null, - $mockFlipAndFilterModulePathsArray = null, - $mockFlipAndSortModulePathsArray = null - ) { - $property = new \ReflectionProperty(ModuleResolver::class, 'instance'); + private function setMockResolverProperties( + ModuleResolver $instance, + $mockPaths = null, + $mockModules = null, + $mockBlockList = [] + ): void { + $property = new ReflectionProperty(ModuleResolver::class, 'enabledModulePaths'); $property->setAccessible(true); - $property->setValue(null); + $property->setValue($instance, $mockPaths); - $mockMethods = []; - if (isset($mockToken)) { - $mockMethods['getAdminToken'] = $mockToken; - } - if (isset($mockGetEnabledModules)) { - $mockMethods['getEnabledModules'] = $mockGetEnabledModules; - } - if (isset($mockApplyCustomMethods)) { - $mockMethods['applyCustomModuleMethods'] = $mockApplyCustomMethods; - } - if (isset($mockGlobRelevantWrapper)) { - $mockMethods['globRelevantWrapper'] = $mockGlobRelevantWrapper; - } - if (isset($mockRelevantPaths)) { - $mockMethods['globRelevantPaths'] = $mockRelevantPaths; - } - if (isset($mockGetCustomModulePaths)) { - $mockMethods['getCustomModulePaths'] = $mockGetCustomModulePaths; - } - if (isset($mockGetRegisteredModuleList)) { - $mockMethods['getRegisteredModuleList'] = $mockGetRegisteredModuleList; - } - if (isset($mockAggregateTestModulePathsFromComposerJson)) { - $mockMethods['aggregateTestModulePathsFromComposerJson'] = $mockAggregateTestModulePathsFromComposerJson; - } - if (isset($mockAggregateTestModulePathsFromComposerInstaller)) { - $mockMethods['aggregateTestModulePathsFromComposerInstaller'] = - $mockAggregateTestModulePathsFromComposerInstaller; - } - if (isset($mockGetComposerJsonTestModulePaths)) { - $mockMethods['getComposerJsonTestModulePaths'] = $mockGetComposerJsonTestModulePaths; - } - if (isset($mockGetComposerInstalledTestModulePaths)) { - $mockMethods['getComposerInstalledTestModulePaths'] = $mockGetComposerInstalledTestModulePaths; - } - if (isset($mockAggregateTestModulePaths)) { - $mockMethods['aggregateTestModulePaths'] = $mockAggregateTestModulePaths; - } - if (isset($mockNormalizeModuleNames)) { - $mockMethods['normalizeModuleNames'] = $mockNormalizeModuleNames; - } - if (isset($mockFlipAndFilterModulePathsArray)) { - $mockMethods['flipAndFilterModulePathsArray'] = $mockFlipAndFilterModulePathsArray; - } - if (isset($mockFlipAndSortModulePathsArray)) { - $mockMethods['flipAndSortModulePathsArray'] = $mockFlipAndSortModulePathsArray; - } - $mockResolver = AspectMock::double( - ModuleResolver::class, - $mockMethods - ); - $instance = AspectMock::double( - ObjectManager::class, - ['create' => $mockResolver->make(), 'get' => null] - )->make(); - // bypass the private constructor - AspectMock::double(ObjectManagerFactory::class, ['getObjectManager' => $instance]); - - return $mockResolver; + $property = new ReflectionProperty(ModuleResolver::class, 'enabledModules'); + $property->setAccessible(true); + $property->setValue($instance, $mockModules); + + $property = new ReflectionProperty(ModuleResolver::class, 'moduleBlocklist'); + $property->setAccessible(true); + $property->setValue($instance, $mockBlockList); } /** - * Function used to set mock for Resolver properties + * Function used to set mock for ResolverCreator properties. * - * @param ModuleResolver $instance - * @param array $mockPaths - * @param array $mockModules - * @param array $mockBlacklist - * @throws \Exception + * @param MockObject|null $moduleResolverService + * + * @return void */ - private function setMockResolverProperties($instance, $mockPaths = null, $mockModules = null, $mockBlacklist = []) + private function setMockResolverCreatorProperties(?MockObject $moduleResolverService): void { - $property = new \ReflectionProperty(ModuleResolver::class, 'enabledModulePaths'); - $property->setAccessible(true); - $property->setValue($instance, $mockPaths); - - $property = new \ReflectionProperty(ModuleResolver::class, 'enabledModules'); - $property->setAccessible(true); - $property->setValue($instance, $mockModules); - - $property = new \ReflectionProperty(ModuleResolver::class, 'moduleBlacklist'); + $property = new ReflectionProperty(ModuleResolverService::class, 'INSTANCE'); $property->setAccessible(true); - $property->setValue($instance, $mockBlacklist); + $property->setValue($moduleResolverService); } /** * Mocks MftfApplicationConfig->forceGenerateEnabled() - * @param $forceGenerate - * @throws \Exception + * @param bool $forceGenerate + * * @return void + * @throws Exception */ - private function mockForceGenerate($forceGenerate) + private function mockForceGenerate(bool $forceGenerate): void { - $mockConfig = AspectMock::double( - MftfApplicationConfig::class, - ['forceGenerateEnabled' => $forceGenerate] - ); - $instance = AspectMock::double( - ObjectManager::class, - ['create' => $mockConfig->make(), 'get' => null] - )->make(); - AspectMock::double(ObjectManagerFactory::class, ['getObjectManager' => $instance]); + $mockConfig = $this->createMock(MftfApplicationConfig::class); + $mockConfig->expects($this->once()) + ->method('forceGenerateEnabled') + ->willReturn($forceGenerate); + + $property = new ReflectionProperty(MftfApplicationConfig::class, 'MFTF_APPLICATION_CONTEXT'); + $property->setAccessible(true); + $property->setValue($mockConfig); } /** - * After method functionality + * After method functionality. + * * @return void */ - protected function tearDown() + protected function tearDown(): void { // re set env if (!isset($_ENV['MAGENTO_ADMIN_USERNAME'])) { - $_ENV['MAGENTO_ADMIN_USERNAME'] = "admin"; + $_ENV['MAGENTO_ADMIN_USERNAME'] = 'admin'; } - - AspectMock::clean(); } } diff --git a/dev/tests/unit/Magento/FunctionalTestFramework/Util/Path/FilePathFormatterTest.php b/dev/tests/unit/Magento/FunctionalTestFramework/Util/Path/FilePathFormatterTest.php new file mode 100644 index 000000000..c833fc6be --- /dev/null +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Util/Path/FilePathFormatterTest.php @@ -0,0 +1,102 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace tests\unit\Magento\FunctionalTestFramework\Util\Path; + +use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; +use Magento\FunctionalTestingFramework\Util\Path\FilePathFormatter; +use tests\unit\Util\MagentoTestCase; + +class FilePathFormatterTest extends MagentoTestCase +{ + /** + * Test file format. + * + * @param string $path + * @param bool|null $withTrailingSeparator + * @param string|null $expectedPath + * + * @return void + * @throws TestFrameworkException + * @dataProvider formatDataProvider + */ + public function testFormat(string $path, ?bool $withTrailingSeparator, ?string $expectedPath): void + { + if (null !== $expectedPath) { + if ($withTrailingSeparator === null) { + $this->assertEquals($expectedPath, FilePathFormatter::format($path)); + return; + } + $this->assertEquals($expectedPath, FilePathFormatter::format($path, $withTrailingSeparator)); + } else { + // Assert no exception + if ($withTrailingSeparator === null) { + FilePathFormatter::format($path); + } else { + FilePathFormatter::format($path, $withTrailingSeparator); + } + $this->assertTrue(true); + } + } + + /** + * Test file format with exception. + * + * @param string $path + * @param bool|null $withTrailingSeparator + * + * @return void + * @throws TestFrameworkException + * @dataProvider formatExceptionDataProvider + */ + public function testFormatWithException(string $path, ?bool $withTrailingSeparator): void + { + $this->expectException(TestFrameworkException::class); + $this->expectExceptionMessage("Invalid or non-existing file: $path\n"); + + if ($withTrailingSeparator === null) { + FilePathFormatter::format($path); + return; + } + FilePathFormatter::format($path, $withTrailingSeparator); + } + + /** + * Data input. + * + * @return array + */ + public function formatDataProvider(): array + { + $path1 = rtrim(TESTS_BP, '/'); + $path2 = $path1 . DIRECTORY_SEPARATOR; + + return [ + [$path1, null, $path2], + [$path1, false, $path1], + [$path1, true, $path2], + [$path2, null, $path2], + [$path2, false, $path1], + [$path2, true, $path2], + [__DIR__ . DIRECTORY_SEPARATOR . basename(__FILE__), null, __FILE__ . DIRECTORY_SEPARATOR], + ['', null, null] // Empty string is valid + ]; + } + + /** + * Invalid data input. + * + * @return array + */ + public function formatExceptionDataProvider(): array + { + return [ + ['abc', null], + ['X://some\dir/@', null] + ]; + } +} diff --git a/dev/tests/unit/Magento/FunctionalTestFramework/Util/Path/UrlFormatterTest.php b/dev/tests/unit/Magento/FunctionalTestFramework/Util/Path/UrlFormatterTest.php new file mode 100644 index 000000000..21cfa9daa --- /dev/null +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Util/Path/UrlFormatterTest.php @@ -0,0 +1,106 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace tests\unit\Magento\FunctionalTestFramework\Util\Path; + +use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; +use Magento\FunctionalTestingFramework\Util\Path\UrlFormatter; +use tests\unit\Util\MagentoTestCase; + +class UrlFormatterTest extends MagentoTestCase +{ + /** + * Test url format. + * + * @param string $path + * @param bool|null $withTrailingSeparator + * @param string $expectedPath + * + * @return void + * @dataProvider formatDataProvider + */ + public function testFormat(string $path, ?bool $withTrailingSeparator, string $expectedPath): void + { + if ($withTrailingSeparator === null) { + $this->assertEquals($expectedPath, UrlFormatter::format($path)); + return; + } + $this->assertEquals($expectedPath, UrlFormatter::format($path, $withTrailingSeparator)); + } + + /** + * Test url format with exception. + * + * @param string $path + * @param bool|null $withTrailingSeparator + * + * @return void + * @dataProvider formatExceptionDataProvider + */ + public function testFormatWithException(string $path, ?bool $withTrailingSeparator): void + { + $this->expectException(TestFrameworkException::class); + $this->expectExceptionMessage("Invalid url: $path\n"); + + if ($withTrailingSeparator === null) { + UrlFormatter::format($path); + return; + } + UrlFormatter::format($path, $withTrailingSeparator); + } + + /** + * Data input. + * + * @return array + */ + public function formatDataProvider(): array + { + $url1 = 'http://magento.local/index.php'; + $url2 = $url1 . '/'; + $url3 = 'https://www.example.com/index.php/admin'; + $url4 = $url3 . '/'; + $url5 = 'www.google.com'; + $url6 = 'http://www.google.com/'; + $url7 = 'http://127.0.0.1:8200'; + $url8 = 'wwøw.goåoøgle.coøm'; + $url9 = 'http://www.google.com'; + + return [ + [$url1, null, $url2], + [$url1, false, $url1], + [$url1, true, $url2], + [$url2, null, $url2], + [$url2, false, $url1], + [$url2, true, $url2], + [$url3, null, $url4], + [$url3, false, $url3], + [$url3, true, $url4], + [$url4, null, $url4], + [$url4, false, $url3], + [$url4, true, $url4], + [$url5, true, $url6], + [$url7, false, $url7], + [$url8, false, $url9], + ['https://magento.local/path?', false, 'https://magento.local/path?'], + ['https://magento.local/path#', false, 'https://magento.local/path#'], + ['https://magento.local/path?#', false, 'https://magento.local/path?#'] + ]; + } + + /** + * Invalid data input. + * + * @return array + */ + public function formatExceptionDataProvider(): array + { + return [ + ['', null] + ]; + } +} diff --git a/dev/tests/unit/Magento/FunctionalTestFramework/Util/Sorter/ParallelGroupSorterTest.php b/dev/tests/unit/Magento/FunctionalTestFramework/Util/Sorter/ParallelGroupSorterTest.php index 997bf0c3e..5429ec92a 100644 --- a/dev/tests/unit/Magento/FunctionalTestFramework/Util/Sorter/ParallelGroupSorterTest.php +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Util/Sorter/ParallelGroupSorterTest.php @@ -3,22 +3,26 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ -namespace Tests\unit\Magento\FunctionalTestFramework\Util\Sorter; +declare(strict_types=1); -use AspectMock\Test as AspectMock; -use Magento\FunctionalTestingFramework\Suite\Handlers\SuiteObjectHandler; -use Magento\FunctionalTestingFramework\Suite\Objects\SuiteObject; +namespace tests\unit\Magento\FunctionalTestFramework\Util\Sorter; + +use Magento\FunctionalTestingFramework\Exceptions\FastFailException; use Magento\FunctionalTestingFramework\Test\Handlers\TestObjectHandler; use Magento\FunctionalTestingFramework\Test\Objects\TestObject; use Magento\FunctionalTestingFramework\Util\Sorter\ParallelGroupSorter; -use Magento\FunctionalTestingFramework\Util\MagentoTestCase; +use ReflectionProperty; +use tests\unit\Util\MagentoTestCase; class ParallelGroupSorterTest extends MagentoTestCase { /** - * Test a basic sort of available tests based on size + * Test a basic sort of available tests based on size. + * + * @return void + * @throws FastFailException */ - public function testBasicTestGroupSort() + public function testBasicTestsSplitByTime(): void { $sampleTestArray = [ 'test1' => 100, @@ -43,41 +47,24 @@ public function testBasicTestGroupSort() $testSorter = new ParallelGroupSorter(); $actualResult = $testSorter->getTestsGroupedBySize([], $sampleTestArray, 200); - $this->assertCount(5, $actualResult); - foreach ($actualResult as $gropuNumber => $actualTests) { - $expectedTests = $expectedResult[$gropuNumber]; + foreach ($actualResult as $groupNumber => $actualTests) { + $expectedTests = $expectedResult[$groupNumber]; $this->assertEquals($expectedTests, array_keys($actualTests)); } } /** - * Test a sort of both tests and a suite which is larger than the given line limitation + * Test a sort of both tests and a suite which is larger than the given line limitation. + * + * @return void + * @throws FastFailException */ - public function testSortWithSuites() + public function testTestsAndSuitesSplitByTime(): void { // mock tests for test object handler. - $numberOfCalls = 0; - $mockTest1 = AspectMock::double( - TestObject::class, - ['getEstimatedDuration' => function () use (&$numberOfCalls) { - $actionCount = [300, 275]; - $result = $actionCount[$numberOfCalls]; - $numberOfCalls++; - - return $result; - }] - )->make(); - - $mockHandler = AspectMock::double( - TestObjectHandler::class, - ['getObject' => function () use ($mockTest1) { - return $mockTest1; - }] - )->make(); - - AspectMock::double(TestObjectHandler::class, ['getInstance' => $mockHandler])->make(); + $this->createMockForTest(0, [300, 275]); // create test to size array $sampleTestArray = [ @@ -101,8 +88,8 @@ public function testSortWithSuites() $this->assertCount(5, $actualResult); $expectedResults = [ - 1 => ['mockSuite1_0'], - 2 => ['mockSuite1_1'], + 1 => ['mockSuite1_0_G'], + 2 => ['mockSuite1_1_G'], 3 => ['test3'], 4 => ['test2','test5', 'test4'], 5 => ['test1'], @@ -112,4 +99,353 @@ public function testSortWithSuites() $this->assertEquals($expectedResults[$groupNum], array_keys($group)); } } + + /** + * Test splitting tests based on a fixed group number. + * + * @return void + * @throws FastFailException + */ + public function testBasicTestsSplitByGroup(): void + { + $sampleTestArray = [ + 'test1' => 100, + 'test2' => 300, + 'test3' => 50, + 'test4' => 60, + 'test5' => 25, + 'test6' => 125, + 'test7' => 250, + 'test8' => 1, + 'test9' => 80, + 'test10' => 25, + 'test11' => 89, + 'test12' => 69, + 'test13' => 23, + 'test14' => 15, + 'test15' => 25, + 'test16' => 71, + 'test17' => 67, + 'test18' => 34, + 'test19' => 45, + 'test20' => 58, + 'test21' => 9 + ]; + + $expectedResult = [ + 1 => ['test2', 'test8'], + 2 => ['test7', 'test18', 'test14', 'test21'], + 3 => ['test6', 'test12', 'test20', 'test5', 'test10'], + 4 => ['test1', 'test16', 'test4', 'test3', 'test15'], + 5 => ['test11', 'test9', 'test17', 'test19', 'test13'], + ]; + + $testSorter = new ParallelGroupSorter(); + $actualResult = $testSorter->getTestsGroupedByFixedGroupCount([], $sampleTestArray, 5); + $this->assertCount(5, $actualResult); + + foreach ($actualResult as $groupNumber => $actualTests) { + $expectedTests = $expectedResult[$groupNumber]; + $this->assertEquals($expectedTests, array_keys($actualTests)); + } + } + + /** + * Test splitting tests based a group number bigger than ever needed. + * + * @return void + * @throws FastFailException + */ + public function testBasicTestsSplitByBigGroupNumber(): void + { + $sampleTestArray = [ + 'test1' => 100, + 'test2' => 300, + 'test3' => 50, + 'test4' => 60, + 'test5' => 25 + ]; + + $expectedResult = [ + 1 => ['test2'], + 2 => ['test1'], + 3 => ['test4'], + 4 => ['test3'], + 5 => ['test5'] + ]; + + $testSorter = new ParallelGroupSorter(); + $actualResult = $testSorter->getTestsGroupedByFixedGroupCount([], $sampleTestArray, 10); + $this->assertCount(5, $actualResult); + + foreach ($actualResult as $groupNumber => $actualTests) { + $expectedTests = $expectedResult[$groupNumber]; + $this->assertEquals($expectedTests, array_keys($actualTests)); + } + } + + /** + * Test splitting tests based a minimum group number. + * + * @return void + * @throws FastFailException + */ + public function testBasicTestsSplitByMinGroupNumber(): void + { + $sampleTestArray = [ + 'test1' => 100, + 'test2' => 300, + 'test3' => 50, + 'test4' => 60, + 'test5' => 25, + ]; + + $expectedResult = [ + 1 => ['test2', 'test1', 'test4', 'test3', 'test5'] + ]; + + $testSorter = new ParallelGroupSorter(); + $actualResult = $testSorter->getTestsGroupedByFixedGroupCount([], $sampleTestArray, 1); + $this->assertCount(1, $actualResult); + + foreach ($actualResult as $groupNumber => $actualTests) { + $expectedTests = $expectedResult[$groupNumber]; + $this->assertEquals($expectedTests, array_keys($actualTests)); + } + } + + /** + * Test splitting tests and suites based on a fixed group number. + * + * @return void + * @throws FastFailException + */ + public function testTestsAndSuitesSplitByGroup(): void + { + // mock tests for test object handler. + $this->createMockForTest(0); + + // create test to size array + $sampleTestArray = [ + 'test1' => 1, + 'test2' => 125, + 'test3' => 35, + 'test4' => 111, + 'test5' => 43, + 'test6' => 321, + 'test7' => 260, + 'test8' => 5, + 'test9' => 189, + 'test10' => 246, + 'test11' => 98, + 'test12' => 96, + 'test13' => 232, + 'test14' => 51, + 'test15' => 52, + 'test16' => 127, + 'test17' => 76, + 'test18' => 43, + 'test19' => 154, + 'test20' => 85, + 'test21' => 219, + 'test22' => 87, + 'test23' => 65, + 'test24' => 216, + 'test25' => 271, + 'test26' => 99, + 'test27' => 102, + 'test28' => 179, + 'test29' => 243, + 'test30' => 93, + 'test31' => 330, + 'test32' => 85, + 'test33' => 291 + ]; + + // create mock suite references + $sampleSuiteArray = [ + 'mockSuite1' => ['mockTest1', 'mockTest2'] + ]; + + // perform sort + $testSorter = new ParallelGroupSorter(); + $actualResult = $testSorter->getTestsGroupedByFixedGroupCount($sampleSuiteArray, $sampleTestArray, 15); + // verify the resulting groups + $this->assertCount(15, $actualResult); + + $expectedResults = [ + 1 => ['test31', 'test3'], + 2 => ['test6', 'test5'], + 3 => ['test33', 'test17'], + 4 => ['test25', 'test32'], + 5 => ['test7', 'test22'], + 6 => ['test10', 'test30'], + 7 => ['test29', 'test12'], + 8 => ['test13', 'test11', 'test8', 'test1'], + 9 => ['test21', 'test26', 'test14'], + 10 => ['test24', 'test27', 'test18'], + 11 => ['test9', 'test4', 'test23'], + 12 => ['test28', 'test2', 'test15'], + 13 => ['test19', 'test16', 'test20'], + 14 => ['mockSuite1_0_G'], + 15 => ['mockSuite1_1_G'] + ]; + + foreach ($actualResult as $groupNum => $group) { + $this->assertEquals($expectedResults[$groupNum], array_keys($group)); + } + } + + /** + * Test splitting tests and suites based a group number bigger than ever needed. + * + * @return void + * @throws FastFailException + */ + public function testTestsAndSuitesSplitByBigGroupNumber(): void + { + // mock tests for test object handler. + $this->createMockForTest(0); + + // create test to size array + $sampleTestArray = [ + 'test1' => 275, + 'test2' => 190, + 'test3' => 200 + ]; + + // create mock suite references + $sampleSuiteArray = [ + 'mockSuite1' => ['mockTest1', 'mockTest2'] + ]; + + // perform sort + $testSorter = new ParallelGroupSorter(); + $actualResult = $testSorter->getTestsGroupedByFixedGroupCount($sampleSuiteArray, $sampleTestArray, 10); + // verify the resulting groups + $this->assertCount(5, $actualResult); + + $expectedResults = [ + 1 => ['test1'], + 2 => ['test3'], + 3 => ['test2'], + 4 => ['mockSuite1_0_G'], + 5 => ['mockSuite1_1_G'], + ]; + + foreach ($actualResult as $groupNum => $group) { + $this->assertEquals($expectedResults[$groupNum], array_keys($group)); + } + } + + /** + * Test splitting tests and suites based a minimum group number. + * + * @return void + * @throws FastFailException + */ + public function testTestsAndSuitesSplitByMinGroupNumber(): void + { + // mock tests for test object handler. + $this->createMockForTest(0); + + // create test to size array + $sampleTestArray = [ + 'test1' => 1, + 'test2' => 125, + 'test3' => 35 + ]; + + // create mock suite references + $sampleSuiteArray = [ + 'mockSuite1' => ['mockTest1', 'mockTest2'] + ]; + + // perform sort + $testSorter = new ParallelGroupSorter(); + $actualResult = $testSorter->getTestsGroupedByFixedGroupCount($sampleSuiteArray, $sampleTestArray, 3); + // verify the resulting groups + $this->assertCount(3, $actualResult); + + $expectedResults = [ + 1 => ['test2', 'test3', 'test1'], + 2 => ['mockSuite1_0_G'], + 3 => ['mockSuite1_1_G'] + ]; + + foreach ($actualResult as $groupNum => $group) { + $this->assertEquals($expectedResults[$groupNum], array_keys($group)); + } + } + + /** + * Test splitting tests and suites with invalid group number. + * + * @return void + */ + public function testTestsAndSuitesSplitByInvalidGroupNumber(): void + { + // mock tests for test object handler. + $this->createMockForTest(0); + + // create test to size array + $sampleTestArray = [ + 'test1' => 1, + 'test2' => 125, + 'test3' => 35 + ]; + // create mock suite references + $sampleSuiteArray = [ + 'mockSuite1' => ['mockTest1', 'mockTest2'] + ]; + + $this->expectException(FastFailException::class); + $this->expectExceptionMessage("Invalid parameter 'groupTotal': must be equal or greater than 2"); + + // perform sort + $testSorter = new ParallelGroupSorter(); + $testSorter->getTestsGroupedByFixedGroupCount($sampleSuiteArray, $sampleTestArray, 1); + } + + /** + * @inheritDoc + */ + public static function tearDownAfterClass(): void + { + $instanceProperty = new ReflectionProperty(TestObjectHandler::class, 'testObjectHandler'); + $instanceProperty->setAccessible(true); + $instanceProperty->setValue(null); + } + + /** + * Mock test object and test object handler. + * + * @param int $numberOfCalls + * @param array $actionCount + * + * @return void + */ + private function createMockForTest(int $numberOfCalls, array $actionCount = [300, 275, 300, 275]): void + { + $mockTest1 = $this->createMock(TestObject::class); + $mockTest1 + ->method('getEstimatedDuration') + ->willReturnCallback( + function () use (&$numberOfCalls, $actionCount) { + $result = $actionCount[$numberOfCalls]; + $numberOfCalls++; + + return $result; + } + ); + + $mockHandler = $this->createMock(TestObjectHandler::class); + $mockHandler + ->method('getObject') + ->willReturn($mockTest1); + + $instanceProperty = new ReflectionProperty(TestObjectHandler::class, 'testObjectHandler'); + $instanceProperty->setAccessible(true); + $instanceProperty->setValue($mockHandler, $mockHandler); + } } diff --git a/dev/tests/unit/Magento/FunctionalTestFramework/Util/TestGeneratorTest.php b/dev/tests/unit/Magento/FunctionalTestFramework/Util/TestGeneratorTest.php index cadc1c6a8..dbdff8304 100644 --- a/dev/tests/unit/Magento/FunctionalTestFramework/Util/TestGeneratorTest.php +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Util/TestGeneratorTest.php @@ -3,49 +3,176 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); -namespace Tests\unit\Magento\FunctionalTestFramework\Test\Handlers; - -use AspectMock\Test as AspectMock; +namespace tests\unit\Magento\FunctionalTestFramework\Util; +use Exception; +use Magento\FunctionalTestingFramework\Config\MftfApplicationConfig; +use Magento\FunctionalTestingFramework\Exceptions\TestReferenceException; +use Magento\FunctionalTestingFramework\Filter\FilterList; +use Magento\FunctionalTestingFramework\ObjectManager; +use Magento\FunctionalTestingFramework\Test\Handlers\TestObjectHandler; use Magento\FunctionalTestingFramework\Test\Objects\ActionObject; +use Magento\FunctionalTestingFramework\Test\Objects\TestHookObject; use Magento\FunctionalTestingFramework\Test\Objects\TestObject; -use Magento\FunctionalTestingFramework\Util\MagentoTestCase; +use Magento\FunctionalTestingFramework\Util\Filesystem\CestFileCreatorUtil; +use Magento\FunctionalTestingFramework\Util\GenerationErrorHandler; +use Magento\FunctionalTestingFramework\Util\ModuleResolver; use Magento\FunctionalTestingFramework\Util\TestGenerator; -use Magento\FunctionalTestingFramework\Config\MftfApplicationConfig; +use ReflectionClass; +use ReflectionProperty; +use tests\unit\Util\MagentoTestCase; +use tests\unit\Util\TestLoggingUtil; class TestGeneratorTest extends MagentoTestCase { + /** + * @inheritdoc + */ + public static function setUpBeforeClass(): void + { + parent::setUpBeforeClass(); + + $property = new ReflectionProperty(ObjectManager::class, 'instance'); + $property->setAccessible(true); + $property->setValue(null); + + $property = new ReflectionProperty(ModuleResolver::class, 'instance'); + $property->setAccessible(true); + $property->setValue(null); + } + + /** + * Before method functionality. + * + * @return void + */ + protected function setUp(): void + { + TestLoggingUtil::getInstance()->setMockLoggingUtil(); + } + + /** + * After method functionality. + * + * @return void + */ + protected function tearDown(): void + { + GenerationErrorHandler::getInstance()->reset(); + } + /** * Basic test to check exceptions for incorrect entities. * - * @throws \Exception + * @return void + * @throws Exception */ - public function testEntityException() + public function testEntityException(): void { $actionObject = new ActionObject('fakeAction', 'comment', [ 'userInput' => '{{someEntity.entity}}' ]); - $testObject = new TestObject("sampleTest", ["merge123" => $actionObject], [], [], "filename"); + $testObject = new TestObject('sampleTest', ['merge123' => $actionObject], [], [], 'filename'); + $testGeneratorObject = TestGenerator::getInstance('', ['sampleTest' => $testObject]); + $this->mockTestObjectHandler(); + + $testGeneratorObject->createAllTestFiles(null, []); - $testGeneratorObject = TestGenerator::getInstance("", ["sampleTest" => $testObject]); + // assert that no exception for createAllTestFiles and generation error is stored in GenerationErrorHandler + $errorMessage = '/' . preg_quote('Removed invalid test object sampleTest') . '/'; + TestLoggingUtil::getInstance()->validateMockLogStatmentRegex('error', $errorMessage, []); + $testErrors = GenerationErrorHandler::getInstance()->getErrorsByType('test'); + $this->assertArrayHasKey('sampleTest', $testErrors); + } - AspectMock::double(TestGenerator::class, ['loadAllTestObjects' => ["sampleTest" => $testObject]]); + /** + * Basic test to check unique id is appended to input as prefix + * + * @return void + * @throws Exception + */ + public function testUniqueIdAppendedToInputStringAsPrefix() + { + $actionObject = new ActionObject('fakeAction', 'comment', [ + 'userInput' => '{{someEntity.entity}}' + ]); - $this->expectExceptionMessage("Could not resolve entity reference \"{{someEntity.entity}}\" " . - "in Action with stepKey \"fakeAction\".\n" . - "Exception occurred parsing action at StepKey \"fakeAction\" in Test \"sampleTest\""); + $testObject = new TestObject('sampleTest', ['merge123' => $actionObject], [], [], 'filename'); + $testGeneratorObject = TestGenerator::getInstance('', ['sampleTest' => $testObject]); - $testGeneratorObject->createAllTestFiles(null, []); + $result = $testGeneratorObject->getUniqueIdForInput('prefix', "foo"); + + $this->assertMatchesRegularExpression('/[A-Za-z0-9]+foo/', $result); } /** - * Tests that skipped tests do not have a fully generated body + * Basic test to check if exception is thrown when invalid entity is found in xml file * - * @throws \Magento\FunctionalTestingFramework\Exceptions\TestReferenceException + * @return void + * @throws Exception */ - public function testSkippedNoGeneration() + public function testInvalidEntity() + { + $actionObject = new ActionObject('fakeAction', 'comment', [ + 'userInput' => '{{someEntity.entity}}' + ]); + + $testObject = new TestObject('sampleTest', ['merge123' => $actionObject], [], [], 'filename'); + $testGeneratorObject = TestGenerator::getInstance('', ['sampleTest' => $testObject]); + $this->expectException(TestReferenceException::class); + $result = $testGeneratorObject->entityExistsCheck('testintity', "teststepkey"); + } + + /** + * Basic test to check unique id is appended to input as suffix + * + * @return void + * @throws Exception + */ + public function testUniqueIdAppendedToInputStringAsSuffix() + { + $actionObject = new ActionObject('fakeAction', 'comment', [ + 'userInput' => '{{someEntity.entity}}' + ]); + + $testObject = new TestObject('sampleTest', ['merge123' => $actionObject], [], [], 'filename'); + $testGeneratorObject = TestGenerator::getInstance('', ['sampleTest' => $testObject]); + + $result = $testGeneratorObject->getUniqueIdForInput('suffix', "foo"); + + $this->assertMatchesRegularExpression('/foo[A-Za-z0-9]+/', $result); + } + + /** + * Basic test for wrong output for input + * + * @return void + * @throws Exception + */ + public function testFailedRegexForUniqueAttribute() + { + $actionObject = new ActionObject('fakeAction', 'comment', [ + 'userInput' => '{{someEntity.entity}}' + ]); + + $testObject = new TestObject('sampleTest', ['merge123' => $actionObject], [], [], 'filename'); + $testGeneratorObject = TestGenerator::getInstance('', ['sampleTest' => $testObject]); + + $result = $testGeneratorObject->getUniqueIdForInput('suffix', "foo"); + + $this->assertDoesNotMatchRegularExpression('/bar[A-Za-z0-9]+/', $result); + } + + /** + * Tests that skipped tests do not have a fully generated body. + * + * @return void + * @throws TestReferenceException + */ + public function testSkippedNoGeneration(): void { $actionInput = 'fakeInput'; $actionObject = new ActionObject('fakeAction', 'comment', [ @@ -53,37 +180,288 @@ public function testSkippedNoGeneration() ]); $annotations = ['skip' => ['issue']]; - $testObject = new TestObject("sampleTest", ["merge123" => $actionObject], $annotations, [], "filename"); + $testObject = new TestObject('sampleTest', ['merge123' => $actionObject], $annotations, [], 'filename'); - $testGeneratorObject = TestGenerator::getInstance("", ["sampleTest" => $testObject]); + $testGeneratorObject = TestGenerator::getInstance('', ['sampleTest' => $testObject]); $output = $testGeneratorObject->assembleTestPhp($testObject); - $this->assertContains('This test is skipped', $output); - $this->assertNotContains($actionInput, $output); + $this->assertStringContainsString('This test is skipped', $output); + $this->assertStringNotContainsString($actionInput, $output); } /** - * Tests that skipped tests have a fully generated body when --allowSkipped is passed in + * Tests that skipped tests have a fully generated body when --allowSkipped is passed in. * - * @throws \Magento\FunctionalTestingFramework\Exceptions\TestReferenceException + * @return void + * @throws TestReferenceException */ - public function testAllowSkipped() + public function testAllowSkipped(): void { // Mock allowSkipped for TestGenerator - AspectMock::double(MftfApplicationConfig::class, ['allowSkipped' => true]); + $mockConfig = $this->createMock(MftfApplicationConfig::class); + $mockConfig + ->method('allowSkipped') + ->willReturn(true); + + $property = new ReflectionProperty(MftfApplicationConfig::class, 'MFTF_APPLICATION_CONTEXT'); + $property->setAccessible(true); + $property->setValue($mockConfig); $actionInput = 'fakeInput'; $actionObject = new ActionObject('fakeAction', 'comment', [ 'userInput' => $actionInput ]); + $beforeActionInput = 'beforeInput'; + $beforeActionObject = new ActionObject('beforeAction', 'comment', [ + 'userInput' => $beforeActionInput + ]); $annotations = ['skip' => ['issue']]; - $testObject = new TestObject("sampleTest", ["merge123" => $actionObject], $annotations, [], "filename"); + $beforeHook = new TestHookObject('before', 'sampleTest', ['beforeAction' => $beforeActionObject]); + $testObject = new TestObject( + 'sampleTest', + ['fakeAction' => $actionObject], + $annotations, + ['before' => $beforeHook], + 'filename' + ); - $testGeneratorObject = TestGenerator::getInstance("", ["sampleTest" => $testObject]); + $testGeneratorObject = TestGenerator::getInstance('', ['sampleTest' => $testObject]); $output = $testGeneratorObject->assembleTestPhp($testObject); - $this->assertNotContains('This test is skipped', $output); - $this->assertContains($actionInput, $output); + $this->assertStringNotContainsString('This test is skipped', $output); + $this->assertStringContainsString($actionInput, $output); + $this->assertStringContainsString($beforeActionInput, $output); + } + + /** + * Tests that TestGenerator createAllTestFiles correctly filters based on severity. + * + * @return void + * @throws TestReferenceException + */ + public function testSeverityFilter(): void + { + $mockConfig = $this->createMock(MftfApplicationConfig::class); + $fileList = new FilterList(['severity' => ['CRITICAL']]); + $mockConfig + ->method('getFilterList') + ->willReturn($fileList); + + $property = new ReflectionProperty(MftfApplicationConfig::class, 'MFTF_APPLICATION_CONTEXT'); + $property->setAccessible(true); + $property->setValue($mockConfig); + + $actionInput = 'fakeInput'; + $actionObject = new ActionObject('fakeAction', 'comment', [ + 'userInput' => $actionInput + ]); + + $annotation1 = ['severity' => ['CRITICAL']]; + $annotation2 = ['severity' => ['MINOR']]; + $test1 = new TestObject( + 'test1', + ['fakeAction' => $actionObject], + $annotation1, + [], + 'filename' + ); + $test2 = new TestObject( + 'test2', + ['fakeAction' => $actionObject], + $annotation2, + [], + 'filename' + ); + + // Mock createCestFile to return name of tests that testGenerator tried to create + $generatedTests = []; + $cestFileCreatorUtil = $this->createMock(CestFileCreatorUtil::class); + $cestFileCreatorUtil + ->method('create') + ->will( + $this->returnCallback( + function ($filename) use (&$generatedTests) { + $generatedTests[$filename] = true; + } + ) + ); + + $property = new ReflectionProperty(CestFileCreatorUtil::class, 'INSTANCE'); + $property->setAccessible(true); + $property->setValue($cestFileCreatorUtil); + + $testGeneratorObject = TestGenerator::getInstance('', ['sampleTest' => $test1, 'test2' => $test2]); + $testGeneratorObject->createAllTestFiles(); + + // Ensure Test1 was Generated but not Test 2 + $this->assertArrayHasKey('test1Cest', $generatedTests); + $this->assertArrayNotHasKey('test2Cest', $generatedTests); + } + + /** + * Tests that TestGenerator createAllTestFiles correctly filters based on group. + * + * @return void + * @throws TestReferenceException + */ + public function testIncludeGroupFilter(): void + { + $mockConfig = $this->createMock(MftfApplicationConfig::class); + $fileList = new FilterList(['includeGroup' => ['someGroupValue']]); + $mockConfig + ->method('getFilterList') + ->willReturn($fileList); + + $property = new ReflectionProperty(MftfApplicationConfig::class, 'MFTF_APPLICATION_CONTEXT'); + $property->setAccessible(true); + $property->setValue($mockConfig); + + $actionInput = 'fakeInput'; + $actionObject = new ActionObject('fakeAction', 'comment', [ + 'userInput' => $actionInput + ]); + + $annotation1 = ['group' => ['someGroupValue']]; + $annotation2 = ['group' => ['someOtherGroupValue']]; + $test1 = new TestObject( + 'test1', + ['fakeAction' => $actionObject], + $annotation1, + [], + 'filename' + ); + $test2 = new TestObject( + 'test2', + ['fakeAction' => $actionObject], + $annotation2, + [], + 'filename' + ); + + // Mock createCestFile to return name of tests that testGenerator tried to create + $generatedTests = []; + $cestFileCreatorUtil = $this->createMock(CestFileCreatorUtil::class); + $cestFileCreatorUtil + ->method('create') + ->will( + $this->returnCallback( + function ($filename) use (&$generatedTests) { + $generatedTests[$filename] = true; + } + ) + ); + + $property = new ReflectionProperty(CestFileCreatorUtil::class, 'INSTANCE'); + $property->setAccessible(true); + $property->setValue($cestFileCreatorUtil); + + $testGeneratorObject = TestGenerator::getInstance('', ['sampleTest' => $test1, 'test2' => $test2]); + $testGeneratorObject->createAllTestFiles(); + + // Ensure Test1 was Generated but not Test 2 + $this->assertArrayHasKey('test1Cest', $generatedTests); + $this->assertArrayNotHasKey('test2Cest', $generatedTests); + } + + /** + * Tests that TestGenerator createAllTestFiles correctly filters based on group not included. + * + * @return void + * @throws TestReferenceException + */ + public function testExcludeGroupFilter(): void + { + $mockConfig = $this->createMock(MftfApplicationConfig::class); + $fileList = new FilterList(['excludeGroup' => ['someGroupValue']]); + $mockConfig + ->method('getFilterList') + ->willReturn($fileList); + + $property = new ReflectionProperty(MftfApplicationConfig::class, 'MFTF_APPLICATION_CONTEXT'); + $property->setAccessible(true); + $property->setValue($mockConfig); + + $actionInput = 'fakeInput'; + $actionObject = new ActionObject('fakeAction', 'comment', [ + 'userInput' => $actionInput + ]); + + $annotation1 = ['group' => ['someGroupValue']]; + $annotation2 = ['group' => ['someOtherGroupValue']]; + $test1 = new TestObject( + 'test1', + ['fakeAction' => $actionObject], + $annotation1, + [], + 'filename' + ); + $test2 = new TestObject( + 'test2', + ['fakeAction' => $actionObject], + $annotation2, + [], + 'filename' + ); + + // Mock createCestFile to return name of tests that testGenerator tried to create + $generatedTests = []; + $cestFileCreatorUtil = $this->createMock(CestFileCreatorUtil::class); + $cestFileCreatorUtil + ->method('create') + ->will( + $this->returnCallback( + function ($filename) use (&$generatedTests) { + $generatedTests[$filename] = true; + } + ) + ); + + $property = new ReflectionProperty(CestFileCreatorUtil::class, 'INSTANCE'); + $property->setAccessible(true); + $property->setValue($cestFileCreatorUtil); + + $testGeneratorObject = TestGenerator::getInstance('', ['sampleTest' => $test1, 'test2' => $test2]); + $testGeneratorObject->createAllTestFiles(); + + // Ensure Test2 was Generated but not Test 1 + $this->assertArrayNotHasKey('test1Cest', $generatedTests); + $this->assertArrayHasKey('test2Cest', $generatedTests); + } + + /** + * @inheritDoc + */ + public static function tearDownAfterClass(): void + { + parent::tearDownAfterClass(); + + $cestFileCreatorUtilInstance = new ReflectionProperty(CestFileCreatorUtil::class, 'INSTANCE'); + $cestFileCreatorUtilInstance->setAccessible(true); + $cestFileCreatorUtilInstance->setValue(null); + + $mftfAppConfigInstance = new ReflectionProperty(MftfApplicationConfig::class, 'MFTF_APPLICATION_CONTEXT'); + $mftfAppConfigInstance->setAccessible(true); + $mftfAppConfigInstance->setValue(null); + + $property = new ReflectionProperty(TestObjectHandler::class, 'testObjectHandler'); + $property->setAccessible(true); + $property->setValue(null); + } + + /** + * Mock test object handler for test. + */ + private function mockTestObjectHandler(): void + { + $testObjectHandlerClass = new ReflectionClass(TestObjectHandler::class); + $testObjectHandlerConstructor = $testObjectHandlerClass->getConstructor(); + $testObjectHandlerConstructor->setAccessible(true); + $testObjectHandler = $testObjectHandlerClass->newInstanceWithoutConstructor(); + $testObjectHandlerConstructor->invoke($testObjectHandler); + + $property = new ReflectionProperty(TestObjectHandler::class, 'testObjectHandler'); + $property->setAccessible(true); + $property->setValue($testObjectHandler); } } diff --git a/dev/tests/unit/Magento/FunctionalTestFramework/Util/UnusedEntityCheckTest.php b/dev/tests/unit/Magento/FunctionalTestFramework/Util/UnusedEntityCheckTest.php new file mode 100644 index 000000000..c7a170d86 --- /dev/null +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Util/UnusedEntityCheckTest.php @@ -0,0 +1,190 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace tests\unit\Magento\FunctionalTestFramework\Util; + +use Exception; +use Magento\FunctionalTestingFramework\Config\MftfApplicationConfig; +use Magento\FunctionalTestingFramework\Exceptions\TestReferenceException; +use tests\unit\Util\MagentoTestCase; +use tests\unit\Util\TestLoggingUtil; +use Magento\FunctionalTestingFramework\StaticCheck\UnusedEntityCheck; +use Magento\FunctionalTestingFramework\Util\Script\ScriptUtil; + +class UnusedEntityCheckTest extends MagentoTestCase +{ + + public function testUnusedEntityFilesCheck() + { + $unusedEntityCheck = new UnusedEntityCheck(); + $result = $unusedEntityCheck->unusedEntities(); + $this->assertIsArray($result); + } + + public function testUnusedActiongroupFiles() + { + $unusedEntityCheck = new UnusedEntityCheck(); + $domDocument = new \DOMDocument(); + $actionGroupFiles = ['DeprecationCheckActionGroup' => + '/verification/DeprecationCheckModule/ActionGroup/DeprecationCheckActionGroup.xml', + 'ActionGroupWithMultiplePausesActionGroup'=> + '/verification/PauseCheckModule/ActionGroup/ActionGroupWithMultiplePausesActionGroup.xml', + 'ActionGroupWithNoPauseActionGroup'=> + '/verification/PauseCheckModule/ActionGroup/ActionGroupWithNoPauseActionGroup.xml']; + $result = $unusedEntityCheck->unusedActionEntity($domDocument, [], [], $actionGroupFiles, []); + $this->assertIsArray($result); + $this->assertCount(3, $result); + } + + public function testUnusedActiongroupFilesReturnedWhenActionXmlFilesAreNotEmpty() + { + $this->scriptUtil = new ScriptUtil(); + $unusedEntityCheck = new UnusedEntityCheck(); + $domDocument = new \DOMDocument(); + $modulePaths = $this->scriptUtil->getAllModulePaths(); + $actionGroupXmlFiles = $this->scriptUtil->getModuleXmlFilesByScope($modulePaths, "ActionGroup"); + $actionGroupFiles = ['DeprecationCheckActionGroup' => + '/verification/DeprecationCheckModule/ActionGroup/DeprecationCheckActionGroup.xml', + 'ActionGroupWithMultiplePausesActionGroup'=> + '/verification/PauseCheckModule/ActionGroup/ActionGroupWithMultiplePausesActionGroup.xml', + 'ActionGroupWithNoPauseActionGroup' => + '/verification/PauseCheckModule/ActionGroup/ActionGroupWithNoPauseActionGroup.xml']; + $result = $unusedEntityCheck->unusedActionEntity( + $domDocument, + $actionGroupXmlFiles, + [], + $actionGroupFiles, + [] + ); + $this->assertIsArray($result); + $this->assertCount(3, $result); + } + + public function testUnusedSectionFiles() + { + $unusedEntityCheck = new UnusedEntityCheck(); + $domDocument = new \DOMDocument(); + $section = [ + 'DeprecationCheckSection' => '/verification/DeprecationCheckModule/Section/DeprecationCheckSection.xml', + 'DeprecatedSection' => '/verification/TestModule/Section/DeprecatedSection.xml', + 'LocatorFunctionSection' => '/verification/TestModule/Section/LocatorFunctionSection.xml', + 'SampleSection' => '/verification/TestModuleMerged/Section/MergeSection.xml' + ]; + $result=$unusedEntityCheck->unusedSectionEntity($domDocument, [], [], [], $section, []); + $this->assertIsArray($result); + $this->assertCount(4, $result); + } + + public function testUnusedSectionFilesReturnedWhenSectionXmlFilesAreNotEmpty() + { + $unusedEntityCheck = new UnusedEntityCheck(); + $this->scriptUtil = new ScriptUtil(); + $modulePaths = $this->scriptUtil->getAllModulePaths(); + $sectionXmlFiles = $this->scriptUtil->getModuleXmlFilesByScope($modulePaths, "Section"); + + $domDocument = new \DOMDocument(); + $section = [ + 'DeprecationCheckSection' => '/verification/DeprecationCheckModule/Section/DeprecationCheckSection.xml', + 'DeprecatedSection' => '/verification/TestModule/Section/DeprecatedSection.xml', + 'LocatorFunctionSection' => '/verification/TestModule/Section/LocatorFunctionSection.xml', + 'SampleSection' => '/verification/TestModuleMerged/Section/MergeSection.xml' + ]; + $result = $unusedEntityCheck->unusedSectionEntity($domDocument, $sectionXmlFiles, [], [], $section, []); + $this->assertIsArray($result); + $this->assertCount(4, $result); + } + + public function testUnusedPageFiles() + { + $unusedEntityCheck = new UnusedEntityCheck(); + $domDocument = new \DOMDocument(); + $page = [ + 'DeprecationCheckPage' => '/verification/DeprecationCheckModule/Page/DeprecationCheckPage.xml', + 'DeprecatedPage' => '/verification/TestModule/Page/DeprecatedPage.xml', + 'AdminOneParamPage' => '/verification/TestModule/Page/SamplePage/AdminOneParamPage.xml', + 'AdminPage' => '/verification/TestModule/Page/SamplePage/AdminPage.xml', + 'ExternalPage' => '/verification/TestModule/Page/SamplePage/ExternalPage.xml', + 'NoParamPage' => '/verification/TestModule/Page/SamplePage/NoParamPage.xml' + ]; + $result = $unusedEntityCheck->unusedPageEntity($domDocument, [], [], $page, []); + $this->assertIsArray($result); + $this->assertCount(6, $result); + } + + public function testUnusedPageFilesReturnedWhenPageXmlFilesPassedAreNotEmpty() + { + $unusedEntityCheck = new UnusedEntityCheck(); + $domDocument = new \DOMDocument(); + $this->scriptUtil = new ScriptUtil(); + $modulePaths = $this->scriptUtil->getAllModulePaths(); + $pageXmlFiles = $this->scriptUtil->getModuleXmlFilesByScope($modulePaths, "Page"); + $page = [ + 'DeprecationCheckPage' => '/verification/DeprecationCheckModule/Page/DeprecationCheckPage.xml', + 'DeprecatedPage' => '/verification/TestModule/Page/DeprecatedPage.xml', + 'AdminOneParamPage' => '/verification/TestModule/Page/SamplePage/AdminOneParamPage.xml', + 'AdminPage' => '/verification/TestModule/Page/SamplePage/AdminPage.xml', + 'ExternalPage' => '/verification/TestModule/Page/SamplePage/ExternalPage.xml', + 'NoParamPage' => '/verification/TestModule/Page/SamplePage/NoParamPage.xml' + ]; + $result = $unusedEntityCheck->unusedPageEntity($domDocument, $pageXmlFiles, [], $page, []); + $this->assertIsArray($result); + $this->assertCount(6, $result); + } + + public function testUnusedData() + { + $unusedEntityCheck = new UnusedEntityCheck(); + $domDocument = new \DOMDocument(); + $data = [ + "simpleData" => + [ + "dataFilePath" => "/verification/TestModule/Data/ReplacementData.xml" + ], + + "uniqueData" => [ + + "dataFilePath" => "/verification/TestModule/Data/ReplacementData.xml" + ], + + "offset"=> + [ + "dataFilePath" => "/verification/TestModule/Data/ReplacementData.xml" + ] + ]; + $result=$unusedEntityCheck->unusedPageEntity($domDocument, [], [], $data, []); + $this->assertIsArray($result); + $this->assertCount(3, $result); + } + + public function testUnusedDataReturnedWhenCreateDataEntityAreNotEmpty() + { + $unusedEntityCheck = new UnusedEntityCheck(); + $domDocument = new \DOMDocument(); + $this->scriptUtil = new ScriptUtil(); + $modulePaths = $this->scriptUtil->getAllModulePaths(); + $dataXmlFiles = $this->scriptUtil->getModuleXmlFilesByScope($modulePaths, "Data"); + $data = [ + "simpleData" => + [ + "dataFilePath" => "/verification/TestModule/Data/ReplacementData.xml" + ], + + "uniqueData" => [ + + "dataFilePath" => "/verification/TestModule/Data/ReplacementData.xml" + ], + + "offset" => + [ + "dataFilePath" => "/verification/TestModule/Data/ReplacementData.xml" + ] + ]; + $result = $unusedEntityCheck->unusedPageEntity($domDocument, $dataXmlFiles, [], $data, []); + $this->assertIsArray($result); + $this->assertCount(3, $result); + } +} diff --git a/dev/tests/unit/Magento/FunctionalTestFramework/Util/Validation/DuplicateNodeValidationUtilTest.php b/dev/tests/unit/Magento/FunctionalTestFramework/Util/Validation/DuplicateNodeValidationUtilTest.php index 5dba1cb8b..db117c6b6 100644 --- a/dev/tests/unit/Magento/FunctionalTestFramework/Util/Validation/DuplicateNodeValidationUtilTest.php +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Util/Validation/DuplicateNodeValidationUtilTest.php @@ -4,13 +4,11 @@ * See COPYING.txt for license details. */ -namespace tests\unit\Magento\FunctionalTestFramework\Test\Util; +namespace tests\unit\Magento\FunctionalTestFramework\Util\Validation; use Magento\FunctionalTestingFramework\Exceptions\Collector\ExceptionCollector; -use Magento\FunctionalTestingFramework\Exceptions\XmlException; use Magento\FunctionalTestingFramework\Util\Validation\DuplicateNodeValidationUtil; -use Magento\FunctionalTestingFramework\Util\Validation\NameValidationUtil; -use Magento\FunctionalTestingFramework\Util\MagentoTestCase; +use tests\unit\Util\MagentoTestCase; class DuplicateNodeValidationUtilTest extends MagentoTestCase { diff --git a/dev/tests/unit/Magento/FunctionalTestFramework/Util/Validation/NameValidationUtilTest.php b/dev/tests/unit/Magento/FunctionalTestFramework/Util/Validation/NameValidationUtilTest.php index 28d75b1ad..d43eb5282 100644 --- a/dev/tests/unit/Magento/FunctionalTestFramework/Util/Validation/NameValidationUtilTest.php +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Util/Validation/NameValidationUtilTest.php @@ -4,11 +4,11 @@ * See COPYING.txt for license details. */ -namespace tests\unit\Magento\FunctionalTestFramework\Test\Util; +namespace tests\unit\Magento\FunctionalTestFramework\Util\Validation; use Magento\FunctionalTestingFramework\Exceptions\XmlException; use Magento\FunctionalTestingFramework\Util\Validation\NameValidationUtil; -use Magento\FunctionalTestingFramework\Util\MagentoTestCase; +use tests\unit\Util\MagentoTestCase; class NameValidationUtilTest extends MagentoTestCase { @@ -17,7 +17,7 @@ class NameValidationUtilTest extends MagentoTestCase */ public function testCurlyBracesInTestName() { - $this->validateBlacklistedTestName("{{curlyBraces}}"); + $this->validateBlocklistedTestName("{{curlyBraces}}"); } /** @@ -25,7 +25,7 @@ public function testCurlyBracesInTestName() */ public function testQuotesInTestName() { - $this->validateBlacklistedTestName("\"quotes\""); + $this->validateBlocklistedTestName("\"quotes\""); } /** @@ -33,7 +33,7 @@ public function testQuotesInTestName() */ public function testSingleQuotesInTestName() { - $this->validateBlacklistedTestName("'singleQuotes'"); + $this->validateBlocklistedTestName("'singleQuotes'"); } /** @@ -41,7 +41,7 @@ public function testSingleQuotesInTestName() */ public function testParenthesesInTestName() { - $this->validateBlacklistedTestName("(parenthesis)"); + $this->validateBlocklistedTestName("(parenthesis)"); } /** @@ -49,7 +49,7 @@ public function testParenthesesInTestName() */ public function testDollarSignInTestName() { - $this->validateBlacklistedTestName("\$dollarSign\$"); + $this->validateBlocklistedTestName("\$dollarSign\$"); } /** @@ -57,7 +57,7 @@ public function testDollarSignInTestName() */ public function testSpacesInTestName() { - $this->validateBlacklistedTestName("Test Name With Spaces"); + $this->validateBlocklistedTestName("Test Name With Spaces"); } /** @@ -66,7 +66,7 @@ public function testSpacesInTestName() * @param string $testName * @return void */ - private function validateBlacklistedTestName($testName) + private function validateBlocklistedTestName($testName) { $this->expectException(XmlException::class); NameValidationUtil::validateName($testName, "Test"); diff --git a/dev/tests/unit/Util/EntityDataObjectBuilder.php b/dev/tests/unit/Util/EntityDataObjectBuilder.php index dfe7d6836..82496d2ce 100644 --- a/dev/tests/unit/Util/EntityDataObjectBuilder.php +++ b/dev/tests/unit/Util/EntityDataObjectBuilder.php @@ -18,7 +18,8 @@ class EntityDataObjectBuilder "name" => "Hopper", "gpa" => "3.5678", "phone" => "5555555", - "isprimary" => "true" + "isprimary" => "true", + "empty_string" => "" ]; /** diff --git a/dev/tests/unit/Util/MagentoTestCase.php b/dev/tests/unit/Util/MagentoTestCase.php index 6a4e5d35b..13c79403f 100644 --- a/dev/tests/unit/Util/MagentoTestCase.php +++ b/dev/tests/unit/Util/MagentoTestCase.php @@ -3,10 +3,10 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); -namespace Magento\FunctionalTestingFramework\Util; +namespace tests\unit\Util; -use AspectMock\Test as AspectMock; use PHPUnit\Framework\TestCase; /** @@ -14,22 +14,25 @@ */ class MagentoTestCase extends TestCase { - public static function setUpBeforeClass() + /** + * @inheritDoc + */ + public static function setUpBeforeClass(): void { if (!self::fileExists(DOCS_OUTPUT_DIR)) { mkdir(DOCS_OUTPUT_DIR, 0755, true); } + parent::setUpBeforeClass(); } /** - * Teardown for removing AspectMock Double References - * @return void + * @inheritDoc */ - public static function tearDownAfterClass() + public static function tearDownAfterClass(): void { - AspectMock::clean(); array_map('unlink', glob(DOCS_OUTPUT_DIR . DIRECTORY_SEPARATOR . "*")); + if (file_exists(DOCS_OUTPUT_DIR)) { rmdir(DOCS_OUTPUT_DIR); } diff --git a/dev/tests/unit/Util/MockModuleResolverBuilder.php b/dev/tests/unit/Util/MockModuleResolverBuilder.php deleted file mode 100644 index 0e1b6fc31..000000000 --- a/dev/tests/unit/Util/MockModuleResolverBuilder.php +++ /dev/null @@ -1,61 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -namespace tests\unit\Util; - -use AspectMock\Test as AspectMock; -use Magento\FunctionalTestingFramework\ObjectManager; -use Magento\FunctionalTestingFramework\ObjectManagerFactory; -use Magento\FunctionalTestingFramework\Util\ModuleResolver; -use Magento\FunctionalTestingFramework\Config\MftfApplicationConfig; - -class MockModuleResolverBuilder -{ - /** - * Default paths for mock ModuleResolver - * - * @var array - */ - private $defaultPaths = ['Magento_Module' => '/base/path/some/other/path/Magento/Module']; - - /** - * Mock ModuleResolver builder - * - * @param array $paths - * @return void - * @throws \Exception - */ - public function setup($paths = null) - { - if (empty($paths)) { - $paths = $this->defaultPaths; - } - - $mockConfig = AspectMock::double(MftfApplicationConfig::class, ['forceGenerateEnabled' => false]); - $instance = AspectMock::double(ObjectManager::class, ['create' => $mockConfig->make(), 'get' => null])->make(); - AspectMock::double(ObjectManagerFactory::class, ['getObjectManager' => $instance]); - - $property = new \ReflectionProperty(ModuleResolver::class, 'instance'); - $property->setAccessible(true); - $property->setValue(null); - - $mockResolver = AspectMock::double( - ModuleResolver::class, - [ - 'getAdminToken' => false, - 'globRelevantPaths' => [], - 'getEnabledModules' => [] - ] - ); - $instance = AspectMock::double(ObjectManager::class, ['create' => $mockResolver->make(), 'get' => null]) - ->make(); - AspectMock::double(ObjectManagerFactory::class, ['getObjectManager' => $instance]); - - $resolver = ModuleResolver::getInstance(); - $property = new \ReflectionProperty(ModuleResolver::class, 'enabledModuleNameAndPaths'); - $property->setAccessible(true); - $property->setValue($resolver, $paths); - } -} diff --git a/dev/tests/unit/Util/OperationElementBuilder.php b/dev/tests/unit/Util/OperationElementBuilder.php index 2c2a4d9da..69e848de1 100644 --- a/dev/tests/unit/Util/OperationElementBuilder.php +++ b/dev/tests/unit/Util/OperationElementBuilder.php @@ -20,7 +20,8 @@ class OperationElementBuilder 'name' => 'string', 'gpa' => 'number', 'phone' => 'integer', - 'isPrimary' => 'boolean' + 'isPrimary' => 'boolean', + 'empty_string' => 'string' ]; /** diff --git a/dev/tests/unit/Util/SuiteDataArrayBuilder.php b/dev/tests/unit/Util/SuiteDataArrayBuilder.php index 85411b447..9f937707a 100644 --- a/dev/tests/unit/Util/SuiteDataArrayBuilder.php +++ b/dev/tests/unit/Util/SuiteDataArrayBuilder.php @@ -181,7 +181,7 @@ private function appendEntriesToSuiteContents($currentContents, $type, $contents */ public function withAfterHook($afterHook = null) { - if ($afterHook == null) { + if ($afterHook === null) { $this->afterHook = [$this->testActionAfterName => [ ActionObjectExtractor::NODE_NAME => $this->testActionType, ActionObjectExtractor::TEST_STEP_MERGE_KEY => $this->testActionAfterName @@ -202,7 +202,7 @@ public function withAfterHook($afterHook = null) */ public function withBeforeHook($beforeHook = null) { - if ($beforeHook == null) { + if ($beforeHook === null) { $this->beforeHook = [$this->testActionBeforeName => [ ActionObjectExtractor::NODE_NAME => $this->testActionType, ActionObjectExtractor::TEST_STEP_MERGE_KEY => $this->testActionBeforeName diff --git a/dev/tests/unit/Util/TestDataArrayBuilder.php b/dev/tests/unit/Util/TestDataArrayBuilder.php index eb192e040..0a9672dd7 100644 --- a/dev/tests/unit/Util/TestDataArrayBuilder.php +++ b/dev/tests/unit/Util/TestDataArrayBuilder.php @@ -109,7 +109,7 @@ public function withName($name) */ public function withAnnotations($annotations = null) { - if ($annotations == null) { + if ($annotations === null) { $this->annotations = ['group' => [['value' => 'test']]]; } else { $this->annotations = $annotations; @@ -126,7 +126,7 @@ public function withAnnotations($annotations = null) */ public function withBeforeHook($beforeHook = null) { - if ($beforeHook == null) { + if ($beforeHook === null) { $this->beforeHook = [$this->testActionBeforeName => [ ActionObjectExtractor::NODE_NAME => $this->testActionType, ActionObjectExtractor::TEST_STEP_MERGE_KEY => $this->testActionBeforeName @@ -146,7 +146,7 @@ public function withBeforeHook($beforeHook = null) */ public function withAfterHook($afterHook = null) { - if ($afterHook == null) { + if ($afterHook === null) { $this->afterHook = [$this->testActionAfterName => [ ActionObjectExtractor::NODE_NAME => $this->testActionType, ActionObjectExtractor::TEST_STEP_MERGE_KEY => $this->testActionAfterName @@ -167,7 +167,7 @@ public function withAfterHook($afterHook = null) */ public function withFailedHook($failedHook = null) { - if ($failedHook == null) { + if ($failedHook === null) { $this->failedHook = [$this->testActionFailedName => [ ActionObjectExtractor::NODE_NAME => $this->testActionType, ActionObjectExtractor::TEST_STEP_MERGE_KEY => $this->testActionFailedName @@ -188,7 +188,7 @@ public function withFailedHook($failedHook = null) */ public function withTestActions($actions = null) { - if ($actions == null) { + if ($actions === null) { $this->testActions = [$this->testTestActionName => [ ActionObjectExtractor::NODE_NAME => $this->testActionType, ActionObjectExtractor::TEST_STEP_MERGE_KEY => $this->testTestActionName @@ -207,7 +207,7 @@ public function withTestActions($actions = null) */ public function withFileName($filename = null) { - if ($filename == null) { + if ($filename === null) { $this->filename = "/magento2-functional-testing-framework/dev/tests/verification/TestModule/Test/BasicFunctionalTest.xml"; } else { @@ -232,6 +232,28 @@ public function withTestReference($reference = null) return $this; } + /** + * Reset data array builder + * + * @return void + */ + public function reset() + { + // reset + $this->testName = 'testTest'; + $this->filename = null; + $this->testActionBeforeName = 'testActionBefore'; + $this->testActionAfterName = 'testActionAfter'; + $this->testActionFailedName = 'testActionFailed'; + $this->testActionType = 'testAction'; + $this->annotations = []; + $this->beforeHook = []; + $this->afterHook = []; + $this->failedHook = []; + $this->testReference = null; + $this->testActions = []; + } + /** * Output the resulting test data array based on parameters set in the object * diff --git a/dev/tests/unit/Util/TestLoggingUtil.php b/dev/tests/unit/Util/TestLoggingUtil.php index 7becc609a..ed29f091c 100644 --- a/dev/tests/unit/Util/TestLoggingUtil.php +++ b/dev/tests/unit/Util/TestLoggingUtil.php @@ -3,16 +3,17 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); namespace tests\unit\Util; -use AspectMock\Test as AspectMock; use Magento\FunctionalTestingFramework\Util\Logger\LoggingUtil; use Magento\FunctionalTestingFramework\Util\Logger\MftfLogger; use Monolog\Handler\TestHandler; -use PHPUnit\Framework\Assert; +use PHPUnit\Framework\TestCase; +use ReflectionProperty; -class TestLoggingUtil extends Assert +class TestLoggingUtil extends TestCase { /** * @var TestLoggingUtil @@ -25,24 +26,23 @@ class TestLoggingUtil extends Assert private $testLogHandler; /** - * TestLoggingUtil constructor. + * Private constructor. */ private function __construct() { - // private constructor + parent::__construct(null, [], ''); } /** - * Static singleton get function + * Static singleton get function. * * @return TestLoggingUtil */ - public static function getInstance() + public static function getInstance(): TestLoggingUtil { - if (self::$instance == null) { + if (self::$instance === null) { self::$instance = new TestLoggingUtil(); } - return self::$instance; } @@ -51,21 +51,28 @@ public static function getInstance() * * @return void */ - public function setMockLoggingUtil() + public function setMockLoggingUtil(): void { $this->testLogHandler = new TestHandler(); $testLogger = new MftfLogger('testLogger'); $testLogger->pushHandler($this->testLogHandler); - $mockLoggingUtil = AspectMock::double( - LoggingUtil::class, - ['getLogger' => $testLogger] - )->make(); - $property = new \ReflectionProperty(LoggingUtil::class, 'instance'); + + $mockLoggingUtil = $this->createMock(LoggingUtil::class); + $mockLoggingUtil + ->method('getLogger') + ->willReturn($testLogger); + + $property = new ReflectionProperty(LoggingUtil::class, 'instance'); $property->setAccessible(true); $property->setValue($mockLoggingUtil); } - public function validateMockLogEmpty() + /** + * Check if mock log is empty. + * + * @return void + */ + public function validateMockLogEmpty(): void { $records = $this->testLogHandler->getRecords(); $this->assertTrue(empty($records)); @@ -77,9 +84,10 @@ public function validateMockLogEmpty() * @param string $type * @param string $message * @param array $context + * * @return void */ - public function validateMockLogStatement($type, $message, $context) + public function validateMockLogStatement(string $type, string $message, array $context): void { $records = $this->testLogHandler->getRecords(); $record = $records[count($records)-1]; // we assume the latest record is what requires validation @@ -88,12 +96,21 @@ public function validateMockLogStatement($type, $message, $context) $this->assertEquals($context, $record['context']); } - public function validateMockLogStatmentRegex($type, $regex, $context) + /** + * Check mock log statement regular expression. + * + * @param string $type + * @param string $regex + * @param array $context + * + * @return void + */ + public function validateMockLogStatmentRegex(string $type, string $regex, array $context): void { $records = $this->testLogHandler->getRecords(); $record = $records[count($records)-1]; // we assume the latest record is what requires validation $this->assertEquals(strtoupper($type), $record['level_name']); - $this->assertRegExp($regex, $record['message']); + $this->assertMatchesRegularExpression($regex, $record['message']); $this->assertEquals($context, $record['context']); } @@ -103,8 +120,10 @@ public function validateMockLogStatmentRegex($type, $regex, $context) * * @return void */ - public function clearMockLoggingUtil() + public function clearMockLoggingUtil(): void { - AspectMock::clean(LoggingUtil::class); + $property = new ReflectionProperty(LoggingUtil::class, 'instance'); + $property->setAccessible(true); + $property->setValue(null); } } diff --git a/dev/tests/util/MftfStaticTestCase.php b/dev/tests/util/MftfStaticTestCase.php new file mode 100644 index 000000000..708561031 --- /dev/null +++ b/dev/tests/util/MftfStaticTestCase.php @@ -0,0 +1,49 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace tests\util; + +use Magento\FunctionalTestingFramework\Util\Filesystem\DirSetupUtil; +use PHPUnit\Framework\TestCase; +use Symfony\Component\Console\Input\InputInterface; + +class MftfStaticTestCase extends TestCase +{ + const STATIC_RESULTS_DIR = TESTS_MODULE_PATH . + DIRECTORY_SEPARATOR . + '_output' . + DIRECTORY_SEPARATOR . + 'static-results'; + + const RESOURCES_PATH = TESTS_MODULE_PATH . + DIRECTORY_SEPARATOR . + "Resources" . + DIRECTORY_SEPARATOR . + 'StaticChecks'; + + /** + * Sets input interface + * @param $path + * @return \PHPUnit\Framework\MockObject\MockObject + */ + public function mockInputInterface($path = null) + { + $input = $this->getMockBuilder(InputInterface::class) + ->disableOriginalConstructor() + ->getMock(); + if ($path) { + $input->method('getOption') + ->with('path') + ->willReturn($path); + } + return $input; + } + + public function tearDown(): void + { + DirSetupUtil::rmdirRecursive(self::STATIC_RESULTS_DIR); + } +} diff --git a/dev/tests/util/MftfTestCase.php b/dev/tests/util/MftfTestCase.php index 8bfa1009f..a563186b3 100644 --- a/dev/tests/util/MftfTestCase.php +++ b/dev/tests/util/MftfTestCase.php @@ -6,6 +6,8 @@ namespace tests\util; use Magento\FunctionalTestingFramework\ObjectManager; +use Magento\FunctionalTestingFramework\Suite\Handlers\SuiteObjectHandler; +use Magento\FunctionalTestingFramework\Suite\SuiteGenerator; use Magento\FunctionalTestingFramework\Test\Handlers\TestObjectHandler; use Magento\FunctionalTestingFramework\Util\TestGenerator; use PHPUnit\Framework\TestCase; @@ -80,6 +82,28 @@ public function validateSchemaErrorWithTest($fileContents, $objectType ,$expecte } } + /** + * Asserts that the given callback throws the given exception + * + * @param string $expectClass + * @param array $expectedMessages + * @param callable $callback + */ + protected function assertExceptionRegex(string $expectClass, array $expectedMessages, callable $callback) + { + try { + $callback(); + } catch (\Throwable $exception) { + $this->assertInstanceOf($expectClass, $exception, 'An invalid exception was thrown.'); + foreach ($expectedMessages as $expectedMessage) { + $this->assertMatchesRegularExpression($expectedMessage, $exception->getMessage()); + } + return; + } + + $this->fail('No exception was thrown.'); + } + /** * Clears test handler and object manager to force recollection of test data * @@ -96,5 +120,15 @@ private function clearHandler() $property = new \ReflectionProperty(ObjectManager::class, 'instance'); $property->setAccessible(true); $property->setValue(null); + + // clear suite generator to force recollection of test data + $property = new \ReflectionProperty(SuiteGenerator::class, 'instance'); + $property->setAccessible(true); + $property->setValue(null); + + // clear suite object handler to force recollection of test data + $property = new \ReflectionProperty(SuiteObjectHandler::class, 'instance'); + $property->setAccessible(true); + $property->setValue(null); } } diff --git a/dev/tests/verification/DeprecationCheckModule/ActionGroup/DeprecationCheckActionGroup.xml b/dev/tests/verification/DeprecationCheckModule/ActionGroup/DeprecationCheckActionGroup.xml new file mode 100644 index 000000000..c83dab211 --- /dev/null +++ b/dev/tests/verification/DeprecationCheckModule/ActionGroup/DeprecationCheckActionGroup.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="DeprecationCheckActionGroup"> + <see stepKey="deprecatedSee" userInput="text" selector="{{DeprecationCheckSection.deprecationCheckElement}}" /> + </actionGroup> +</actionGroups> diff --git a/dev/tests/verification/DeprecationCheckModule/ActionGroup/DeprecationCheckDeprecatedActionGroup.xml b/dev/tests/verification/DeprecationCheckModule/ActionGroup/DeprecationCheckDeprecatedActionGroup.xml new file mode 100644 index 000000000..e8412a29e --- /dev/null +++ b/dev/tests/verification/DeprecationCheckModule/ActionGroup/DeprecationCheckDeprecatedActionGroup.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="DeprecationCheckDeprecatedActionGroup" deprecated="deprecated"> + <see stepKey="deprecatedSee" userInput="text" selector="{{DeprecationCheckSection.deprecationCheckElement}}" /> + </actionGroup> +</actionGroups> diff --git a/dev/tests/verification/DeprecationCheckModule/Data/DeprecationCheckData.xml b/dev/tests/verification/DeprecationCheckModule/Data/DeprecationCheckData.xml new file mode 100644 index 000000000..75a6b6678 --- /dev/null +++ b/dev/tests/verification/DeprecationCheckModule/Data/DeprecationCheckData.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="DeprecationCheckData" type="type1" deprecated="deprecated"> + <data key="field">value</data> + </entity> +</entities> diff --git a/dev/tests/verification/DeprecationCheckModule/Metadata/DeprecationCheckMeta.xml b/dev/tests/verification/DeprecationCheckModule/Metadata/DeprecationCheckMeta.xml new file mode 100644 index 000000000..757c6284b --- /dev/null +++ b/dev/tests/verification/DeprecationCheckModule/Metadata/DeprecationCheckMeta.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<operations xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataOperation.xsd"> + <operation name="DeprecationCheckMeta" dataType="type1" type="create" auth="adminFormKey" url="/V1/test" method="POST" deprecated="deprecated"> + <contentType>application/json</contentType> + <object key="category" dataType="type1"> + <field key="key1">value1</field> + <field key="key2">value2</field> + </object> + </operation> +</operations> \ No newline at end of file diff --git a/dev/tests/verification/DeprecationCheckModule/Page/DeprecationCheckPage.xml b/dev/tests/verification/DeprecationCheckModule/Page/DeprecationCheckPage.xml new file mode 100644 index 000000000..dd3f187dc --- /dev/null +++ b/dev/tests/verification/DeprecationCheckModule/Page/DeprecationCheckPage.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> + <page name="DeprecationCheckPage" url="/test.html" area="storefront" module="UnknownVendor_DeprecationCheckModule" deprecated="Deprecated page"> + <section name="DeprecationCheckSection"/> + </page> +</pages> diff --git a/dev/tests/verification/DeprecationCheckModule/Section/DeprecationCheckSection.xml b/dev/tests/verification/DeprecationCheckModule/Section/DeprecationCheckSection.xml new file mode 100644 index 000000000..1837e1ef5 --- /dev/null +++ b/dev/tests/verification/DeprecationCheckModule/Section/DeprecationCheckSection.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="DeprecationCheckSection" deprecated="deprecated"> + <element name="deprecationCheckElement" type="button" selector="#elementOne" deprecated="deprecated"/> + <element name="elementTwo" type="button" selector="#elementTwo"/> + <element name="elementThree" type="button" selector="#elementThree"/> + </section> +</sections> diff --git a/dev/tests/verification/DeprecationCheckModule/Suite/deprecationCheckSuite.xml b/dev/tests/verification/DeprecationCheckModule/Suite/deprecationCheckSuite.xml new file mode 100644 index 000000000..b89ad99f6 --- /dev/null +++ b/dev/tests/verification/DeprecationCheckModule/Suite/deprecationCheckSuite.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<suites xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Suite/etc/suiteSchema.xsd"> + <suite name="deprecationCheckSuite"> + <include> + <test name="DeprecationCheckDeprecatedTest"/> + <test name="DeprecationCheckTest"/> + </include> + </suite> +</suites> diff --git a/dev/tests/verification/DeprecationCheckModule/Test/DeprecationCheckDeprecatedTest.xml b/dev/tests/verification/DeprecationCheckModule/Test/DeprecationCheckDeprecatedTest.xml new file mode 100644 index 000000000..1b3ace053 --- /dev/null +++ b/dev/tests/verification/DeprecationCheckModule/Test/DeprecationCheckDeprecatedTest.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="DeprecationCheckDeprecatedTest" deprecated="deprecated"> + <createData entity="DeprecationCheckData" stepKey="deprecatedCreateData"/> + <actionGroup ref="DeprecationCheckActionGroup" stepKey="deprecationCheckActionGroup" /> + <amOnPage url="{{DeprecationCheckPage.url}}" stepKey="deprecatedAmOnPage" /> + <actionGroup ref="DeprecationCheckDeprecatedActionGroup" stepKey="deprecationCheckDeprecatedActionGroup" /> + </test> +</tests> diff --git a/dev/tests/verification/DeprecationCheckModule/Test/DeprecationCheckTest.xml b/dev/tests/verification/DeprecationCheckModule/Test/DeprecationCheckTest.xml new file mode 100644 index 000000000..1c4fc3dba --- /dev/null +++ b/dev/tests/verification/DeprecationCheckModule/Test/DeprecationCheckTest.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="DeprecationCheckTest"> + <createData entity="DeprecationCheckData" stepKey="deprecatedCreateData"/> + <actionGroup ref="DeprecationCheckActionGroup" stepKey="deprecationCheckActionGroup" /> + <amOnPage url="{{DeprecationCheckPage.url}}" stepKey="deprecatedAmOnPage" /> + <actionGroup ref="DeprecationCheckDeprecatedActionGroup" stepKey="deprecationCheckDeprecatedActionGroup" /> + </test> +</tests> diff --git a/dev/tests/verification/PauseCheckModule/ActionGroup/ActionGroupWithMultiplePausesActionGroup.xml b/dev/tests/verification/PauseCheckModule/ActionGroup/ActionGroupWithMultiplePausesActionGroup.xml new file mode 100644 index 000000000..5e9299631 --- /dev/null +++ b/dev/tests/verification/PauseCheckModule/ActionGroup/ActionGroupWithMultiplePausesActionGroup.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="ActionGroupWithMultiplePausesActionGroup"> + <fillField selector="#foo" userInput="foo" stepKey="fillField1"/> + <pause stepKey="pauseAfterFillField1"/> + <fillField selector="#bar" userInput="bar" stepKey="fillField2"/> + <pause stepKey="pauseAfterFillField2"/> + <fillField selector="#baz" userInput="baz" stepKey="fillField3"/> + <pause stepKey="pauseAfterFillField3"/> + </actionGroup> +</actionGroups> diff --git a/dev/tests/verification/PauseCheckModule/ActionGroup/ActionGroupWithNoPauseActionGroup.xml b/dev/tests/verification/PauseCheckModule/ActionGroup/ActionGroupWithNoPauseActionGroup.xml new file mode 100644 index 000000000..8acae47e8 --- /dev/null +++ b/dev/tests/verification/PauseCheckModule/ActionGroup/ActionGroupWithNoPauseActionGroup.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="ActionGroupWithNoPauseActionGroup"> + <fillField selector="#foo" userInput="foo" stepKey="fillField1"/> + <fillField selector="#bar" userInput="bar" stepKey="fillField2"/> + <fillField selector="#baz" userInput="baz" stepKey="fillField3"/> + </actionGroup> +</actionGroups> diff --git a/dev/tests/verification/PauseCheckModule/ActionGroup/ActionGroupWithPauseActionGroup.xml b/dev/tests/verification/PauseCheckModule/ActionGroup/ActionGroupWithPauseActionGroup.xml new file mode 100644 index 000000000..aaef1befa --- /dev/null +++ b/dev/tests/verification/PauseCheckModule/ActionGroup/ActionGroupWithPauseActionGroup.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="ActionGroupWithPauseActionGroup"> + <fillField selector="#foo" userInput="myData1" stepKey="fillField1"/> + <fillField selector="#bar" userInput="myData2" stepKey="fillField2"/> + <pause stepKey="pauseAfterFillField2"/> + </actionGroup> +</actionGroups> diff --git a/dev/tests/verification/PauseCheckModule/Suite/suiteWithMultiplePauseActionsSuite.xml b/dev/tests/verification/PauseCheckModule/Suite/suiteWithMultiplePauseActionsSuite.xml new file mode 100644 index 000000000..47ff1088b --- /dev/null +++ b/dev/tests/verification/PauseCheckModule/Suite/suiteWithMultiplePauseActionsSuite.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<suites xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Suite/etc/suiteSchema.xsd"> + <suite name="suiteWithMultiplePauseActionsSuite"> + <include> + <group name="include" /> + </include> + <before> + <amOnPage url="some.url" stepKey="before"/> + <createData entity="SecretData" stepKey="create1"> + <field key="someKey">dataHere</field> + </createData> + <pause stepKey="pauseCreate1"/> + </before> + <after> + <deleteData createDataKey="create1" stepKey="delete1"/> + <deleteData url="deleteThis" stepKey="deleteThis"/> + <fillField selector="#fill" userInput="{{SecretData.key2}}" stepKey="fillAfter"/> + <pause stepKey="pauseFillAfter"/> + </after> + </suite> +</suites> diff --git a/dev/tests/verification/PauseCheckModule/Suite/suiteWithPauseActionSuite.xml b/dev/tests/verification/PauseCheckModule/Suite/suiteWithPauseActionSuite.xml new file mode 100644 index 000000000..71a3b5769 --- /dev/null +++ b/dev/tests/verification/PauseCheckModule/Suite/suiteWithPauseActionSuite.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<suites xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Suite/etc/suiteSchema.xsd"> + <suite name="suiteWithPauseActionSuite"> + <include> + <group name="include" /> + </include> + <before> + <amOnPage url="some.url" stepKey="before"/> + <createData entity="createThis" stepKey="create"> + <field key="someKey">dataHere</field> + </createData> + <pause stepKey="pauseSuite"/> + <click stepKey="clickWithData" userInput="$create.data$"/> + <fillField selector="#foo" userInput="myData1" stepKey="fillField1"/> + </before> + <after> + <comment userInput="afterBlock" stepKey="afterBlock"/> + </after> + </suite> +</suites> diff --git a/dev/tests/verification/PauseCheckModule/Test/TestWithMultiplePauseActionsTest.xml b/dev/tests/verification/PauseCheckModule/Test/TestWithMultiplePauseActionsTest.xml new file mode 100644 index 000000000..fa47e976c --- /dev/null +++ b/dev/tests/verification/PauseCheckModule/Test/TestWithMultiplePauseActionsTest.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="TestWithMultiplePauseActionsTest"> + <annotations> + <severity value="CRITICAL"/> + <group value="functional"/> + <features value="Pause check"/> + <stories value="MQE-433"/> + </annotations> + <before> + <amOnPage url="/beforeUrl" stepKey="beforeAmOnPageKey"/> + <pause stepKey="pauseBeforeAmOnPageKey"/> + </before> + <fillField stepKey="step1" selector="#username" userInput="step1"/> + <fillField stepKey="step2" selector="#password" userInput="step2"/> + <pause stepKey="pauseAfterStep2"/> + <after> + <amOnPage url="/afterUrl" stepKey="afterAmOnPageKey"/> + <pause stepKey="pauseAfterAmOnPageKey"/> + </after> + </test> +</tests> diff --git a/dev/tests/verification/PauseCheckModule/Test/TestWithPauseActionTest.xml b/dev/tests/verification/PauseCheckModule/Test/TestWithPauseActionTest.xml new file mode 100644 index 000000000..70d0903b8 --- /dev/null +++ b/dev/tests/verification/PauseCheckModule/Test/TestWithPauseActionTest.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="TestWithPauseActionTest"> + <annotations> + <severity value="CRITICAL"/> + <group value="functional"/> + <features value="Pause check"/> + <stories value="MQE-433"/> + </annotations> + <amOnPage stepKey="step1" url="/step1"/> + <fillField stepKey="step2" selector="#username" userInput="step2"/> + <fillField stepKey="step3" selector="#password" userInput="step3"/> + <pause stepKey="pauseAfterStep3"/> + </test> +</tests> diff --git a/dev/tests/verification/ResilientGenerationModule/ActionGroup/BasicActionGroup/NotGenerateArgActionGroup.xml b/dev/tests/verification/ResilientGenerationModule/ActionGroup/BasicActionGroup/NotGenerateArgActionGroup.xml new file mode 100644 index 000000000..5d0246b08 --- /dev/null +++ b/dev/tests/verification/ResilientGenerationModule/ActionGroup/BasicActionGroup/NotGenerateArgActionGroup.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="NotGenerateArgActionGroup"> + <arguments> + <argument name="someArgument" defaultValue="ReplacementPerson"/> + </arguments> + <see selector="{{SampleSection.threeParamElement(someArgument.firstname, someArgument.lastname, 'test')}}" userInput="{{someArgument.lastname}}" stepKey="seeLastName" /> + </actionGroup> +</actionGroups> diff --git a/dev/tests/verification/ResilientGenerationModule/ActionGroup/BasicActionGroup/NotGenerateCreateDataActionGroup.xml b/dev/tests/verification/ResilientGenerationModule/ActionGroup/BasicActionGroup/NotGenerateCreateDataActionGroup.xml new file mode 100644 index 000000000..580c9081c --- /dev/null +++ b/dev/tests/verification/ResilientGenerationModule/ActionGroup/BasicActionGroup/NotGenerateCreateDataActionGroup.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="NotGenerateCreateDataActionGroup"> + <createData entity="NotExtendParentData" stepKey="createData"/> + </actionGroup> +</actionGroups> diff --git a/dev/tests/verification/ResilientGenerationModule/ActionGroup/BasicActionGroup/NotGenerateExtendChildActionGroup.xml b/dev/tests/verification/ResilientGenerationModule/ActionGroup/BasicActionGroup/NotGenerateExtendChildActionGroup.xml new file mode 100644 index 000000000..9566ad170 --- /dev/null +++ b/dev/tests/verification/ResilientGenerationModule/ActionGroup/BasicActionGroup/NotGenerateExtendChildActionGroup.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="NotGenerateExtendChildActionGroup" extends="NotGenerateExtendParentActionGroup"> + <arguments> + <argument name="otherCount" type="string"/> + </arguments> + <grabMultiple selector="notASelector" stepKey="grabProducts"/> + <comment userInput="New Input After" stepKey="afterGrabProducts" after="grabProducts"/> + <comment userInput="New Input Before" stepKey="beforeGrabProducts" before="grabProducts"/> + <assertCount stepKey="assertSecondCount"> + <expectedResult type="int">{{otherCount}}</expectedResult> + <actualResult type="variable">grabProducts</actualResult> + </assertCount> + </actionGroup> +</actionGroups> diff --git a/dev/tests/verification/ResilientGenerationModule/ActionGroup/BasicActionGroup/NotGenerateExtendChildBadReferenceActionGroup.xml b/dev/tests/verification/ResilientGenerationModule/ActionGroup/BasicActionGroup/NotGenerateExtendChildBadReferenceActionGroup.xml new file mode 100644 index 000000000..4896c239f --- /dev/null +++ b/dev/tests/verification/ResilientGenerationModule/ActionGroup/BasicActionGroup/NotGenerateExtendChildBadReferenceActionGroup.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="NotGenerateExtendChildBadReferenceActionGroup" extends="extendBasicActionGroup"> + <fillField stepKey="fill" selector="$name" userInput="$$N.N$$"/> + </actionGroup> +</actionGroups> diff --git a/dev/tests/verification/ResilientGenerationModule/ActionGroup/BasicActionGroup/NotGenerateExtendParentActionGroup.xml b/dev/tests/verification/ResilientGenerationModule/ActionGroup/BasicActionGroup/NotGenerateExtendParentActionGroup.xml new file mode 100644 index 000000000..5e3cfaa54 --- /dev/null +++ b/dev/tests/verification/ResilientGenerationModule/ActionGroup/BasicActionGroup/NotGenerateExtendParentActionGroup.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="NotGenerateExtendParentActionGroup"> + <arguments> + <argument name="count" type="string"/> + </arguments> + <grabMultiple selector="selector" stepKey="grabProducts"/> + <assertCount stepKey="assertCount"> + <expectedResult type="int">{{count}}</expectedResult> + <actualResult type="variable">grabProducts</actualResult> + </assertCount> + <click stepKey="click" userInput="{{N.N}}" selector="#name"/> + </actionGroup> +</actionGroups> diff --git a/dev/tests/verification/ResilientGenerationModule/ActionGroup/BasicActionGroup/NotGenerateParamsActionGroup.xml b/dev/tests/verification/ResilientGenerationModule/ActionGroup/BasicActionGroup/NotGenerateParamsActionGroup.xml new file mode 100644 index 000000000..29ebba5f7 --- /dev/null +++ b/dev/tests/verification/ResilientGenerationModule/ActionGroup/BasicActionGroup/NotGenerateParamsActionGroup.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="NotGenerateParamsActionGroup"> + <arguments> + <argument name="param" type="entity"/> + <argument name="param2" type="entity" defaultValue="simpleParamData"/> + </arguments> + <click selector="{{SampleSection.twoParamElement({$testVariable2}, param.firstname)}}" stepKey="click1"/> + <click selector="{{SampleSection.threeParamElement(param.lastname, param2.uniqueNamePre, {$testVariable})}}" stepKey="click2"/> + <seeElement selector="{{SampleSection.fourParamElement(param.middlename, {$testVariable}, {$testVariable2}, $$createSimpleData.name$$)}}" stepKey="see1"/> + </actionGroup> +</actionGroups> diff --git a/dev/tests/verification/ResilientGenerationModule/ActionGroup/BasicActionGroup/NotGenerateSectionActionGroup.xml b/dev/tests/verification/ResilientGenerationModule/ActionGroup/BasicActionGroup/NotGenerateSectionActionGroup.xml new file mode 100644 index 000000000..81a4d4c8d --- /dev/null +++ b/dev/tests/verification/ResilientGenerationModule/ActionGroup/BasicActionGroup/NotGenerateSectionActionGroup.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="NotGenerateSectionActionGroup"> + <arguments> + <argument name="section" defaultValue="SampleSection"/> + </arguments> + <executeJS function="{{section.oneParamElement('full-width')}}" stepKey="keyone"/> + </actionGroup> +</actionGroups> diff --git a/dev/tests/verification/ResilientGenerationModule/ActionGroup/FunctionalActionGroup/NotGenerateMassMergeAfterActionGroup.xml b/dev/tests/verification/ResilientGenerationModule/ActionGroup/FunctionalActionGroup/NotGenerateMassMergeAfterActionGroup.xml new file mode 100644 index 000000000..5fc49e0b3 --- /dev/null +++ b/dev/tests/verification/ResilientGenerationModule/ActionGroup/FunctionalActionGroup/NotGenerateMassMergeAfterActionGroup.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="NotGenerateMassMergeAfter"> + <fillField selector="#foo" userInput="foo" stepKey="fillField1"/> + <fillField selector="#bar" userInput="bar" stepKey="fillField2"/> + <fillField selector="#baz" userInput="baz" stepKey="fillField3"/> + </actionGroup> +</actionGroups> diff --git a/dev/tests/verification/ResilientGenerationModule/ActionGroup/FunctionalActionGroup/NotGenerateMassMergeBeforeActionGroup.xml b/dev/tests/verification/ResilientGenerationModule/ActionGroup/FunctionalActionGroup/NotGenerateMassMergeBeforeActionGroup.xml new file mode 100644 index 000000000..23352d61e --- /dev/null +++ b/dev/tests/verification/ResilientGenerationModule/ActionGroup/FunctionalActionGroup/NotGenerateMassMergeBeforeActionGroup.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="NotGenerateMassMergeBefore"> + <fillField selector="#foo" userInput="foo" stepKey="fillField1"/> + <fillField selector="#bar" userInput="bar" stepKey="fillField2"/> + <fillField selector="#baz" userInput="baz" stepKey="fillField3"/> + </actionGroup> +</actionGroups> diff --git a/dev/tests/verification/ResilientGenerationModule/ActionGroup/FunctionalActionGroup/NotGenerateMergeActionGroup.xml b/dev/tests/verification/ResilientGenerationModule/ActionGroup/FunctionalActionGroup/NotGenerateMergeActionGroup.xml new file mode 100644 index 000000000..6796df94e --- /dev/null +++ b/dev/tests/verification/ResilientGenerationModule/ActionGroup/FunctionalActionGroup/NotGenerateMergeActionGroup.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="NotGenerateMergeActionGroup"> + <arguments> + <argument name="myArg"/> + </arguments> + <fillField stepKey="deleteMe" userInput="Please delete me" selector="#delete" /> + <see selector="{{SampleSectionN.oneParamElement(myArg.firstname)}}" stepKey="see1"/> + <amOnPage url="{{SamplePage.url(myArg.firstname,myArg.lastname)}}" stepKey="amOnPage1"/> + </actionGroup> +</actionGroups> diff --git a/dev/tests/verification/ResilientGenerationModule/ActionGroup/MergeFunctionalActionGroup/NotGenerateMassMergeAfterActionGroup.xml b/dev/tests/verification/ResilientGenerationModule/ActionGroup/MergeFunctionalActionGroup/NotGenerateMassMergeAfterActionGroup.xml new file mode 100644 index 000000000..18d4332ac --- /dev/null +++ b/dev/tests/verification/ResilientGenerationModule/ActionGroup/MergeFunctionalActionGroup/NotGenerateMassMergeAfterActionGroup.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="NotGenerateMassMergeAfter" insertAfter="NfillField2"> + <click stepKey="mergeAfterBar" selector="#foo2"/> + <click stepKey="mergeAfterFoo2" selector="#bar2"/> + <click stepKey="mergeAfterBar2" selector="#baz2"/> + </actionGroup> +</actionGroups> diff --git a/dev/tests/verification/ResilientGenerationModule/ActionGroup/MergeFunctionalActionGroup/NotGenerateMassMergeBeforeActionGroup.xml b/dev/tests/verification/ResilientGenerationModule/ActionGroup/MergeFunctionalActionGroup/NotGenerateMassMergeBeforeActionGroup.xml new file mode 100644 index 000000000..07166d1fb --- /dev/null +++ b/dev/tests/verification/ResilientGenerationModule/ActionGroup/MergeFunctionalActionGroup/NotGenerateMassMergeBeforeActionGroup.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="NotGenerateMassMergeBefore" insertBefore="fillField2N"> + <click stepKey="mergeBeforeBar" selector="#foo2"/> + <click stepKey="mergeAfterFoo2" selector="#bar2"/> + <click stepKey="mergeAfterBar2" selector="#baz2"/> + </actionGroup> +</actionGroups> diff --git a/dev/tests/verification/ResilientGenerationModule/ActionGroup/MergeFunctionalActionGroup/NotGenerateMergeActionGroup.xml b/dev/tests/verification/ResilientGenerationModule/ActionGroup/MergeFunctionalActionGroup/NotGenerateMergeActionGroup.xml new file mode 100644 index 000000000..964a40a74 --- /dev/null +++ b/dev/tests/verification/ResilientGenerationModule/ActionGroup/MergeFunctionalActionGroup/NotGenerateMergeActionGroup.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="NotGenerateMergeActionGroup"> + <see stepKey="myMergedSeeElement" selector=".merge .{{myArg.firstname}}" before="see1"/> + <click stepKey="myMergedClick" selector=".merge .{{myArg.lastNname}}" after="amOnPage1"/> + <remove keyForRemoval="deleteMe"/> + </actionGroup> +</actionGroups> diff --git a/dev/tests/verification/ResilientGenerationModule/Data/ExtendedData.xml b/dev/tests/verification/ResilientGenerationModule/Data/ExtendedData.xml new file mode 100644 index 000000000..59243531e --- /dev/null +++ b/dev/tests/verification/ResilientGenerationModule/Data/ExtendedData.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="NotExtendParentData" extends="NparentData"> + <data key="name">otherName</data> + <data key="nameExtend">extendName</data> + <data key="uniqueNamePost">item</data> + <data key="anotherUniqueNamePre" unique="suffix">postnameExtend</data> + <requiredEntity type="item">value</requiredEntity> + </entity> +</entities> diff --git a/dev/tests/verification/ResilientGenerationModule/Suite/NotGenerateEmptySuite.xml b/dev/tests/verification/ResilientGenerationModule/Suite/NotGenerateEmptySuite.xml new file mode 100644 index 000000000..869b4acf6 --- /dev/null +++ b/dev/tests/verification/ResilientGenerationModule/Suite/NotGenerateEmptySuite.xml @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<suites xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Suite/etc/suiteSchema.xsd"> + <suite name="NotGenerateEmptySuite"> + </suite> +</suites> diff --git a/dev/tests/verification/ResilientGenerationModule/Suite/NotGenerateHookAfterSuite.xml b/dev/tests/verification/ResilientGenerationModule/Suite/NotGenerateHookAfterSuite.xml new file mode 100644 index 000000000..96054071e --- /dev/null +++ b/dev/tests/verification/ResilientGenerationModule/Suite/NotGenerateHookAfterSuite.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<suites xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Suite/etc/suiteSchema.xsd"> + <suite name="NotGenerateHookAfterSuite"> + <include> + <test name="IncludeTest"/> + </include> + <before> + <amOnPage url="some.url" stepKey="before"/> + </before> + <after> + <actionGroup ref="actionGroupWithTwoArgumentsN" stepKey="AC"> + <argument name="somePerson" value="simpleData"/> + <argument name="anotherPerson" value="uniqueData"/> + </actionGroup> + </after> + </suite> +</suites> diff --git a/dev/tests/verification/ResilientGenerationModule/Suite/NotGenerateHookBeforeSuite.xml b/dev/tests/verification/ResilientGenerationModule/Suite/NotGenerateHookBeforeSuite.xml new file mode 100644 index 000000000..36e046114 --- /dev/null +++ b/dev/tests/verification/ResilientGenerationModule/Suite/NotGenerateHookBeforeSuite.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<suites xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Suite/etc/suiteSchema.xsd"> + <suite name="NotGenerateHookBeforeSuite"> + <include> + <test name="IncludeTest"/> + </include> + <before> + <actionGroup ref="actionGroupWithTwoArgumentsN" stepKey="AC"> + <argument name="somePerson" value="simpleData"/> + <argument name="anotherPerson" value="uniqueData"/> + </actionGroup> + </before> + <after> + <amOnPage url="some.url" stepKey="after"/> + </after> + </suite> +</suites> diff --git a/dev/tests/verification/ResilientGenerationModule/Suite/PartialGenerateForIncludeSuite.xml b/dev/tests/verification/ResilientGenerationModule/Suite/PartialGenerateForIncludeSuite.xml new file mode 100644 index 000000000..7e9f3d76c --- /dev/null +++ b/dev/tests/verification/ResilientGenerationModule/Suite/PartialGenerateForIncludeSuite.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<suites xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Suite/etc/suiteSchema.xsd"> + <suite name="PartialGenerateForIncludeSuite"> + <include> + <test name="IncludeTest"/> + <group name = "N" /> + <test name="NotGenerateDataReferenceTest"/> + </include> + <exclude> + <test name="ExcludeTest2"/> + </exclude> + </suite> +</suites> diff --git a/dev/tests/verification/ResilientGenerationModule/Suite/PartialGenerateNoExcludeSuite.xml b/dev/tests/verification/ResilientGenerationModule/Suite/PartialGenerateNoExcludeSuite.xml new file mode 100644 index 000000000..cde48cbbb --- /dev/null +++ b/dev/tests/verification/ResilientGenerationModule/Suite/PartialGenerateNoExcludeSuite.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<suites xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Suite/etc/suiteSchema.xsd"> + <suite name="PartialGenerateNoExcludeSuite"> + <include> + <test name="IncludeTest"/> + </include> + <exclude> + <test name="NotGenerateDataReferenceTest"/> + </exclude> + </suite> +</suites> diff --git a/dev/tests/verification/ResilientGenerationModule/Test/ActionGroupTest/NotGenerateActionGroupHasBeforeOrAfterTest.xml b/dev/tests/verification/ResilientGenerationModule/Test/ActionGroupTest/NotGenerateActionGroupHasBeforeOrAfterTest.xml new file mode 100644 index 000000000..1cb3dd4a2 --- /dev/null +++ b/dev/tests/verification/ResilientGenerationModule/Test/ActionGroupTest/NotGenerateActionGroupHasBeforeOrAfterTest.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="NotGenerateActionGroupHasBeforeOrAfterTest"> + <actionGroup ref="FunctionalActionGroup" stepKey="ag" before="N"/> + </test> +</tests> diff --git a/dev/tests/verification/ResilientGenerationModule/Test/ActionGroupTest/NotGenerateArgTest.xml b/dev/tests/verification/ResilientGenerationModule/Test/ActionGroupTest/NotGenerateArgTest.xml new file mode 100644 index 000000000..8b98a9a19 --- /dev/null +++ b/dev/tests/verification/ResilientGenerationModule/Test/ActionGroupTest/NotGenerateArgTest.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="NotGenerateArgTest"> + <actionGroup ref="NotGenerateArgActionGroup" stepKey="actionGroup"> + <argument name="someArgument" value="N"/> + </actionGroup> + </test> +</tests> diff --git a/dev/tests/verification/ResilientGenerationModule/Test/ActionGroupTest/NotGenerateExtendChildTest.xml b/dev/tests/verification/ResilientGenerationModule/Test/ActionGroupTest/NotGenerateExtendChildTest.xml new file mode 100644 index 000000000..9f9d022f6 --- /dev/null +++ b/dev/tests/verification/ResilientGenerationModule/Test/ActionGroupTest/NotGenerateExtendChildTest.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="NotGenerateExtendChildTest"> + <actionGroup ref="NotGenerateExtendChildActionGroup" stepKey="actionGroup"> + <argument name="otherCount" value="2"/> + </actionGroup> + </test> +</tests> diff --git a/dev/tests/verification/ResilientGenerationModule/Test/ActionGroupTest/NotGenerateMassMergeAfterTest.xml b/dev/tests/verification/ResilientGenerationModule/Test/ActionGroupTest/NotGenerateMassMergeAfterTest.xml new file mode 100644 index 000000000..ed3f31a79 --- /dev/null +++ b/dev/tests/verification/ResilientGenerationModule/Test/ActionGroupTest/NotGenerateMassMergeAfterTest.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="NotGenerateMassMergeAferTest"> + <actionGroup ref="NotGenerateMassMergeAfter" stepKey="actionGroup"/> + </test> +</tests> diff --git a/dev/tests/verification/ResilientGenerationModule/Test/ActionGroupTest/NotGenerateMassMergeBeforeTest.xml b/dev/tests/verification/ResilientGenerationModule/Test/ActionGroupTest/NotGenerateMassMergeBeforeTest.xml new file mode 100644 index 000000000..1046138b4 --- /dev/null +++ b/dev/tests/verification/ResilientGenerationModule/Test/ActionGroupTest/NotGenerateMassMergeBeforeTest.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="NotGenerateMassMergeBeforeTest"> + <actionGroup ref="NotGenerateMassMergeBefore" stepKey="actionGroup"/> + </test> +</tests> diff --git a/dev/tests/verification/ResilientGenerationModule/Test/ActionGroupTest/NotGenerateMergeTest.xml b/dev/tests/verification/ResilientGenerationModule/Test/ActionGroupTest/NotGenerateMergeTest.xml new file mode 100644 index 000000000..7fa4684e0 --- /dev/null +++ b/dev/tests/verification/ResilientGenerationModule/Test/ActionGroupTest/NotGenerateMergeTest.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="NotGenerateMergeTest"> + <actionGroup ref="NotGenerateMergeActionGroup" stepKey="actionGroup"> + <argument name="myArg" value="N"/> + </actionGroup> + </test> +</tests> diff --git a/dev/tests/verification/ResilientGenerationModule/Test/ActionGroupTest/NotGenerateNonExistingActionGroupTest.xml b/dev/tests/verification/ResilientGenerationModule/Test/ActionGroupTest/NotGenerateNonExistingActionGroupTest.xml new file mode 100644 index 000000000..61b7544e8 --- /dev/null +++ b/dev/tests/verification/ResilientGenerationModule/Test/ActionGroupTest/NotGenerateNonExistingActionGroupTest.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="NotGenerateNonExistingActionGroupTest"> + <actionGroup ref="NNNFunctionalActionGroup" stepKey="ag"/> + </test> +</tests> diff --git a/dev/tests/verification/ResilientGenerationModule/Test/ActionGroupTest/NotGenerateParamsTest.xml b/dev/tests/verification/ResilientGenerationModule/Test/ActionGroupTest/NotGenerateParamsTest.xml new file mode 100644 index 000000000..3704ba695 --- /dev/null +++ b/dev/tests/verification/ResilientGenerationModule/Test/ActionGroupTest/NotGenerateParamsTest.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="NotGenerateParamsTest"> + <actionGroup ref="NotGenerateParamsActionGroup" stepKey="actionGroup"> + <argument name="param" value="n"/> + <argument name="param2" value="o"/> + </actionGroup> + </test> +</tests> diff --git a/dev/tests/verification/ResilientGenerationModule/Test/ActionGroupTest/NotGenerateRequiredDataTest.xml b/dev/tests/verification/ResilientGenerationModule/Test/ActionGroupTest/NotGenerateRequiredDataTest.xml new file mode 100644 index 000000000..93da0c5ce --- /dev/null +++ b/dev/tests/verification/ResilientGenerationModule/Test/ActionGroupTest/NotGenerateRequiredDataTest.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="NotGenerateRequiredDataTest"> + <actionGroup ref="NotGenerateRequiredDataActionGroup" stepKey="actionGroup"/> + </test> +</tests> diff --git a/dev/tests/verification/ResilientGenerationModule/Test/ActionGroupTest/NotGenerateSectionTest.xml b/dev/tests/verification/ResilientGenerationModule/Test/ActionGroupTest/NotGenerateSectionTest.xml new file mode 100644 index 000000000..3d7499b8b --- /dev/null +++ b/dev/tests/verification/ResilientGenerationModule/Test/ActionGroupTest/NotGenerateSectionTest.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="NotGenerateSectionTest"> + <actionGroup ref="NotGenerateSectionActionGroup" stepKey="actionGroup"> + <argument name="section" value="N"/> + </actionGroup> + </test> +</tests> diff --git a/dev/tests/verification/ResilientGenerationModule/Test/ExtendTest/NotGenerateChildExtendedBadReferenceTest.xml b/dev/tests/verification/ResilientGenerationModule/Test/ExtendTest/NotGenerateChildExtendedBadReferenceTest.xml new file mode 100644 index 000000000..45e1177f1 --- /dev/null +++ b/dev/tests/verification/ResilientGenerationModule/Test/ExtendTest/NotGenerateChildExtendedBadReferenceTest.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="NotGenerateChildExtendedBadReferenceTest" extends="ParentExtendedTest"> + <annotations> + <severity value="MINOR"/> + <title value="NotGenerateChildExtendedBadReferenceTest"/> + <features value="Child"/> + <stories value="Child"/> + </annotations> + <before> + <amOnPage url="/firstUrl" stepKey="firstBeforeAmOnPageKey" before="beforeAmOnPageKey"/> + <amOnPage url="/lastUrl" stepKey="lastBefore" after="beforeAmOnPageKey"/> + </before> + <comment stepKey="lastStepKey" userInput="Last Comment"/> + <comment stepKey="beforeBasicCommentWithNoData" userInput="{{N.N}}" before="basicCommentWithNoData"/> + <comment stepKey="afterBasicCommentWithNoData" userInput="After Comment" after="basicCommentWithNoData"/> + </test> +</tests> diff --git a/dev/tests/verification/ResilientGenerationModule/Test/ExtendTest/NotGenerateChildExtendedMergingTest.xml b/dev/tests/verification/ResilientGenerationModule/Test/ExtendTest/NotGenerateChildExtendedMergingTest.xml new file mode 100644 index 000000000..61f15a31a --- /dev/null +++ b/dev/tests/verification/ResilientGenerationModule/Test/ExtendTest/NotGenerateChildExtendedMergingTest.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="NotGenerateChildExtendedMergingTest" extends="ParentExtendedTest"> + <annotations> + <severity value="MINOR"/> + <title value="NotGenerateChildExtendedTestMerging"/> + <features value="Child"/> + <stories value="Child"/> + </annotations> + <before> + <amOnPage url="/firstUrl" stepKey="firstBeforeAmOnPageKey" before="beforeAmOnPageKey"/> + <amOnPage url="/lastUrl" stepKey="lastBefore" after="beforeAmOnPageKeyN"/> + </before> + <comment stepKey="lastStepKey" userInput="Last Comment"/> + <comment stepKey="beforeBasicCommentWithNoData" userInput="Before Comment" before="basicCommentWithNoData"/> + <comment stepKey="afterBasicCommentWithNoData" userInput="After Comment" after="basicCommentWithNoData"/> + </test> +</tests> diff --git a/dev/tests/verification/ResilientGenerationModule/Test/ExtendTest/NotGenerateExtendNonExistingParentTest.xml b/dev/tests/verification/ResilientGenerationModule/Test/ExtendTest/NotGenerateExtendNonExistingParentTest.xml new file mode 100644 index 000000000..9f42cb55c --- /dev/null +++ b/dev/tests/verification/ResilientGenerationModule/Test/ExtendTest/NotGenerateExtendNonExistingParentTest.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="NotGenerateExtendNonExistingParentTest" extends="N"> + <comment stepKey="comment" userInput="comment"/> + </test> +</tests> diff --git a/dev/tests/verification/ResilientGenerationModule/Test/ExtendTest/NotGenerateExtendSelfTest.xml b/dev/tests/verification/ResilientGenerationModule/Test/ExtendTest/NotGenerateExtendSelfTest.xml new file mode 100644 index 000000000..46ae93933 --- /dev/null +++ b/dev/tests/verification/ResilientGenerationModule/Test/ExtendTest/NotGenerateExtendSelfTest.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="NotGenerateExtendSelfTest" extends="NotGenerateExtendSelfTest"> + <comment stepKey="comment" userInput="comment"/> + </test> +</tests> diff --git a/dev/tests/verification/ResilientGenerationModule/Test/ExtendTest/NotGenerateExtendedChildTest.xml b/dev/tests/verification/ResilientGenerationModule/Test/ExtendTest/NotGenerateExtendedChildTest.xml new file mode 100644 index 000000000..75154b83d --- /dev/null +++ b/dev/tests/verification/ResilientGenerationModule/Test/ExtendTest/NotGenerateExtendedChildTest.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="NotGenerateExtendedChildTest" extends="NotGenerateParentTest"> + <annotations> + <severity value="MINOR"/> + <title value="NotGenerateExtendedChildTest"/> + <features value="NotGenerateExtendedChildTest"/> + <stories value="NotGenerateExtendedChildTest"/> + </annotations> + <comment stepKey="basicCommentWithNoData" userInput="Different Input"/> + </test> +</tests> diff --git a/dev/tests/verification/ResilientGenerationModule/Test/ExtendTest/NotGenerateParentTest.xml b/dev/tests/verification/ResilientGenerationModule/Test/ExtendTest/NotGenerateParentTest.xml new file mode 100644 index 000000000..0735ad13a --- /dev/null +++ b/dev/tests/verification/ResilientGenerationModule/Test/ExtendTest/NotGenerateParentTest.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="NotGenerateParentTest"> + <annotations> + <severity value="AVERAGE"/> + <title value="NotGenerateParentTest"/> + <features value="NotGenerateParentTest"/> + <stories value="NotGenerateParentTest"/> + </annotations> + <before> + <amOnPage url="{{NOTaPAGE.url}}" stepKey="beforeAmOnPageKey"/> + </before> + <after> + <amOnPage url="/afterUrl" stepKey="afterAmOnPageKey"/> + </after> + <comment stepKey="basicCommentWithNoData" userInput="Parent Comment"/> + <amOnPage url="/url/in/parent" stepKey="amOnPageInParent"/> + </test> +</tests> diff --git a/dev/tests/verification/ResilientGenerationModule/Test/MergeTest/NotGenerateBasicMergeTest.xml b/dev/tests/verification/ResilientGenerationModule/Test/MergeTest/NotGenerateBasicMergeTest.xml new file mode 100644 index 000000000..a2f68271c --- /dev/null +++ b/dev/tests/verification/ResilientGenerationModule/Test/MergeTest/NotGenerateBasicMergeTest.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="NotGenerateBasicMergeTest"> + <before> + <see stepKey="before2" selector="#before2" after="before1N"/> + </before> + <after> + <amOnPage url="/afterUrl1" stepKey="after1"/> + </after> + <click stepKey="step7Merge" selector="{{SampleSection.oneParamElement(DefaultPerson.firstname)}} .step7Merge" after="step6Merge"/> + <click stepKey="step2" selector="#step2" after="step1NNN"/> + <click stepKey="step4" selector="#step4" before="step5"/> + <remove keyForRemoval="step6"/> + <click stepKey="step6Merge" selector="#step6Merged" after="step5"/> + <click stepKey="step10" selector="#step10MergedInResult"/> + <actionGroup ref="FunctionalActionGroupWithData" stepKey="step8Merge" after="step7MergeNNN"/> + </test> +</tests> diff --git a/dev/tests/verification/ResilientGenerationModule/Test/MergeTest/NotGenerateMergeMassViaInsertAfter.xml b/dev/tests/verification/ResilientGenerationModule/Test/MergeTest/NotGenerateMergeMassViaInsertAfter.xml new file mode 100644 index 000000000..1bde12ac5 --- /dev/null +++ b/dev/tests/verification/ResilientGenerationModule/Test/MergeTest/NotGenerateMergeMassViaInsertAfter.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="NotGenerateMergeMassViaInsertAfter" insertAfter="fillField2N"> + <click stepKey="clickOne" selector="#mergeOne"/> + <click stepKey="clickTwo" selector="#mergeTwo"/> + <click stepKey="clickThree" selector="#mergeThree"/> + </test> +</tests> diff --git a/dev/tests/verification/ResilientGenerationModule/Test/MergeTest/NotGenerateMergeMassViaInsertBefore.xml b/dev/tests/verification/ResilientGenerationModule/Test/MergeTest/NotGenerateMergeMassViaInsertBefore.xml new file mode 100644 index 000000000..fba36fa01 --- /dev/null +++ b/dev/tests/verification/ResilientGenerationModule/Test/MergeTest/NotGenerateMergeMassViaInsertBefore.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="NotGenerateMergeMassViaInsertBefore" insertBefore="fillField2N"> + <click stepKey="clickOne" selector="#mergeOne"/> + <click stepKey="clickTwo" selector="#mergeTwo"/> + <click stepKey="clickThree" selector="#mergeThree"/> + </test> +</tests> diff --git a/dev/tests/verification/ResilientGenerationModule/Test/NotGenerateActionHasBothBeforeAndAfterTest.xml b/dev/tests/verification/ResilientGenerationModule/Test/NotGenerateActionHasBothBeforeAndAfterTest.xml new file mode 100644 index 000000000..2ba798498 --- /dev/null +++ b/dev/tests/verification/ResilientGenerationModule/Test/NotGenerateActionHasBothBeforeAndAfterTest.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="NotGenerateActionHasBothBeforeAndAfterTest"> + <amOnPage url="{{AdminLoginPage.url}}" stepKey="navigateToAdmin" before="B" after="A"/> + </test> +</tests> diff --git a/dev/tests/verification/ResilientGenerationModule/Test/NotGenerateActionHasInvalidBeforeOrAfterTest.xml b/dev/tests/verification/ResilientGenerationModule/Test/NotGenerateActionHasInvalidBeforeOrAfterTest.xml new file mode 100644 index 000000000..eddb2da94 --- /dev/null +++ b/dev/tests/verification/ResilientGenerationModule/Test/NotGenerateActionHasInvalidBeforeOrAfterTest.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="NotGenerateActionHasInvalidBeforeOrAfterTest"> + <amOnPage url="{{AdminLoginPage.url}}" stepKey="navigateToAdmin" after="N"/> + <click stepKey="a" selector="{{$a.b$}}" userInput="a"/> + </test> +</tests> diff --git a/dev/tests/verification/ResilientGenerationModule/Test/NotGenerateAssertTest.xml b/dev/tests/verification/ResilientGenerationModule/Test/NotGenerateAssertTest.xml new file mode 100644 index 000000000..77368f232 --- /dev/null +++ b/dev/tests/verification/ResilientGenerationModule/Test/NotGenerateAssertTest.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="NotGenerateAssertTest"> + <before> + <createData entity="ReplacementPerson" stepKey="createData1"/> + </before> + <createData entity="UniquePerson" stepKey="createData2"/> + <grabTextFrom selector=".copyright>span" stepKey="grabTextFrom1"/> + + <!-- assert entity resolution --> + <assertEquals stepKey="assertEqualsEntity" message="pass"> + <expectedResult type="string">{{simpleData.firstnameN}}</expectedResult> + <actualResult type="string">{{simpleData.lastname}}</actualResult> + </assertEquals> + </test> +</tests> diff --git a/dev/tests/verification/ResilientGenerationModule/Test/NotGenerateBasicMergeTest.xml b/dev/tests/verification/ResilientGenerationModule/Test/NotGenerateBasicMergeTest.xml new file mode 100644 index 000000000..7344b4f42 --- /dev/null +++ b/dev/tests/verification/ResilientGenerationModule/Test/NotGenerateBasicMergeTest.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="NotGenerateBasicMergeTest"> + <annotations> + <severity value="CRITICAL"/> + <title value="NotGenerateBasicMergeTest"/> + <features value="Merge Functional Cest"/> + <stories value="MQE-433"/> + </annotations> + <before> + <amOnPage url="/beforeUrl" stepKey="before1"/> + </before> + <after> + <amOnPage url="/afterUrl" stepKey="after1"/> + </after> + <amOnPage stepKey="step1" url="/step1"/> + <fillField stepKey="step3" selector="#username" userInput="step3"/> + <fillField stepKey="step5" selector="#password" userInput="step5"/> + <click stepKey="step6" selector=".step6"/> + <click stepKey="step10" selector="#step10ShouldNotInResult"/> + </test> +</tests> diff --git a/dev/tests/verification/ResilientGenerationModule/Test/NotGenerateBasicXYOffsetTest.xml b/dev/tests/verification/ResilientGenerationModule/Test/NotGenerateBasicXYOffsetTest.xml new file mode 100644 index 000000000..5fee83988 --- /dev/null +++ b/dev/tests/verification/ResilientGenerationModule/Test/NotGenerateBasicXYOffsetTest.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="NotGenerateBasicXYOffsetTest"> + <clickWithLeftButton selector="{{SampleSection.simpleElement}}{{SampleSection.simpleElementOneParam(offset.x)}}" x="{{offsetN.x}}" y="{{offset.y}}" stepKey="clickWithLeftButtonKeyXY1" /> + <clickWithRightButton selector="{{SampleSection.simpleElementOneParam('4123')}}{{SampleSection.simpleElement}}" x="{{offset.x}}" y="{{offset.yN}}" stepKey="clickWithRightButtonKeyXY1" /> + </test> +</tests> diff --git a/dev/tests/verification/ResilientGenerationModule/Test/NotGenerateDataReferenceTest.xml b/dev/tests/verification/ResilientGenerationModule/Test/NotGenerateDataReferenceTest.xml new file mode 100644 index 000000000..be05ca994 --- /dev/null +++ b/dev/tests/verification/ResilientGenerationModule/Test/NotGenerateDataReferenceTest.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="NotGenerateDataReferenceTest"> + <fillField stepKey="fill" userInput="{{NotExtendParentData.name}}" selector="#name"/> + </test> +</tests> diff --git a/dev/tests/verification/ResilientGenerationModule/Test/NotGenerateDataReplacementTest.xml b/dev/tests/verification/ResilientGenerationModule/Test/NotGenerateDataReplacementTest.xml new file mode 100644 index 000000000..efab25f7e --- /dev/null +++ b/dev/tests/verification/ResilientGenerationModule/Test/NotGenerateDataReplacementTest.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="NotGenerateDataReplacementTest"> + <fillField stepKey="inputReplace" selector="#selector" userInput="StringBefore {{simpleDataN.firstname}} StringAfter"/> + <seeCurrentUrlMatches stepKey="seeInRegex" regex="~\/{{simpleData.Nfirstname}}~i"/> + <fillField stepKey="selectorReplace" selector="#{{simpleData.Nfirstname}}" userInput="input"/> + <dragAndDrop stepKey="selector12Replace" selector1="#{{simpleData.Nfirstname}}" selector2="{{NsimpleData.lastname}}"/> + <conditionalClick stepKey="dependentSelectorReplace" dependentSelector="#{{simpleData.Nfirstname}}" selector="{{simpleDataN.lastname}}" visible="true"/> + <amOnUrl stepKey="urlReplace" url="{{simpleData.firstnameN}}.html"/> + <searchAndMultiSelectOption stepKey="parameterArrayReplacement" selector="#selector" parameterArray="[{{simpleData.firstnameN}}, {{NsimpleData.lastname}}]"/> + + <seeInPageSource html="StringBefore {{simpleData.Nfirstname}} StringAfter" stepKey="htmlReplace1"/> + <seeInPageSource html="#{{simpleDataN.firstname}}" stepKey="htmlReplace2"/> + </test> +</tests> \ No newline at end of file diff --git a/dev/tests/verification/ResilientGenerationModule/Test/NotGenerateMergeMassViaInsertAfter.xml b/dev/tests/verification/ResilientGenerationModule/Test/NotGenerateMergeMassViaInsertAfter.xml new file mode 100644 index 000000000..1f2bae414 --- /dev/null +++ b/dev/tests/verification/ResilientGenerationModule/Test/NotGenerateMergeMassViaInsertAfter.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="NotGenerateMergeMassViaInsertAfter"> + <fillField selector="#foo" userInput="foo" stepKey="fillField1"/> + <fillField selector="#bar" userInput="bar" stepKey="fillField2"/> + <fillField selector="#baz" userInput="baz" stepKey="fillField3"/> + </test> +</tests> diff --git a/dev/tests/verification/ResilientGenerationModule/Test/NotGenerateMergeMassViaInsertBefore.xml b/dev/tests/verification/ResilientGenerationModule/Test/NotGenerateMergeMassViaInsertBefore.xml new file mode 100644 index 000000000..ceb7f2e81 --- /dev/null +++ b/dev/tests/verification/ResilientGenerationModule/Test/NotGenerateMergeMassViaInsertBefore.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="NotGenerateMergeMassViaInsertBefore"> + <fillField selector="#foo" userInput="foo" stepKey="fillField1"/> + <fillField selector="#bar" userInput="bar" stepKey="fillField2"/> + <fillField selector="#baz" userInput="baz" stepKey="fillField3"/> + </test> +</tests> diff --git a/dev/tests/verification/ResilientGenerationModule/Test/NotGeneratePageReplacementTest.xml b/dev/tests/verification/ResilientGenerationModule/Test/NotGeneratePageReplacementTest.xml new file mode 100644 index 000000000..1a4a61336 --- /dev/null +++ b/dev/tests/verification/ResilientGenerationModule/Test/NotGeneratePageReplacementTest.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="NotGeneratePageReplacementTest"> + <createData entity="simpleData" stepKey="datakey"/> + <amOnPage stepKey="noParamPage" url="{{NoParamPageN.url}}"/> + <amOnPage stepKey="noParamPageN" url="{{NoParamPage.urlN}}"/> + <amOnPage stepKey="oneParamPageString" url="{{OneParamPage.url('StringLiteral', 'N')}}"/> + <amOnPage stepKey="oneParamPageData" url="{{OneParamPage.url(simpleDataN.firstname)}}"/> + <amOnPage stepKey="oneParamPagePersist" url="{{OneParamPage.url($datakey.firstnameN$)}}"/> + <amOnPage stepKey="twoParamPageString" url="{{TwoParamPage.url('StringLiteral1')}}"/> + <amOnPage stepKey="twoParamPageStringData" url="{{TwoParamPage.url(simpleData.firstnameN, 'StringLiteral2')}}"/> + <amOnPage stepKey="twoParamPageDataPersist" url="{{TwoParamPage.url(simpleData.firstname, $datakey.firstnameN$)}}"/> + <amOnPage stepKey="twoParamPagePersistString" url="{{TwoParamPage.url($datakey.firstnameN$, 'StringLiteral2')}}"/> + </test> +</tests> \ No newline at end of file diff --git a/dev/tests/verification/ResilientGenerationModule/Test/NotGenerateSectionReplacementTest.xml b/dev/tests/verification/ResilientGenerationModule/Test/NotGenerateSectionReplacementTest.xml new file mode 100644 index 000000000..ea527d49f --- /dev/null +++ b/dev/tests/verification/ResilientGenerationModule/Test/NotGenerateSectionReplacementTest.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="NotGenerateSectionReplacementTest"> + <click stepKey="selectorReplace" selector="{{SampleSectionN.simpleElement}}"/> + <click stepKey="selectorReplaceTimeout" selector="{{SampleSection.timeoutElementN}}"/> + + <click stepKey="selectorReplaceOneParam" selector="{{SampleSection.oneParamElement('stringLiteral', 'N')}}"/> + <click stepKey="selectorReplaceTwoParam" selector="{{SampleSection.twoParamElement('stringLiteral1')}}"/> + + <click stepKey="selectorReplaceThreeParamDataRef" selector="{{SampleSection.threeParamElement(simpleData.firstname, simpleDataN.lastname, simpleData.middlename)}}"/> + <click stepKey="selectorReplaceThreeParamOneDupeDataRef" selector="{{SampleSection.threeOneDuplicateParamElement(simpleData.firstname, simpleData.lastname, simpleData.Nmiddlename)}}"/> + + <click stepKey="selectorReplaceOneParamVariable" selector="{{SampleSection.oneParamElement({$dataN})}}"/> + + <click stepKey="selectorReplaceThreeParamMixed1" selector="{{SampleSection.threeParamElement('stringLiteral1', $createdData.Nfirstname$, simpleData.firstname)}}"/> + <click stepKey="selectorReplaceThreeParamMixed2" selector="{{SampleSection.threeParamElement('stringLiteral1', $NcreatedData.firstname$, {$data})}}"/> + + <click stepKey="selectorReplaceTwoParamElements" selector="{{SampleSection.oneParamElement('1')}}{{SampleSectionN.oneParamElement('2')}}"/> + <click stepKey="selectorReplaceTwoParamMixedTypes" selector="{{SampleSection.oneParamElement('1')}}{{SampleSection.oneParamElementN({$data})}}"/> + </test> +</tests> diff --git a/dev/tests/verification/Resources/ActionGroupContainsStepKeyInArgText.txt b/dev/tests/verification/Resources/ActionGroupContainsStepKeyInArgText.txt index d7e31b5f2..3faa07c06 100644 --- a/dev/tests/verification/Resources/ActionGroupContainsStepKeyInArgText.txt +++ b/dev/tests/verification/Resources/ActionGroupContainsStepKeyInArgText.txt @@ -2,8 +2,6 @@ namespace Magento\AcceptanceTest\_default\Backend; use Magento\FunctionalTestingFramework\AcceptanceTester; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\CredentialStore; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\PersistedObjectHandler; use \Codeception\Util\Locator; use Yandex\Allure\Adapter\Annotation\Features; use Yandex\Allure\Adapter\Annotation\Stories; @@ -15,24 +13,41 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/ActionGroupTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/ActionGroupTest/ActionGroupContainsStepKeyInArgText.xml<br>") */ class ActionGroupContainsStepKeyInArgTextCest { + /** + * @var bool + */ + private $isSuccess = false; + /** * @param AcceptanceTester $I * @throws \Exception */ public function _before(AcceptanceTester $I) { + $I->comment('[START BEFORE HOOK]'); $I->comment("Entering Action Group [actionGroup] actionGroupContainsStepKeyInArgValue"); $I->see("arg1", ".selector"); // stepKey: arg1ActionGroup $I->comment("Exiting Action Group [actionGroup] actionGroupContainsStepKeyInArgValue"); + $I->comment('[END BEFORE HOOK]'); + } + + /** + * @param AcceptanceTester $I + * @throws \Exception + */ + public function _after(AcceptanceTester $I) + { + if ($this->isSuccess) { + unlink(__FILE__); + } } /** * @Features({"TestModule"}) - * @Parameter(name = "AcceptanceTester", value="$I") * @param AcceptanceTester $I * @return void * @throws \Exception @@ -43,4 +58,10 @@ class ActionGroupContainsStepKeyInArgTextCest $I->see("arg1", ".selector"); // stepKey: arg1ActionGroup $I->comment("Exiting Action Group [actionGroup] actionGroupContainsStepKeyInArgValue"); } + + public function _passed(AcceptanceTester $I) + { + // Test passed successfully. + $this->isSuccess = true; + } } diff --git a/dev/tests/verification/Resources/ActionGroupMergedViaInsertAfter.txt b/dev/tests/verification/Resources/ActionGroupMergedViaInsertAfter.txt index 127b9cc59..56f80c3af 100644 --- a/dev/tests/verification/Resources/ActionGroupMergedViaInsertAfter.txt +++ b/dev/tests/verification/Resources/ActionGroupMergedViaInsertAfter.txt @@ -2,8 +2,6 @@ namespace Magento\AcceptanceTest\_default\Backend; use Magento\FunctionalTestingFramework\AcceptanceTester; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\CredentialStore; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\PersistedObjectHandler; use \Codeception\Util\Locator; use Yandex\Allure\Adapter\Annotation\Features; use Yandex\Allure\Adapter\Annotation\Stories; @@ -15,13 +13,28 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/ActionGroupFunctionalTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/ActionGroupFunctionalTest/ActionGroupMergedViaInsertAfter.xml<br>") */ class ActionGroupMergedViaInsertAfterCest { + /** + * @var bool + */ + private $isSuccess = false; + + /** + * @param AcceptanceTester $I + * @throws \Exception + */ + public function _after(AcceptanceTester $I) + { + if ($this->isSuccess) { + unlink(__FILE__); + } + } + /** * @Features({"TestModule"}) - * @Parameter(name = "AcceptanceTester", value="$I") * @param AcceptanceTester $I * @return void * @throws \Exception @@ -37,4 +50,10 @@ class ActionGroupMergedViaInsertAfterCest $I->fillField("#baz", "baz"); // stepKey: fillField3Keyone $I->comment("Exiting Action Group [keyone] FunctionalActionGroupForMassMergeAfter"); } + + public function _passed(AcceptanceTester $I) + { + // Test passed successfully. + $this->isSuccess = true; + } } diff --git a/dev/tests/verification/Resources/ActionGroupMergedViaInsertBefore.txt b/dev/tests/verification/Resources/ActionGroupMergedViaInsertBefore.txt index 1486d042e..1479f8d9d 100644 --- a/dev/tests/verification/Resources/ActionGroupMergedViaInsertBefore.txt +++ b/dev/tests/verification/Resources/ActionGroupMergedViaInsertBefore.txt @@ -2,8 +2,6 @@ namespace Magento\AcceptanceTest\_default\Backend; use Magento\FunctionalTestingFramework\AcceptanceTester; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\CredentialStore; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\PersistedObjectHandler; use \Codeception\Util\Locator; use Yandex\Allure\Adapter\Annotation\Features; use Yandex\Allure\Adapter\Annotation\Stories; @@ -15,13 +13,28 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/ActionGroupFunctionalTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/ActionGroupFunctionalTest/ActionGroupMergedViaInsertBefore.xml<br>") */ class ActionGroupMergedViaInsertBeforeCest { + /** + * @var bool + */ + private $isSuccess = false; + + /** + * @param AcceptanceTester $I + * @throws \Exception + */ + public function _after(AcceptanceTester $I) + { + if ($this->isSuccess) { + unlink(__FILE__); + } + } + /** * @Features({"TestModule"}) - * @Parameter(name = "AcceptanceTester", value="$I") * @param AcceptanceTester $I * @return void * @throws \Exception @@ -37,4 +50,10 @@ class ActionGroupMergedViaInsertBeforeCest $I->fillField("#baz", "baz"); // stepKey: fillField3Keyone $I->comment("Exiting Action Group [keyone] FunctionalActionGroupForMassMergeBefore"); } + + public function _passed(AcceptanceTester $I) + { + // Test passed successfully. + $this->isSuccess = true; + } } diff --git a/dev/tests/verification/Resources/ActionGroupReturningValueTest.txt b/dev/tests/verification/Resources/ActionGroupReturningValueTest.txt new file mode 100644 index 000000000..fdf05ad4b --- /dev/null +++ b/dev/tests/verification/Resources/ActionGroupReturningValueTest.txt @@ -0,0 +1,92 @@ +<?php +namespace Magento\AcceptanceTest\_default\Backend; + +use Magento\FunctionalTestingFramework\AcceptanceTester; +use \Codeception\Util\Locator; +use Yandex\Allure\Adapter\Annotation\Features; +use Yandex\Allure\Adapter\Annotation\Stories; +use Yandex\Allure\Adapter\Annotation\Title; +use Yandex\Allure\Adapter\Annotation\Description; +use Yandex\Allure\Adapter\Annotation\Parameter; +use Yandex\Allure\Adapter\Annotation\Severity; +use Yandex\Allure\Adapter\Model\SeverityLevel; +use Yandex\Allure\Adapter\Annotation\TestCaseId; + +/** + * @group functional + * @Description("<h3>Test files</h3>verification/TestModule/Test/ActionGroupFunctionalTest/ActionGroupReturningValueTest.xml<br>") + */ +class ActionGroupReturningValueTestCest +{ + /** + * @var bool + */ + private $isSuccess = false; + + /** + * @param AcceptanceTester $I + * @throws \Exception + */ + public function _before(AcceptanceTester $I) + { + $I->comment('[START BEFORE HOOK]'); + $I->createEntity("createPersonParam", "hook", "ReplacementPerson", [], []); // stepKey: createPersonParam + $I->comment("Entering Action Group [beforeGroup] FunctionalActionGroup"); + $I->fillField("#foo", "myData1"); // stepKey: fillField1BeforeGroup + $I->fillField("#bar", "myData2"); // stepKey: fillField2BeforeGroup + $I->comment("Exiting Action Group [beforeGroup] FunctionalActionGroup"); + $I->comment('[END BEFORE HOOK]'); + } + + /** + * @param AcceptanceTester $I + * @throws \Exception + */ + public function _after(AcceptanceTester $I) + { + $I->comment('[START AFTER HOOK]'); + $I->comment("Entering Action Group [afterGroup] FunctionalActionGroup"); + $I->fillField("#foo", "myData1"); // stepKey: fillField1AfterGroup + $I->fillField("#bar", "myData2"); // stepKey: fillField2AfterGroup + $I->comment("Exiting Action Group [afterGroup] FunctionalActionGroup"); + $I->comment('[END AFTER HOOK]'); + if ($this->isSuccess) { + unlink(__FILE__); + } + } + + /** + * @param AcceptanceTester $I + * @throws \Exception + */ + public function _failed(AcceptanceTester $I) + { + $I->saveScreenshot(); // stepKey: saveScreenshot + } + + /** + * @Severity(level = SeverityLevel::CRITICAL) + * @Features({"TestModule"}) + * @Stories({"MQE-433"}) + * @param AcceptanceTester $I + * @return void + * @throws \Exception + */ + public function ActionGroupReturningValueTest(AcceptanceTester $I) + { + $I->amOnPage("/someUrl"); // stepKey: step1 + $I->comment("Entering Action Group [actionGroupWithReturnValue1] FunctionalActionGroupWithReturnValueActionGroup"); + $grabTextFrom1ActionGroupWithReturnValue1 = $I->grabTextFrom("#foo"); // stepKey: grabTextFrom1ActionGroupWithReturnValue1 + $actionGroupWithReturnValue1 = $I->return($grabTextFrom1ActionGroupWithReturnValue1); // stepKey: returnActionGroupWithReturnValue1 + $I->comment("Exiting Action Group [actionGroupWithReturnValue1] FunctionalActionGroupWithReturnValueActionGroup"); + $I->comment("Entering Action Group [actionGroupWithStringUsage1] actionGroupWithStringUsage"); + $I->see($actionGroupWithReturnValue1, "#element .{$actionGroupWithReturnValue1}"); // stepKey: see1ActionGroupWithStringUsage1 + $I->comment("Exiting Action Group [actionGroupWithStringUsage1] actionGroupWithStringUsage"); + } + + public function _passed(AcceptanceTester $I) + { + // Test passed successfully. + $this->isSuccess = true; + } +} diff --git a/dev/tests/verification/Resources/ActionGroupSkipReadiness.txt b/dev/tests/verification/Resources/ActionGroupSkipReadiness.txt deleted file mode 100644 index d11d5c238..000000000 --- a/dev/tests/verification/Resources/ActionGroupSkipReadiness.txt +++ /dev/null @@ -1,37 +0,0 @@ -<?php -namespace Magento\AcceptanceTest\_default\Backend; - -use Magento\FunctionalTestingFramework\AcceptanceTester; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\CredentialStore; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\PersistedObjectHandler; -use \Codeception\Util\Locator; -use Yandex\Allure\Adapter\Annotation\Features; -use Yandex\Allure\Adapter\Annotation\Stories; -use Yandex\Allure\Adapter\Annotation\Title; -use Yandex\Allure\Adapter\Annotation\Description; -use Yandex\Allure\Adapter\Annotation\Parameter; -use Yandex\Allure\Adapter\Annotation\Severity; -use Yandex\Allure\Adapter\Model\SeverityLevel; -use Yandex\Allure\Adapter\Annotation\TestCaseId; - -/** - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/ActionGroupTest.xml<br>") - */ -class ActionGroupSkipReadinessCest -{ - /** - * @Features({"TestModule"}) - * @Parameter(name = "AcceptanceTester", value="$I") - * @param AcceptanceTester $I - * @return void - * @throws \Exception - */ - public function ActionGroupSkipReadiness(AcceptanceTester $I) - { - $I->comment("Entering Action Group actionGroupWithSkipReadinessActions (skipReadinessActionGroup)"); - $I->skipReadinessCheck(true); - $I->comment("ActionGroupSkipReadiness"); - $I->skipReadinessCheck(false); - $I->comment("Exiting Action Group actionGroupWithSkipReadinessActions (skipReadinessActionGroup)"); - } -} diff --git a/dev/tests/verification/Resources/ActionGroupToExtend.txt b/dev/tests/verification/Resources/ActionGroupToExtend.txt index d7e72260d..7fd1a8f05 100644 --- a/dev/tests/verification/Resources/ActionGroupToExtend.txt +++ b/dev/tests/verification/Resources/ActionGroupToExtend.txt @@ -2,8 +2,6 @@ namespace Magento\AcceptanceTest\_default\Backend; use Magento\FunctionalTestingFramework\AcceptanceTester; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\CredentialStore; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\PersistedObjectHandler; use \Codeception\Util\Locator; use Yandex\Allure\Adapter\Annotation\Features; use Yandex\Allure\Adapter\Annotation\Stories; @@ -15,13 +13,28 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/ActionGroupTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/ActionGroupTest/ActionGroupToExtend.xml<br>") */ class ActionGroupToExtendCest { + /** + * @var bool + */ + private $isSuccess = false; + + /** + * @param AcceptanceTester $I + * @throws \Exception + */ + public function _after(AcceptanceTester $I) + { + if ($this->isSuccess) { + unlink(__FILE__); + } + } + /** * @Features({"TestModule"}) - * @Parameter(name = "AcceptanceTester", value="$I") * @param AcceptanceTester $I * @return void * @throws \Exception @@ -33,4 +46,10 @@ class ActionGroupToExtendCest $I->assertCount(99, $grabProductsActionGroup); // stepKey: assertCountActionGroup $I->comment("Exiting Action Group [actionGroup] ActionGroupToExtend"); } + + public function _passed(AcceptanceTester $I) + { + // Test passed successfully. + $this->isSuccess = true; + } } diff --git a/dev/tests/verification/Resources/ActionGroupUsingCreateData.txt b/dev/tests/verification/Resources/ActionGroupUsingCreateData.txt index 96134fd86..2ca4e0a63 100644 --- a/dev/tests/verification/Resources/ActionGroupUsingCreateData.txt +++ b/dev/tests/verification/Resources/ActionGroupUsingCreateData.txt @@ -2,8 +2,6 @@ namespace Magento\AcceptanceTest\_default\Backend; use Magento\FunctionalTestingFramework\AcceptanceTester; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\CredentialStore; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\PersistedObjectHandler; use \Codeception\Util\Locator; use Yandex\Allure\Adapter\Annotation\Features; use Yandex\Allure\Adapter\Annotation\Stories; @@ -15,41 +13,41 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/ActionGroupTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/ActionGroupTest/ActionGroupUsingCreateData.xml<br>") */ class ActionGroupUsingCreateDataCest { + /** + * @var bool + */ + private $isSuccess = false; + /** * @param AcceptanceTester $I * @throws \Exception */ public function _before(AcceptanceTester $I) { + $I->comment('[START BEFORE HOOK]'); $I->comment("Entering Action Group [Key1] actionGroupWithCreateData"); - $I->comment("[createCategoryKey1] create 'ApiCategory' entity"); - PersistedObjectHandler::getInstance()->createEntity( - "createCategoryKey1", - "hook", - "ApiCategory", - [], - [] - ); - - $I->comment("[createConfigProductKey1] create 'ApiConfigurableProduct' entity"); - PersistedObjectHandler::getInstance()->createEntity( - "createConfigProductKey1", - "hook", - "ApiConfigurableProduct", - ["createCategoryKey1"], - [] - ); - + $I->createEntity("createConfigProductKey1", "hook", "TestData", ["createCategory"], []); // stepKey: createConfigProductKey1 $I->comment("Exiting Action Group [Key1] actionGroupWithCreateData"); + $I->comment('[END BEFORE HOOK]'); + } + + /** + * @param AcceptanceTester $I + * @throws \Exception + */ + public function _after(AcceptanceTester $I) + { + if ($this->isSuccess) { + unlink(__FILE__); + } } /** * @Features({"TestModule"}) - * @Parameter(name = "AcceptanceTester", value="$I") * @param AcceptanceTester $I * @return void * @throws \Exception @@ -57,4 +55,10 @@ class ActionGroupUsingCreateDataCest public function ActionGroupUsingCreateData(AcceptanceTester $I) { } + + public function _passed(AcceptanceTester $I) + { + // Test passed successfully. + $this->isSuccess = true; + } } diff --git a/dev/tests/verification/Resources/ActionGroupUsingNestedArgument.txt b/dev/tests/verification/Resources/ActionGroupUsingNestedArgument.txt index b69ee6c9d..40254de00 100644 --- a/dev/tests/verification/Resources/ActionGroupUsingNestedArgument.txt +++ b/dev/tests/verification/Resources/ActionGroupUsingNestedArgument.txt @@ -2,8 +2,6 @@ namespace Magento\AcceptanceTest\_default\Backend; use Magento\FunctionalTestingFramework\AcceptanceTester; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\CredentialStore; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\PersistedObjectHandler; use \Codeception\Util\Locator; use Yandex\Allure\Adapter\Annotation\Features; use Yandex\Allure\Adapter\Annotation\Stories; @@ -15,13 +13,28 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/ActionGroupTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/ActionGroupTest/ActionGroupUsingNestedArgument.xml<br>") */ class ActionGroupUsingNestedArgumentCest { + /** + * @var bool + */ + private $isSuccess = false; + + /** + * @param AcceptanceTester $I + * @throws \Exception + */ + public function _after(AcceptanceTester $I) + { + if ($this->isSuccess) { + unlink(__FILE__); + } + } + /** * @Features({"TestModule"}) - * @Parameter(name = "AcceptanceTester", value="$I") * @param AcceptanceTester $I * @return void * @throws \Exception @@ -33,4 +46,10 @@ class ActionGroupUsingNestedArgumentCest $I->assertCount(99, $grabProductsActionGroup); // stepKey: assertCountActionGroup $I->comment("Exiting Action Group [actionGroup] ActionGroupToExtend"); } + + public function _passed(AcceptanceTester $I) + { + // Test passed successfully. + $this->isSuccess = true; + } } diff --git a/dev/tests/verification/Resources/ActionGroupWithDataOverrideTest.txt b/dev/tests/verification/Resources/ActionGroupWithDataOverrideTest.txt index c0498101d..79f182e8a 100644 --- a/dev/tests/verification/Resources/ActionGroupWithDataOverrideTest.txt +++ b/dev/tests/verification/Resources/ActionGroupWithDataOverrideTest.txt @@ -2,8 +2,6 @@ namespace Magento\AcceptanceTest\_default\Backend; use Magento\FunctionalTestingFramework\AcceptanceTester; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\CredentialStore; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\PersistedObjectHandler; use \Codeception\Util\Locator; use Yandex\Allure\Adapter\Annotation\Features; use Yandex\Allure\Adapter\Annotation\Stories; @@ -16,29 +14,28 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @group functional - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/ActionGroupFunctionalTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/ActionGroupFunctionalTest/ActionGroupWithDataOverrideTest.xml<br>") */ class ActionGroupWithDataOverrideTestCest { + /** + * @var bool + */ + private $isSuccess = false; + /** * @param AcceptanceTester $I * @throws \Exception */ public function _before(AcceptanceTester $I) { - $I->comment("[createPersonParam] create 'ReplacementPerson' entity"); - PersistedObjectHandler::getInstance()->createEntity( - "createPersonParam", - "hook", - "ReplacementPerson", - [], - [] - ); - + $I->comment('[START BEFORE HOOK]'); + $I->createEntity("createPersonParam", "hook", "ReplacementPerson", [], []); // stepKey: createPersonParam $I->comment("Entering Action Group [beforeGroup] FunctionalActionGroup"); $I->fillField("#foo", "myData1"); // stepKey: fillField1BeforeGroup $I->fillField("#bar", "myData2"); // stepKey: fillField2BeforeGroup $I->comment("Exiting Action Group [beforeGroup] FunctionalActionGroup"); + $I->comment('[END BEFORE HOOK]'); } /** @@ -47,10 +44,15 @@ class ActionGroupWithDataOverrideTestCest */ public function _after(AcceptanceTester $I) { + $I->comment('[START AFTER HOOK]'); $I->comment("Entering Action Group [afterGroup] FunctionalActionGroup"); $I->fillField("#foo", "myData1"); // stepKey: fillField1AfterGroup $I->fillField("#bar", "myData2"); // stepKey: fillField2AfterGroup $I->comment("Exiting Action Group [afterGroup] FunctionalActionGroup"); + $I->comment('[END AFTER HOOK]'); + if ($this->isSuccess) { + unlink(__FILE__); + } } /** @@ -66,7 +68,6 @@ class ActionGroupWithDataOverrideTestCest * @Severity(level = SeverityLevel::CRITICAL) * @Features({"TestModule"}) * @Stories({"MQE-433"}) - * @Parameter(name = "AcceptanceTester", value="$I") * @param AcceptanceTester $I * @return void * @throws \Exception @@ -83,4 +84,10 @@ class ActionGroupWithDataOverrideTestCest $I->comment("Exiting Action Group [actionGroupWithDataOverride1] FunctionalActionGroupWithData"); $I->click("loginButton"); // stepKey: step6 } + + public function _passed(AcceptanceTester $I) + { + // Test passed successfully. + $this->isSuccess = true; + } } diff --git a/dev/tests/verification/Resources/ActionGroupWithDataTest.txt b/dev/tests/verification/Resources/ActionGroupWithDataTest.txt index 01a87104d..ed8b69a77 100644 --- a/dev/tests/verification/Resources/ActionGroupWithDataTest.txt +++ b/dev/tests/verification/Resources/ActionGroupWithDataTest.txt @@ -2,8 +2,6 @@ namespace Magento\AcceptanceTest\_default\Backend; use Magento\FunctionalTestingFramework\AcceptanceTester; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\CredentialStore; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\PersistedObjectHandler; use \Codeception\Util\Locator; use Yandex\Allure\Adapter\Annotation\Features; use Yandex\Allure\Adapter\Annotation\Stories; @@ -16,29 +14,28 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @group functional - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/ActionGroupFunctionalTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/ActionGroupFunctionalTest/ActionGroupWithDataTest.xml<br>") */ class ActionGroupWithDataTestCest { + /** + * @var bool + */ + private $isSuccess = false; + /** * @param AcceptanceTester $I * @throws \Exception */ public function _before(AcceptanceTester $I) { - $I->comment("[createPersonParam] create 'ReplacementPerson' entity"); - PersistedObjectHandler::getInstance()->createEntity( - "createPersonParam", - "hook", - "ReplacementPerson", - [], - [] - ); - + $I->comment('[START BEFORE HOOK]'); + $I->createEntity("createPersonParam", "hook", "ReplacementPerson", [], []); // stepKey: createPersonParam $I->comment("Entering Action Group [beforeGroup] FunctionalActionGroup"); $I->fillField("#foo", "myData1"); // stepKey: fillField1BeforeGroup $I->fillField("#bar", "myData2"); // stepKey: fillField2BeforeGroup $I->comment("Exiting Action Group [beforeGroup] FunctionalActionGroup"); + $I->comment('[END BEFORE HOOK]'); } /** @@ -47,10 +44,15 @@ class ActionGroupWithDataTestCest */ public function _after(AcceptanceTester $I) { + $I->comment('[START AFTER HOOK]'); $I->comment("Entering Action Group [afterGroup] FunctionalActionGroup"); $I->fillField("#foo", "myData1"); // stepKey: fillField1AfterGroup $I->fillField("#bar", "myData2"); // stepKey: fillField2AfterGroup $I->comment("Exiting Action Group [afterGroup] FunctionalActionGroup"); + $I->comment('[END AFTER HOOK]'); + if ($this->isSuccess) { + unlink(__FILE__); + } } /** @@ -66,7 +68,6 @@ class ActionGroupWithDataTestCest * @Severity(level = SeverityLevel::CRITICAL) * @Features({"TestModule"}) * @Stories({"MQE-433"}) - * @Parameter(name = "AcceptanceTester", value="$I") * @param AcceptanceTester $I * @return void * @throws \Exception @@ -83,4 +84,10 @@ class ActionGroupWithDataTestCest $I->comment("Exiting Action Group [actionGroupWithData1] FunctionalActionGroupWithData"); $I->click("loginButton"); // stepKey: step6 } + + public function _passed(AcceptanceTester $I) + { + // Test passed successfully. + $this->isSuccess = true; + } } diff --git a/dev/tests/verification/Resources/ActionGroupWithDefaultArgumentAndStringSelectorParam.txt b/dev/tests/verification/Resources/ActionGroupWithDefaultArgumentAndStringSelectorParam.txt index ce8a4aed8..838c98e3b 100644 --- a/dev/tests/verification/Resources/ActionGroupWithDefaultArgumentAndStringSelectorParam.txt +++ b/dev/tests/verification/Resources/ActionGroupWithDefaultArgumentAndStringSelectorParam.txt @@ -2,8 +2,6 @@ namespace Magento\AcceptanceTest\_default\Backend; use Magento\FunctionalTestingFramework\AcceptanceTester; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\CredentialStore; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\PersistedObjectHandler; use \Codeception\Util\Locator; use Yandex\Allure\Adapter\Annotation\Features; use Yandex\Allure\Adapter\Annotation\Stories; @@ -16,14 +14,29 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @Title("[NO TESTCASEID]: Action Group With Default Argument Value and Hardcoded Value in Param") - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/ActionGroupTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/ActionGroupTest/ActionGroupWithDefaultArgumentAndStringSelectorParam.xml<br>") */ class ActionGroupWithDefaultArgumentAndStringSelectorParamCest { + /** + * @var bool + */ + private $isSuccess = false; + + /** + * @param AcceptanceTester $I + * @throws \Exception + */ + public function _after(AcceptanceTester $I) + { + if ($this->isSuccess) { + unlink(__FILE__); + } + } + /** * @Severity(level = SeverityLevel::BLOCKER) * @Features({"TestModule"}) - * @Parameter(name = "AcceptanceTester", value="$I") * @param AcceptanceTester $I * @return void * @throws \Exception @@ -34,4 +47,10 @@ class ActionGroupWithDefaultArgumentAndStringSelectorParamCest $I->see("John", "#element .test1"); // stepKey: seeFirstNameActionGroup $I->comment("Exiting Action Group [actionGroup] actionGroupWithDefaultArgumentAndStringSelectorParam"); } + + public function _passed(AcceptanceTester $I) + { + // Test passed successfully. + $this->isSuccess = true; + } } diff --git a/dev/tests/verification/Resources/ActionGroupWithMultipleParameterSelectorsFromDefaultArgument.txt b/dev/tests/verification/Resources/ActionGroupWithMultipleParameterSelectorsFromDefaultArgument.txt index b7bef0135..31a77b903 100644 --- a/dev/tests/verification/Resources/ActionGroupWithMultipleParameterSelectorsFromDefaultArgument.txt +++ b/dev/tests/verification/Resources/ActionGroupWithMultipleParameterSelectorsFromDefaultArgument.txt @@ -2,8 +2,6 @@ namespace Magento\AcceptanceTest\_default\Backend; use Magento\FunctionalTestingFramework\AcceptanceTester; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\CredentialStore; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\PersistedObjectHandler; use \Codeception\Util\Locator; use Yandex\Allure\Adapter\Annotation\Features; use Yandex\Allure\Adapter\Annotation\Stories; @@ -16,14 +14,29 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @Title("[NO TESTCASEID]: Action Group With Passed Argument Value and Multiple Argument Values in Param") - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/ActionGroupTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/ActionGroupTest/ActionGroupWithMultipleParameterSelectorsFromDefaultArgument.xml<br>") */ class ActionGroupWithMultipleParameterSelectorsFromDefaultArgumentCest { + /** + * @var bool + */ + private $isSuccess = false; + + /** + * @param AcceptanceTester $I + * @throws \Exception + */ + public function _after(AcceptanceTester $I) + { + if ($this->isSuccess) { + unlink(__FILE__); + } + } + /** * @Severity(level = SeverityLevel::BLOCKER) * @Features({"TestModule"}) - * @Parameter(name = "AcceptanceTester", value="$I") * @param AcceptanceTester $I * @return void * @throws \Exception @@ -34,4 +47,10 @@ class ActionGroupWithMultipleParameterSelectorsFromDefaultArgumentCest $I->see("Doe", "#John-Doe .test"); // stepKey: seeLastNameActionGroup $I->comment("Exiting Action Group [actionGroup] actionGroupWithMultipleParameterSelectorsFromArgument"); } + + public function _passed(AcceptanceTester $I) + { + // Test passed successfully. + $this->isSuccess = true; + } } diff --git a/dev/tests/verification/Resources/ActionGroupWithNoArguments.txt b/dev/tests/verification/Resources/ActionGroupWithNoArguments.txt index 6b81a187f..9d06b309e 100644 --- a/dev/tests/verification/Resources/ActionGroupWithNoArguments.txt +++ b/dev/tests/verification/Resources/ActionGroupWithNoArguments.txt @@ -2,8 +2,6 @@ namespace Magento\AcceptanceTest\_default\Backend; use Magento\FunctionalTestingFramework\AcceptanceTester; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\CredentialStore; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\PersistedObjectHandler; use \Codeception\Util\Locator; use Yandex\Allure\Adapter\Annotation\Features; use Yandex\Allure\Adapter\Annotation\Stories; @@ -16,14 +14,29 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @Title("[NO TESTCASEID]: Action Group With No Argument") - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/ActionGroupTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/ActionGroupTest/ActionGroupWithNoArguments.xml<br>") */ class ActionGroupWithNoArgumentsCest { + /** + * @var bool + */ + private $isSuccess = false; + + /** + * @param AcceptanceTester $I + * @throws \Exception + */ + public function _after(AcceptanceTester $I) + { + if ($this->isSuccess) { + unlink(__FILE__); + } + } + /** * @Severity(level = SeverityLevel::BLOCKER) * @Features({"TestModule"}) - * @Parameter(name = "AcceptanceTester", value="$I") * @param AcceptanceTester $I * @return void * @throws \Exception @@ -34,4 +47,10 @@ class ActionGroupWithNoArgumentsCest $I->wait(1); // stepKey: waitForNothingActionGroup $I->comment("Exiting Action Group [actionGroup] actionGroupWithoutArguments"); } + + public function _passed(AcceptanceTester $I) + { + // Test passed successfully. + $this->isSuccess = true; + } } diff --git a/dev/tests/verification/Resources/ActionGroupWithNoDefaultTest.txt b/dev/tests/verification/Resources/ActionGroupWithNoDefaultTest.txt index 6125c7697..435b8a5cc 100644 --- a/dev/tests/verification/Resources/ActionGroupWithNoDefaultTest.txt +++ b/dev/tests/verification/Resources/ActionGroupWithNoDefaultTest.txt @@ -2,8 +2,6 @@ namespace Magento\AcceptanceTest\_default\Backend; use Magento\FunctionalTestingFramework\AcceptanceTester; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\CredentialStore; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\PersistedObjectHandler; use \Codeception\Util\Locator; use Yandex\Allure\Adapter\Annotation\Features; use Yandex\Allure\Adapter\Annotation\Stories; @@ -16,29 +14,28 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @group functional - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/ActionGroupFunctionalTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/ActionGroupFunctionalTest/ActionGroupWithNoDefaultTest.xml<br>") */ class ActionGroupWithNoDefaultTestCest { + /** + * @var bool + */ + private $isSuccess = false; + /** * @param AcceptanceTester $I * @throws \Exception */ public function _before(AcceptanceTester $I) { - $I->comment("[createPersonParam] create 'ReplacementPerson' entity"); - PersistedObjectHandler::getInstance()->createEntity( - "createPersonParam", - "hook", - "ReplacementPerson", - [], - [] - ); - + $I->comment('[START BEFORE HOOK]'); + $I->createEntity("createPersonParam", "hook", "ReplacementPerson", [], []); // stepKey: createPersonParam $I->comment("Entering Action Group [beforeGroup] FunctionalActionGroup"); $I->fillField("#foo", "myData1"); // stepKey: fillField1BeforeGroup $I->fillField("#bar", "myData2"); // stepKey: fillField2BeforeGroup $I->comment("Exiting Action Group [beforeGroup] FunctionalActionGroup"); + $I->comment('[END BEFORE HOOK]'); } /** @@ -47,10 +44,15 @@ class ActionGroupWithNoDefaultTestCest */ public function _after(AcceptanceTester $I) { + $I->comment('[START AFTER HOOK]'); $I->comment("Entering Action Group [afterGroup] FunctionalActionGroup"); $I->fillField("#foo", "myData1"); // stepKey: fillField1AfterGroup $I->fillField("#bar", "myData2"); // stepKey: fillField2AfterGroup $I->comment("Exiting Action Group [afterGroup] FunctionalActionGroup"); + $I->comment('[END AFTER HOOK]'); + if ($this->isSuccess) { + unlink(__FILE__); + } } /** @@ -66,7 +68,6 @@ class ActionGroupWithNoDefaultTestCest * @Severity(level = SeverityLevel::CRITICAL) * @Features({"TestModule"}) * @Stories({"MQE-433"}) - * @Parameter(name = "AcceptanceTester", value="$I") * @param AcceptanceTester $I * @return void * @throws \Exception @@ -81,4 +82,10 @@ class ActionGroupWithNoDefaultTestCest $I->comment("Exiting Action Group [actionGroupWithDataOverride1] FunctionalActionGroupNoDefault"); $I->click("loginButton"); // stepKey: step6 } + + public function _passed(AcceptanceTester $I) + { + // Test passed successfully. + $this->isSuccess = true; + } } diff --git a/dev/tests/verification/Resources/ActionGroupWithParameterizedElementWithHyphen.txt b/dev/tests/verification/Resources/ActionGroupWithParameterizedElementWithHyphen.txt index 163ebc6ba..376a6298d 100644 --- a/dev/tests/verification/Resources/ActionGroupWithParameterizedElementWithHyphen.txt +++ b/dev/tests/verification/Resources/ActionGroupWithParameterizedElementWithHyphen.txt @@ -2,8 +2,6 @@ namespace Magento\AcceptanceTest\_default\Backend; use Magento\FunctionalTestingFramework\AcceptanceTester; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\CredentialStore; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\PersistedObjectHandler; use \Codeception\Util\Locator; use Yandex\Allure\Adapter\Annotation\Features; use Yandex\Allure\Adapter\Annotation\Stories; @@ -15,13 +13,28 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/ActionGroupTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/ActionGroupTest/ActionGroupWithParameterizedElementWithHyphen.xml<br>") */ class ActionGroupWithParameterizedElementWithHyphenCest { + /** + * @var bool + */ + private $isSuccess = false; + + /** + * @param AcceptanceTester $I + * @throws \Exception + */ + public function _after(AcceptanceTester $I) + { + if ($this->isSuccess) { + unlink(__FILE__); + } + } + /** * @Features({"TestModule"}) - * @Parameter(name = "AcceptanceTester", value="$I") * @param AcceptanceTester $I * @return void * @throws \Exception @@ -32,4 +45,10 @@ class ActionGroupWithParameterizedElementWithHyphenCest $keyoneActionGroup = $I->executeJS("#element .full-width"); // stepKey: keyoneActionGroup $I->comment("Exiting Action Group [actionGroup] SectionArgumentWithParameterizedSelector"); } + + public function _passed(AcceptanceTester $I) + { + // Test passed successfully. + $this->isSuccess = true; + } } diff --git a/dev/tests/verification/Resources/ActionGroupWithParameterizedElementsWithStepKeyReferences.txt b/dev/tests/verification/Resources/ActionGroupWithParameterizedElementsWithStepKeyReferences.txt index de7af8209..83aaecf08 100644 --- a/dev/tests/verification/Resources/ActionGroupWithParameterizedElementsWithStepKeyReferences.txt +++ b/dev/tests/verification/Resources/ActionGroupWithParameterizedElementsWithStepKeyReferences.txt @@ -2,8 +2,6 @@ namespace Magento\AcceptanceTest\_default\Backend; use Magento\FunctionalTestingFramework\AcceptanceTester; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\CredentialStore; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\PersistedObjectHandler; use \Codeception\Util\Locator; use Yandex\Allure\Adapter\Annotation\Features; use Yandex\Allure\Adapter\Annotation\Stories; @@ -15,13 +13,28 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/ActionGroupTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/ActionGroupTest/ActionGroupWithParameterizedElementsWithStepKeyReferences.xml<br>") */ class ActionGroupWithParameterizedElementsWithStepKeyReferencesCest { + /** + * @var bool + */ + private $isSuccess = false; + + /** + * @param AcceptanceTester $I + * @throws \Exception + */ + public function _after(AcceptanceTester $I) + { + if ($this->isSuccess) { + unlink(__FILE__); + } + } + /** * @Features({"TestModule"}) - * @Parameter(name = "AcceptanceTester", value="$I") * @param AcceptanceTester $I * @return void * @throws \Exception @@ -31,18 +44,16 @@ class ActionGroupWithParameterizedElementsWithStepKeyReferencesCest $I->comment("Entering Action Group [actionGroup] actionGroupWithParametrizedSelectors"); $testVariableActionGroup = $I->executeJS("return 1"); // stepKey: testVariableActionGroup $testVariable2ActionGroup = $I->executeJS("return 'test'"); // stepKey: testVariable2ActionGroup - $I->comment("[createSimpleDataActionGroup] create 'simpleData' entity"); - PersistedObjectHandler::getInstance()->createEntity( - "createSimpleDataActionGroup", - "test", - "simpleData", - [], - [] - ); - + $I->createEntity("createSimpleDataActionGroup", "test", "simpleData", [], []); // stepKey: createSimpleDataActionGroup $I->click("#{$testVariable2ActionGroup} .John"); // stepKey: click1ActionGroup $I->click("#Doe-" . msq("simpleParamData") . "prename .{$testVariableActionGroup}"); // stepKey: click2ActionGroup - $I->seeElement("//div[@name='Tiberius'][@class={$testVariableActionGroup}][@data-element='{$testVariable2ActionGroup}'][" . PersistedObjectHandler::getInstance()->retrieveEntityField('createSimpleData', 'name', 'test') . "]"); // stepKey: see1ActionGroup + $I->seeElement("//div[@name='Tiberius'][@class={$testVariableActionGroup}][@data-element='{$testVariable2ActionGroup}'][" . $I->retrieveEntityField('createSimpleData', 'name', 'test') . "]"); // stepKey: see1ActionGroup $I->comment("Exiting Action Group [actionGroup] actionGroupWithParametrizedSelectors"); } + + public function _passed(AcceptanceTester $I) + { + // Test passed successfully. + $this->isSuccess = true; + } } diff --git a/dev/tests/verification/Resources/ActionGroupWithPassedArgumentAndStringSelectorParam.txt b/dev/tests/verification/Resources/ActionGroupWithPassedArgumentAndStringSelectorParam.txt index 9720595e0..1a2e886bb 100644 --- a/dev/tests/verification/Resources/ActionGroupWithPassedArgumentAndStringSelectorParam.txt +++ b/dev/tests/verification/Resources/ActionGroupWithPassedArgumentAndStringSelectorParam.txt @@ -2,8 +2,6 @@ namespace Magento\AcceptanceTest\_default\Backend; use Magento\FunctionalTestingFramework\AcceptanceTester; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\CredentialStore; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\PersistedObjectHandler; use \Codeception\Util\Locator; use Yandex\Allure\Adapter\Annotation\Features; use Yandex\Allure\Adapter\Annotation\Stories; @@ -16,14 +14,29 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @Title("[NO TESTCASEID]: Action Group With Passed Argument Value and Hardcoded Value in Param") - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/ActionGroupTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/ActionGroupTest/ActionGroupWithPassedArgumentAndStringSelectorParam.xml<br>") */ class ActionGroupWithPassedArgumentAndStringSelectorParamCest { + /** + * @var bool + */ + private $isSuccess = false; + + /** + * @param AcceptanceTester $I + * @throws \Exception + */ + public function _after(AcceptanceTester $I) + { + if ($this->isSuccess) { + unlink(__FILE__); + } + } + /** * @Severity(level = SeverityLevel::BLOCKER) * @Features({"TestModule"}) - * @Parameter(name = "AcceptanceTester", value="$I") * @param AcceptanceTester $I * @return void * @throws \Exception @@ -34,4 +47,10 @@ class ActionGroupWithPassedArgumentAndStringSelectorParamCest $I->see("John" . msq("UniquePerson"), "#element .test1"); // stepKey: seeFirstNameActionGroup $I->comment("Exiting Action Group [actionGroup] actionGroupWithDefaultArgumentAndStringSelectorParam"); } + + public function _passed(AcceptanceTester $I) + { + // Test passed successfully. + $this->isSuccess = true; + } } diff --git a/dev/tests/verification/Resources/ActionGroupWithPersistedData.txt b/dev/tests/verification/Resources/ActionGroupWithPersistedData.txt index da0480379..12aae9c21 100644 --- a/dev/tests/verification/Resources/ActionGroupWithPersistedData.txt +++ b/dev/tests/verification/Resources/ActionGroupWithPersistedData.txt @@ -2,8 +2,6 @@ namespace Magento\AcceptanceTest\_default\Backend; use Magento\FunctionalTestingFramework\AcceptanceTester; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\CredentialStore; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\PersistedObjectHandler; use \Codeception\Util\Locator; use Yandex\Allure\Adapter\Annotation\Features; use Yandex\Allure\Adapter\Annotation\Stories; @@ -16,29 +14,28 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @group functional - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/ActionGroupFunctionalTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/ActionGroupFunctionalTest/ActionGroupWithPersistedData.xml<br>") */ class ActionGroupWithPersistedDataCest { + /** + * @var bool + */ + private $isSuccess = false; + /** * @param AcceptanceTester $I * @throws \Exception */ public function _before(AcceptanceTester $I) { - $I->comment("[createPersonParam] create 'ReplacementPerson' entity"); - PersistedObjectHandler::getInstance()->createEntity( - "createPersonParam", - "hook", - "ReplacementPerson", - [], - [] - ); - + $I->comment('[START BEFORE HOOK]'); + $I->createEntity("createPersonParam", "hook", "ReplacementPerson", [], []); // stepKey: createPersonParam $I->comment("Entering Action Group [beforeGroup] FunctionalActionGroup"); $I->fillField("#foo", "myData1"); // stepKey: fillField1BeforeGroup $I->fillField("#bar", "myData2"); // stepKey: fillField2BeforeGroup $I->comment("Exiting Action Group [beforeGroup] FunctionalActionGroup"); + $I->comment('[END BEFORE HOOK]'); } /** @@ -47,10 +44,15 @@ class ActionGroupWithPersistedDataCest */ public function _after(AcceptanceTester $I) { + $I->comment('[START AFTER HOOK]'); $I->comment("Entering Action Group [afterGroup] FunctionalActionGroup"); $I->fillField("#foo", "myData1"); // stepKey: fillField1AfterGroup $I->fillField("#bar", "myData2"); // stepKey: fillField2AfterGroup $I->comment("Exiting Action Group [afterGroup] FunctionalActionGroup"); + $I->comment('[END AFTER HOOK]'); + if ($this->isSuccess) { + unlink(__FILE__); + } } /** @@ -66,28 +68,25 @@ class ActionGroupWithPersistedDataCest * @Severity(level = SeverityLevel::CRITICAL) * @Features({"TestModule"}) * @Stories({"MQE-433"}) - * @Parameter(name = "AcceptanceTester", value="$I") * @param AcceptanceTester $I * @return void * @throws \Exception */ public function ActionGroupWithPersistedData(AcceptanceTester $I) { - $I->comment("[createPerson] create 'DefaultPerson' entity"); - PersistedObjectHandler::getInstance()->createEntity( - "createPerson", - "test", - "DefaultPerson", - [], - [] - ); - + $I->createEntity("createPerson", "test", "DefaultPerson", [], []); // stepKey: createPerson $I->comment("Entering Action Group [actionGroupWithPersistedData1] FunctionalActionGroupWithData"); - $I->amOnPage("/" . PersistedObjectHandler::getInstance()->retrieveEntityField('createPerson', 'firstname', 'test') . "/" . PersistedObjectHandler::getInstance()->retrieveEntityField('createPerson', 'lastname', 'test') . ".html"); // stepKey: amOnPage1ActionGroupWithPersistedData1 - $I->fillField("#foo", PersistedObjectHandler::getInstance()->retrieveEntityField('createPerson', 'firstname', 'test')); // stepKey: fillField1ActionGroupWithPersistedData1 - $I->fillField("#bar", PersistedObjectHandler::getInstance()->retrieveEntityField('createPerson', 'lastname', 'test')); // stepKey: fillField2ActionGroupWithPersistedData1 - $I->searchAndMultiSelectOption("#foo", [PersistedObjectHandler::getInstance()->retrieveEntityField('createPerson', 'firstname', 'test'), PersistedObjectHandler::getInstance()->retrieveEntityField('createPerson', 'lastname', 'test')]); // stepKey: multi1ActionGroupWithPersistedData1 - $I->see("#element ." . PersistedObjectHandler::getInstance()->retrieveEntityField('createPerson', 'firstname', 'test')); // stepKey: see1ActionGroupWithPersistedData1 + $I->amOnPage("/" . $I->retrieveEntityField('createPerson', 'firstname', 'test') . "/" . $I->retrieveEntityField('createPerson', 'lastname', 'test') . ".html"); // stepKey: amOnPage1ActionGroupWithPersistedData1 + $I->fillField("#foo", $I->retrieveEntityField('createPerson', 'firstname', 'test')); // stepKey: fillField1ActionGroupWithPersistedData1 + $I->fillField("#bar", $I->retrieveEntityField('createPerson', 'lastname', 'test')); // stepKey: fillField2ActionGroupWithPersistedData1 + $I->searchAndMultiSelectOption("#foo", [$I->retrieveEntityField('createPerson', 'firstname', 'test'), $I->retrieveEntityField('createPerson', 'lastname', 'test')]); // stepKey: multi1ActionGroupWithPersistedData1 + $I->see("#element ." . $I->retrieveEntityField('createPerson', 'firstname', 'test')); // stepKey: see1ActionGroupWithPersistedData1 $I->comment("Exiting Action Group [actionGroupWithPersistedData1] FunctionalActionGroupWithData"); } + + public function _passed(AcceptanceTester $I) + { + // Test passed successfully. + $this->isSuccess = true; + } } diff --git a/dev/tests/verification/Resources/ActionGroupWithSectionAndDataAsArguments.txt b/dev/tests/verification/Resources/ActionGroupWithSectionAndDataAsArguments.txt index a0b6d0074..65d25df00 100644 --- a/dev/tests/verification/Resources/ActionGroupWithSectionAndDataAsArguments.txt +++ b/dev/tests/verification/Resources/ActionGroupWithSectionAndDataAsArguments.txt @@ -2,8 +2,6 @@ namespace Magento\AcceptanceTest\_default\Backend; use Magento\FunctionalTestingFramework\AcceptanceTester; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\CredentialStore; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\PersistedObjectHandler; use \Codeception\Util\Locator; use Yandex\Allure\Adapter\Annotation\Features; use Yandex\Allure\Adapter\Annotation\Stories; @@ -15,13 +13,28 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/ActionGroupTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/ActionGroupTest/ActionGroupWithSectionAndDataAsArguments.xml<br>") */ class ActionGroupWithSectionAndDataAsArgumentsCest { + /** + * @var bool + */ + private $isSuccess = false; + + /** + * @param AcceptanceTester $I + * @throws \Exception + */ + public function _after(AcceptanceTester $I) + { + if ($this->isSuccess) { + unlink(__FILE__); + } + } + /** * @Features({"TestModule"}) - * @Parameter(name = "AcceptanceTester", value="$I") * @param AcceptanceTester $I * @return void * @throws \Exception @@ -32,4 +45,10 @@ class ActionGroupWithSectionAndDataAsArgumentsCest $I->waitForElementVisible("#element .John", 10); // stepKey: arg1ActionGroup $I->comment("Exiting Action Group [actionGroup] actionGroupWithSectionAndData"); } + + public function _passed(AcceptanceTester $I) + { + // Test passed successfully. + $this->isSuccess = true; + } } diff --git a/dev/tests/verification/Resources/ActionGroupWithSimpleDataUsageFromDefaultArgument.txt b/dev/tests/verification/Resources/ActionGroupWithSimpleDataUsageFromDefaultArgument.txt index 4d3da18a4..65ce18e9f 100644 --- a/dev/tests/verification/Resources/ActionGroupWithSimpleDataUsageFromDefaultArgument.txt +++ b/dev/tests/verification/Resources/ActionGroupWithSimpleDataUsageFromDefaultArgument.txt @@ -2,8 +2,6 @@ namespace Magento\AcceptanceTest\_default\Backend; use Magento\FunctionalTestingFramework\AcceptanceTester; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\CredentialStore; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\PersistedObjectHandler; use \Codeception\Util\Locator; use Yandex\Allure\Adapter\Annotation\Features; use Yandex\Allure\Adapter\Annotation\Stories; @@ -16,14 +14,29 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @Title("[NO TESTCASEID]: Action Group With Simple Data Usage From Default Argument") - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/ActionGroupTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/ActionGroupTest/ActionGroupWithSimpleDataUsageFromDefaultArgument.xml<br>") */ class ActionGroupWithSimpleDataUsageFromDefaultArgumentCest { + /** + * @var bool + */ + private $isSuccess = false; + + /** + * @param AcceptanceTester $I + * @throws \Exception + */ + public function _after(AcceptanceTester $I) + { + if ($this->isSuccess) { + unlink(__FILE__); + } + } + /** * @Severity(level = SeverityLevel::CRITICAL) * @Features({"TestModule"}) - * @Parameter(name = "AcceptanceTester", value="$I") * @param AcceptanceTester $I * @return void * @throws \Exception @@ -34,4 +47,10 @@ class ActionGroupWithSimpleDataUsageFromDefaultArgumentCest $I->see("stringLiteral", "#element .stringLiteral"); // stepKey: see1ActionGroup $I->comment("Exiting Action Group [actionGroup] actionGroupWithStringUsage"); } + + public function _passed(AcceptanceTester $I) + { + // Test passed successfully. + $this->isSuccess = true; + } } diff --git a/dev/tests/verification/Resources/ActionGroupWithSimpleDataUsageFromPassedArgument.txt b/dev/tests/verification/Resources/ActionGroupWithSimpleDataUsageFromPassedArgument.txt index d866c3992..d33a7da1e 100644 --- a/dev/tests/verification/Resources/ActionGroupWithSimpleDataUsageFromPassedArgument.txt +++ b/dev/tests/verification/Resources/ActionGroupWithSimpleDataUsageFromPassedArgument.txt @@ -2,8 +2,6 @@ namespace Magento\AcceptanceTest\_default\Backend; use Magento\FunctionalTestingFramework\AcceptanceTester; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\CredentialStore; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\PersistedObjectHandler; use \Codeception\Util\Locator; use Yandex\Allure\Adapter\Annotation\Features; use Yandex\Allure\Adapter\Annotation\Stories; @@ -16,14 +14,29 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @Title("[NO TESTCASEID]: Action Group With Simple Data Usage From Passed Argument") - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/ActionGroupTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/ActionGroupTest/ActionGroupWithSimpleDataUsageFromPassedArgument.xml<br>") */ class ActionGroupWithSimpleDataUsageFromPassedArgumentCest { + /** + * @var bool + */ + private $isSuccess = false; + + /** + * @param AcceptanceTester $I + * @throws \Exception + */ + public function _after(AcceptanceTester $I) + { + if ($this->isSuccess) { + unlink(__FILE__); + } + } + /** * @Severity(level = SeverityLevel::CRITICAL) * @Features({"TestModule"}) - * @Parameter(name = "AcceptanceTester", value="$I") * @param AcceptanceTester $I * @return void * @throws \Exception @@ -46,19 +59,25 @@ class ActionGroupWithSimpleDataUsageFromPassedArgumentCest $I->see("simpleData.firstname", "#element .simpleData.firstname"); // stepKey: see1ActionGroup2 $I->comment("Exiting Action Group [actionGroup2] actionGroupWithStringUsage"); $I->comment("Entering Action Group [actionGroup3] actionGroupWithStringUsage"); - $I->see(PersistedObjectHandler::getInstance()->retrieveEntityField('persisted', 'data', 'test'), "#element ." . PersistedObjectHandler::getInstance()->retrieveEntityField('persisted', 'data', 'test')); // stepKey: see1ActionGroup3 + $I->see($I->retrieveEntityField('persisted', 'data', 'test'), "#element ." . $I->retrieveEntityField('persisted', 'data', 'test')); // stepKey: see1ActionGroup3 $I->comment("Exiting Action Group [actionGroup3] actionGroupWithStringUsage"); $I->comment("Entering Action Group [actionGroup4] actionGroupWithEntityUsage"); $I->see("John", "#element .John"); // stepKey: see1ActionGroup4 $I->comment("Exiting Action Group [actionGroup4] actionGroupWithEntityUsage"); $I->comment("Entering Action Group [actionGroup5] actionGroupWithEntityUsage"); - $I->see(PersistedObjectHandler::getInstance()->retrieveEntityField('simpleData', 'firstname', 'test'), "#element ." . PersistedObjectHandler::getInstance()->retrieveEntityField('simpleData', 'firstname', 'test')); // stepKey: see1ActionGroup5 + $I->see($I->retrieveEntityField('simpleData', 'firstname', 'test'), "#element ." . $I->retrieveEntityField('simpleData', 'firstname', 'test')); // stepKey: see1ActionGroup5 $I->comment("Exiting Action Group [actionGroup5] actionGroupWithEntityUsage"); $I->comment("Entering Action Group [actionGroup6] actionGroupWithEntityUsage"); - $I->see(PersistedObjectHandler::getInstance()->retrieveEntityField('simpleData', 'firstname[0]', 'test'), "#element ." . PersistedObjectHandler::getInstance()->retrieveEntityField('simpleData', 'firstname[0]', 'test')); // stepKey: see1ActionGroup6 + $I->see($I->retrieveEntityField('simpleData', 'firstname[0]', 'test'), "#element ." . $I->retrieveEntityField('simpleData', 'firstname[0]', 'test')); // stepKey: see1ActionGroup6 $I->comment("Exiting Action Group [actionGroup6] actionGroupWithEntityUsage"); $I->comment("Entering Action Group [actionGroup7] actionGroupWithEntityUsage"); - $I->see(PersistedObjectHandler::getInstance()->retrieveEntityField('simpleData', 'firstname[data_index]', 'test'), "#element ." . PersistedObjectHandler::getInstance()->retrieveEntityField('simpleData', 'firstname[data_index]', 'test')); // stepKey: see1ActionGroup7 + $I->see($I->retrieveEntityField('simpleData', 'firstname[data_index]', 'test'), "#element ." . $I->retrieveEntityField('simpleData', 'firstname[data_index]', 'test')); // stepKey: see1ActionGroup7 $I->comment("Exiting Action Group [actionGroup7] actionGroupWithEntityUsage"); } + + public function _passed(AcceptanceTester $I) + { + // Test passed successfully. + $this->isSuccess = true; + } } diff --git a/dev/tests/verification/Resources/ActionGroupWithSingleParameterSelectorFromDefaultArgument.txt b/dev/tests/verification/Resources/ActionGroupWithSingleParameterSelectorFromDefaultArgument.txt index af7af43a4..7aa0ba5a6 100644 --- a/dev/tests/verification/Resources/ActionGroupWithSingleParameterSelectorFromDefaultArgument.txt +++ b/dev/tests/verification/Resources/ActionGroupWithSingleParameterSelectorFromDefaultArgument.txt @@ -2,8 +2,6 @@ namespace Magento\AcceptanceTest\_default\Backend; use Magento\FunctionalTestingFramework\AcceptanceTester; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\CredentialStore; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\PersistedObjectHandler; use \Codeception\Util\Locator; use Yandex\Allure\Adapter\Annotation\Features; use Yandex\Allure\Adapter\Annotation\Stories; @@ -16,14 +14,29 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @Title("[NO TESTCASEID]: Action Group With Default Argument Value and Argument Value in Param") - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/ActionGroupTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/ActionGroupTest/ActionGroupWithSingleParameterSelectorFromDefaultArgument.xml<br>") */ class ActionGroupWithSingleParameterSelectorFromDefaultArgumentCest { + /** + * @var bool + */ + private $isSuccess = false; + + /** + * @param AcceptanceTester $I + * @throws \Exception + */ + public function _after(AcceptanceTester $I) + { + if ($this->isSuccess) { + unlink(__FILE__); + } + } + /** * @Severity(level = SeverityLevel::BLOCKER) * @Features({"TestModule"}) - * @Parameter(name = "AcceptanceTester", value="$I") * @param AcceptanceTester $I * @return void * @throws \Exception @@ -34,4 +47,10 @@ class ActionGroupWithSingleParameterSelectorFromDefaultArgumentCest $I->see("Doe", "#element .John"); // stepKey: seeLastNameActionGroup $I->comment("Exiting Action Group [actionGroup] actionGroupWithSingleParameterSelectorFromArgument"); } + + public function _passed(AcceptanceTester $I) + { + // Test passed successfully. + $this->isSuccess = true; + } } diff --git a/dev/tests/verification/Resources/ActionGroupWithSingleParameterSelectorFromPassedArgument.txt b/dev/tests/verification/Resources/ActionGroupWithSingleParameterSelectorFromPassedArgument.txt index 096623789..93fb74898 100644 --- a/dev/tests/verification/Resources/ActionGroupWithSingleParameterSelectorFromPassedArgument.txt +++ b/dev/tests/verification/Resources/ActionGroupWithSingleParameterSelectorFromPassedArgument.txt @@ -2,8 +2,6 @@ namespace Magento\AcceptanceTest\_default\Backend; use Magento\FunctionalTestingFramework\AcceptanceTester; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\CredentialStore; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\PersistedObjectHandler; use \Codeception\Util\Locator; use Yandex\Allure\Adapter\Annotation\Features; use Yandex\Allure\Adapter\Annotation\Stories; @@ -16,14 +14,29 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @Title("[NO TESTCASEID]: Action Group With Passed Argument Value and Argument Value in Param") - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/ActionGroupTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/ActionGroupTest/ActionGroupWithSingleParameterSelectorFromPassedArgument.xml<br>") */ class ActionGroupWithSingleParameterSelectorFromPassedArgumentCest { + /** + * @var bool + */ + private $isSuccess = false; + + /** + * @param AcceptanceTester $I + * @throws \Exception + */ + public function _after(AcceptanceTester $I) + { + if ($this->isSuccess) { + unlink(__FILE__); + } + } + /** * @Severity(level = SeverityLevel::BLOCKER) * @Features({"TestModule"}) - * @Parameter(name = "AcceptanceTester", value="$I") * @param AcceptanceTester $I * @return void * @throws \Exception @@ -34,4 +47,10 @@ class ActionGroupWithSingleParameterSelectorFromPassedArgumentCest $I->see("Doe", "#element .John" . msq("UniquePerson")); // stepKey: seeLastNameActionGroup $I->comment("Exiting Action Group [actionGroup] actionGroupWithSingleParameterSelectorFromArgument"); } + + public function _passed(AcceptanceTester $I) + { + // Test passed successfully. + $this->isSuccess = true; + } } diff --git a/dev/tests/verification/Resources/ActionGroupWithStepKeyReferences.txt b/dev/tests/verification/Resources/ActionGroupWithStepKeyReferences.txt index eddaaf784..0d62a57e3 100644 --- a/dev/tests/verification/Resources/ActionGroupWithStepKeyReferences.txt +++ b/dev/tests/verification/Resources/ActionGroupWithStepKeyReferences.txt @@ -2,8 +2,6 @@ namespace Magento\AcceptanceTest\_default\Backend; use Magento\FunctionalTestingFramework\AcceptanceTester; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\CredentialStore; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\PersistedObjectHandler; use \Codeception\Util\Locator; use Yandex\Allure\Adapter\Annotation\Features; use Yandex\Allure\Adapter\Annotation\Stories; @@ -15,13 +13,28 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/ActionGroupTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/ActionGroupTest/ActionGroupWithStepKeyReferences.xml<br>") */ class ActionGroupWithStepKeyReferencesCest { + /** + * @var bool + */ + private $isSuccess = false; + + /** + * @param AcceptanceTester $I + * @throws \Exception + */ + public function _after(AcceptanceTester $I) + { + if ($this->isSuccess) { + unlink(__FILE__); + } + } + /** * @Features({"TestModule"}) - * @Parameter(name = "AcceptanceTester", value="$I") * @param AcceptanceTester $I * @return void * @throws \Exception @@ -29,68 +42,38 @@ class ActionGroupWithStepKeyReferencesCest public function ActionGroupWithStepKeyReferences(AcceptanceTester $I) { $I->comment("Entering Action Group [actionGroup] FunctionActionGroupWithStepKeyReferences"); - $I->comment("[createSimpleDataActionGroup] create 'simpleData' entity"); - PersistedObjectHandler::getInstance()->createEntity( - "createSimpleDataActionGroup", - "test", - "simpleData", - [], - [] - ); - + $I->createEntity("createSimpleDataActionGroup", "test", "TestData", [], []); // stepKey: createSimpleDataActionGroup $grabTextDataActionGroup = $I->grabTextFrom(".class"); // stepKey: grabTextDataActionGroup - $I->fillField(".{$grabTextDataActionGroup}", PersistedObjectHandler::getInstance()->retrieveEntityField('createSimpleDataActionGroup', 'field', 'test')); // stepKey: fill1ActionGroup + $I->fillField(".{$grabTextDataActionGroup}", $I->retrieveEntityField('createSimpleDataActionGroup', 'field', 'test')); // stepKey: fill1ActionGroup $I->comment("Invocation stepKey will not be appended in non stepKey instances"); $I->click($action0); // stepKey: action0ActionGroup $I->fillField($action1); // stepKey: action1ActionGroup $I->comment("Invocation stepKey will be appended in non stepKey instances"); $action3ActionGroup = $I->executeJS($action3ActionGroup); // stepKey: action3ActionGroup - $action4ActionGroup = $I->magentoCLI($action4ActionGroup, "\"stuffHere\""); // stepKey: action4ActionGroup + $action4ActionGroup = $I->magentoCLI($action4ActionGroup, 60, "\"stuffHere\""); // stepKey: action4ActionGroup $I->comment($action4ActionGroup); $date = new \DateTime(); $date->setTimestamp(strtotime("{$action5}")); $date->setTimezone(new \DateTimeZone("America/Los_Angeles")); $action5ActionGroup = $date->format("H:i:s"); - $action6ActionGroup = $I->formatMoney($action6ActionGroup); // stepKey: action6ActionGroup - $I->comment("[action7ActionGroup] delete entity '{$action7ActionGroupActionGroup}'"); - PersistedObjectHandler::getInstance()->deleteEntity( - "{$action7ActionGroupActionGroup}", - "test" - ); - - $I->comment("[action8ActionGroup] get '{$action8}' entity"); - PersistedObjectHandler::getInstance()->getEntity( - "action8ActionGroup", - "test", - "{$action8}", - [], - null - ); - - $I->comment("[action9ActionGroup] update '1' entity to '{$action9}'"); - PersistedObjectHandler::getInstance()->updateEntity( - "1", - "test", - "{$action9}", - [] - ); - - $I->comment("[action10ActionGroup] create '{$action10}' entity"); - PersistedObjectHandler::getInstance()->createEntity( - "action10ActionGroup", - "test", - "{$action10}", - [], - [] - ); - + $action6ActionGroup = $I->formatCurrency($action6ActionGroup, "en_CA", "USD"); // stepKey: action6ActionGroup + $I->deleteEntity("{$action7ActionGroupActionGroup}", "test"); // stepKey: action7ActionGroup + $I->getEntity("action8ActionGroup", "test", "{$action8}", [], null); // stepKey: action8ActionGroup + $I->updateEntity("1", "test", "{$action9}",[]); // stepKey: action9ActionGroup $action11ActionGroup = $I->grabAttributeFrom($action11ActionGroup, "someInput"); // stepKey: action11ActionGroup $action12ActionGroup = $I->grabCookie($action12ActionGroup, ['domain' => 'www.google.com']); // stepKey: action12ActionGroup $action13ActionGroup = $I->grabFromCurrentUrl($action13ActionGroup); // stepKey: action13ActionGroup $action14ActionGroup = $I->grabMultiple($action14ActionGroup); // stepKey: action14ActionGroup $action15ActionGroup = $I->grabTextFrom($action15ActionGroup); // stepKey: action15ActionGroup $action16ActionGroup = $I->grabValueFrom($action16ActionGroup); // stepKey: action16ActionGroup + $action17ActionGroup = $I->grabCookieAttributes($action17ActionGroup, ['domain' => 'www.google.com']); // stepKey: action17ActionGroup $I->comment("Exiting Action Group [actionGroup] FunctionActionGroupWithStepKeyReferences"); } + + public function _passed(AcceptanceTester $I) + { + // Test passed successfully. + $this->isSuccess = true; + } } diff --git a/dev/tests/verification/Resources/ActionGroupWithTopLevelPersistedData.txt b/dev/tests/verification/Resources/ActionGroupWithTopLevelPersistedData.txt index ef1a5636c..4db67f901 100644 --- a/dev/tests/verification/Resources/ActionGroupWithTopLevelPersistedData.txt +++ b/dev/tests/verification/Resources/ActionGroupWithTopLevelPersistedData.txt @@ -2,8 +2,6 @@ namespace Magento\AcceptanceTest\_default\Backend; use Magento\FunctionalTestingFramework\AcceptanceTester; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\CredentialStore; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\PersistedObjectHandler; use \Codeception\Util\Locator; use Yandex\Allure\Adapter\Annotation\Features; use Yandex\Allure\Adapter\Annotation\Stories; @@ -16,29 +14,28 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @group functional - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/ActionGroupFunctionalTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/ActionGroupFunctionalTest/ActionGroupWithTopLevelPersistedData.xml<br>") */ class ActionGroupWithTopLevelPersistedDataCest { + /** + * @var bool + */ + private $isSuccess = false; + /** * @param AcceptanceTester $I * @throws \Exception */ public function _before(AcceptanceTester $I) { - $I->comment("[createPersonParam] create 'ReplacementPerson' entity"); - PersistedObjectHandler::getInstance()->createEntity( - "createPersonParam", - "hook", - "ReplacementPerson", - [], - [] - ); - + $I->comment('[START BEFORE HOOK]'); + $I->createEntity("createPersonParam", "hook", "ReplacementPerson", [], []); // stepKey: createPersonParam $I->comment("Entering Action Group [beforeGroup] FunctionalActionGroup"); $I->fillField("#foo", "myData1"); // stepKey: fillField1BeforeGroup $I->fillField("#bar", "myData2"); // stepKey: fillField2BeforeGroup $I->comment("Exiting Action Group [beforeGroup] FunctionalActionGroup"); + $I->comment('[END BEFORE HOOK]'); } /** @@ -47,10 +44,15 @@ class ActionGroupWithTopLevelPersistedDataCest */ public function _after(AcceptanceTester $I) { + $I->comment('[START AFTER HOOK]'); $I->comment("Entering Action Group [afterGroup] FunctionalActionGroup"); $I->fillField("#foo", "myData1"); // stepKey: fillField1AfterGroup $I->fillField("#bar", "myData2"); // stepKey: fillField2AfterGroup $I->comment("Exiting Action Group [afterGroup] FunctionalActionGroup"); + $I->comment('[END AFTER HOOK]'); + if ($this->isSuccess) { + unlink(__FILE__); + } } /** @@ -66,7 +68,6 @@ class ActionGroupWithTopLevelPersistedDataCest * @Severity(level = SeverityLevel::CRITICAL) * @Features({"TestModule"}) * @Stories({"MQE-433"}) - * @Parameter(name = "AcceptanceTester", value="$I") * @param AcceptanceTester $I * @return void * @throws \Exception @@ -74,11 +75,17 @@ class ActionGroupWithTopLevelPersistedDataCest public function ActionGroupWithTopLevelPersistedData(AcceptanceTester $I) { $I->comment("Entering Action Group [actionGroupWithPersistedData1] FunctionalActionGroupWithData"); - $I->amOnPage("/" . PersistedObjectHandler::getInstance()->retrieveEntityField('createPersonParam', 'firstname', 'test') . "/" . PersistedObjectHandler::getInstance()->retrieveEntityField('createPersonParam', 'lastname', 'test') . ".html"); // stepKey: amOnPage1ActionGroupWithPersistedData1 - $I->fillField("#foo", PersistedObjectHandler::getInstance()->retrieveEntityField('createPersonParam', 'firstname', 'test')); // stepKey: fillField1ActionGroupWithPersistedData1 - $I->fillField("#bar", PersistedObjectHandler::getInstance()->retrieveEntityField('createPersonParam', 'lastname', 'test')); // stepKey: fillField2ActionGroupWithPersistedData1 - $I->searchAndMultiSelectOption("#foo", [PersistedObjectHandler::getInstance()->retrieveEntityField('createPersonParam', 'firstname', 'test'), PersistedObjectHandler::getInstance()->retrieveEntityField('createPersonParam', 'lastname', 'test')]); // stepKey: multi1ActionGroupWithPersistedData1 - $I->see("#element ." . PersistedObjectHandler::getInstance()->retrieveEntityField('createPersonParam', 'firstname', 'test')); // stepKey: see1ActionGroupWithPersistedData1 + $I->amOnPage("/" . $I->retrieveEntityField('createPersonParam', 'firstname', 'test') . "/" . $I->retrieveEntityField('createPersonParam', 'lastname', 'test') . ".html"); // stepKey: amOnPage1ActionGroupWithPersistedData1 + $I->fillField("#foo", $I->retrieveEntityField('createPersonParam', 'firstname', 'test')); // stepKey: fillField1ActionGroupWithPersistedData1 + $I->fillField("#bar", $I->retrieveEntityField('createPersonParam', 'lastname', 'test')); // stepKey: fillField2ActionGroupWithPersistedData1 + $I->searchAndMultiSelectOption("#foo", [$I->retrieveEntityField('createPersonParam', 'firstname', 'test'), $I->retrieveEntityField('createPersonParam', 'lastname', 'test')]); // stepKey: multi1ActionGroupWithPersistedData1 + $I->see("#element ." . $I->retrieveEntityField('createPersonParam', 'firstname', 'test')); // stepKey: see1ActionGroupWithPersistedData1 $I->comment("Exiting Action Group [actionGroupWithPersistedData1] FunctionalActionGroupWithData"); } + + public function _passed(AcceptanceTester $I) + { + // Test passed successfully. + $this->isSuccess = true; + } } diff --git a/dev/tests/verification/Resources/ActionsInDifferentModulesSuite.txt b/dev/tests/verification/Resources/ActionsInDifferentModulesSuite.txt new file mode 100644 index 000000000..116eff358 --- /dev/null +++ b/dev/tests/verification/Resources/ActionsInDifferentModulesSuite.txt @@ -0,0 +1,244 @@ +<?php + +namespace Group; + +use Facebook\WebDriver\Remote\RemoteWebDriver; +use Magento\FunctionalTestingFramework\DataGenerator\Handlers\PersistedObjectHandler; +use Magento\FunctionalTestingFramework\DataGenerator\Handlers\CredentialStore; +use Magento\FunctionalTestingFramework\Module\MagentoWebDriver; +use Magento\FunctionalTestingFramework\Module\MagentoAssert; +use Magento\FunctionalTestingFramework\Module\MagentoActionProxies; +use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; +use Codeception\Lib\ModuleContainer; +use Codeception\Module; + +/** + * Group class is Codeception Extension which is allowed to handle to all internal events. + * This class itself can be used to listen events for test execution of one particular group. + * It may be especially useful to create fixtures data, prepare server, etc. + * + * INSTALLATION: + * + * To use this group extension, include it to "extensions" option of global Codeception config. + */ +class ActionsInDifferentModulesSuite extends \Codeception\GroupObject +{ + public static $group = 'ActionsInDifferentModulesSuite'; + private $testCount = 1; + private $preconditionFailure = null; + private $currentTestRun = 0; + private static $HOOK_EXECUTION_INIT = "\n/******** Beginning execution of ActionsInDifferentModulesSuite suite %s block ********/\n"; + private static $HOOK_EXECUTION_END = "\n/******** Execution of ActionsInDifferentModulesSuite suite %s block complete ********/\n"; + /** @var MagentoWebDriver */ + private $webDriver; + /** @var ModuleContainer */ + private $moduleContainer; + + public function _before(\Codeception\Event\TestEvent $e) + { + $this->webDriver = $this->getModule('\Magento\FunctionalTestingFramework\Module\MagentoWebDriver'); + $this->moduleContainer = $this->webDriver->getModuleContainer(); + // increment test count per execution + $this->currentTestRun++; + $this->executePreConditions(); + + if ($this->preconditionFailure != null) { + //if our preconditions fail, we need to mark all the tests as incomplete. + $e->getTest()->getMetadata()->setIncomplete("SUITE PRECONDITION FAILED:" . PHP_EOL . $this->preconditionFailure); + } + } + + private function executePreConditions() + { + if ($this->currentTestRun == 1) { + $this->testCount = $this->getTestCount(); + + print sprintf(self::$HOOK_EXECUTION_INIT, "before"); + + try { + if ($this->webDriver->webDriver != null) { + $this->webDriver->_restart(); + } else { + $this->webDriver->_initializeSession(); + } + $cli = $this->getModuleForAction("magentoCLISecret")->magentoCLISecret($this->getModuleForAction("getSecret")->getSecret("magento/some/secret"), 60); // stepKey: cli + print($cli); // stepKey: cli + $create1Fields['someKey'] = "dataHere"; + PersistedObjectHandler::getInstance()->createEntity( + "create1", + "suite", + "SecretData", + [], + $create1Fields + ); + PersistedObjectHandler::getInstance()->createEntity( + "create2", + "suite", + "SecretData", + [] + ); + PersistedObjectHandler::getInstance()->createEntity( + "create3", + "suite", + "SecretData", + ["create1", "create2"] + ); + $this->getModuleForAction("fillSecretField")->fillSecretField("#fill", $this->getModuleForAction("getSecret")->getSecret("magento/some/secret") . "+" . $this->getModuleForAction("getSecret")->getSecret("magento/some/secret")); // stepKey: fillBefore + $this->getModuleForAction("click")->click(PersistedObjectHandler::getInstance()->retrieveEntityField('create2', 'key2', 'suite')); // stepKey: click + print("Entering Action Group [return1] ActionGroupReturningValueActionGroup"); + $grabProducts1Return1 = $this->getModuleForAction("grabMultiple")->grabMultiple("selector"); // stepKey: grabProducts1Return1 + $this->getModuleForAction("assertCount")->assertCount(1, $grabProducts1Return1); // stepKey: assertCountReturn1 + $return1 = $this->getModuleForAction("return")->return($grabProducts1Return1); // stepKey: returnProducts1Return1 + print("Exiting Action Group [return1] ActionGroupReturningValueActionGroup"); + } catch (\Exception $exception) { + $this->preconditionFailure = $exception->getMessage(); + } + + // reset configuration and close session + $this->webDriver->_resetConfig(); + $this->webDriver->webDriver->close(); + $this->webDriver->webDriver = null; + + print sprintf(self::$HOOK_EXECUTION_END, "before"); + } + } + + public function _after(\Codeception\Event\TestEvent $e) + { + $this->executePostConditions($e); + } + + private function executePostConditions(\Codeception\Event\TestEvent $e) + { + if ($this->currentTestRun == $this->testCount) { + print sprintf(self::$HOOK_EXECUTION_INIT, "after"); + + try { + // Find out if Test in Suite failed, will cause potential failures in suite after + $cest = $e->getTest(); + + //Access private TestResultObject to find stack and if there are any errors (as opposed to failures) + $testResultObject = call_user_func(\Closure::bind( + function () use ($cest) { + return $cest->getTestResultObject(); + }, + $cest + )); + $errors = $testResultObject->errors(); + + if (!empty($errors)) { + foreach ($errors as $error) { + if ($error->failedTest()->getTestMethod() == $cest->getName()) { + // Do not attempt to run _after if failure was in the _after block + // Try to run _after but catch exceptions to prevent them from overwriting original failure. + print("LAST TEST IN SUITE FAILED, TEST AFTER MAY NOT BE SUCCESSFUL\n"); + } + } + } + if ($this->webDriver->webDriver != null) { + $this->webDriver->_restart(); + } else { + $this->webDriver->_initializeSession(); + } + print("Entering Action Group [return2] ExtendedActionGroupReturningValueActionGroup"); + $grabProducts1Return2 = $this->getModuleForAction("grabMultiple")->grabMultiple("selector"); // stepKey: grabProducts1Return2 + $this->getModuleForAction("assertCount")->assertCount(1, $grabProducts1Return2); // stepKey: assertCountReturn2 + $return2 = $this->getModuleForAction("return")->return($grabProducts1Return2); // stepKey: returnProducts1Return2 + $grabProducts2Return2 = $this->getModuleForAction("grabMultiple")->grabMultiple("otherSelector"); // stepKey: grabProducts2Return2 + $this->getModuleForAction("assertCount")->assertCount(2, $grabProducts2Return2); // stepKey: assertSecondCountReturn2 + $return2 = $this->getModuleForAction("return")->return($grabProducts2Return2); // stepKey: returnProducts2Return2 + print("Exiting Action Group [return2] ExtendedActionGroupReturningValueActionGroup"); + PersistedObjectHandler::getInstance()->deleteEntity( + "create1", + "suite" + ); + PersistedObjectHandler::getInstance()->deleteEntity( + "create2", + "suite" + ); + PersistedObjectHandler::getInstance()->deleteEntity( + "create3", + "suite" + ); + $this->getModuleForAction("deleteEntityByUrl")->deleteEntityByUrl("deleteThis"); // stepKey: deleteThis + $this->getModuleForAction("fillSecretField")->fillSecretField("#fill", $this->getModuleForAction("getSecret")->getSecret("magento/some/secret")); // stepKey: fillAfter + $cli2 = $this->getModuleForAction("magentoCLISecret")->magentoCLISecret($this->getModuleForAction("getSecret")->getSecret("magento/some/secret") . "-some/data-" . $this->getModuleForAction("getSecret")->getSecret("magento/some/secret"), 60); // stepKey: cli2 + print($cli2); // stepKey: cli2 + } catch (\Exception $exception) { + print $exception->getMessage(); + } + + PersistedObjectHandler::getInstance()->clearSuiteObjects(); + + $this->closeSession($this->webDriver); + + print sprintf(self::$HOOK_EXECUTION_END, "after"); + } + } + + /** + * Close session method closes current session. + * If config 'close_all_sessions' is set to 'true' all sessions will be closed. + * + * return void + */ + private function closeSession(): void + { + $webDriverConfig = $this->webDriver->_getConfig(); + $this->webDriver->_closeSession(); + if (isset($webDriverConfig['close_all_sessions']) && $webDriverConfig['close_all_sessions'] === "true") { + $wdHost = sprintf( + '%s://%s:%s%s', + $webDriverConfig['protocol'], + $webDriverConfig['host'], + $webDriverConfig['port'], + $webDriverConfig['path'] + ); + $availableSessions = RemoteWebDriver::getAllSessions($wdHost); + foreach ($availableSessions as $session) { + $remoteWebDriver = RemoteWebDriver::createBySessionID($session['id'], $wdHost); + $remoteWebDriver->quit(); + } + } + } + + /** + * Return the module for an action. + * + * @param string $action + * @return Module + * @throws \Exception + */ + private function getModuleForAction($action) + { + $module = $this->moduleContainer->moduleForAction($action); + if ($module === null) { + throw new TestFrameworkException('Invalid action "' . $action . '"' . PHP_EOL); + } + return $module; + } + + /** + * Counts how many tests in group. + * + * @return integer + */ + private function getTestCount() + { + $config = $this->getGlobalConfig(); + if (empty($config['groups']) || empty($config['groups'][self::$group])) { + return $this->testCount; + } + $pathToGroupDir = TESTS_BP . DIRECTORY_SEPARATOR . array_first($config['groups'][self::$group]); + $pathToGroupCests = $pathToGroupDir . "*Cest.php"; + + $files = glob($pathToGroupCests); + if (is_array($files)) { + $qty = count($files); + print('In a group "' . self::$group . '" suite executor found ' . $qty . ' tests.' . PHP_EOL); + return $qty; + } + + return $this->testCount; + } +} diff --git a/dev/tests/verification/Resources/ArgumentWithSameNameAsElement.txt b/dev/tests/verification/Resources/ArgumentWithSameNameAsElement.txt index 2da03b506..a5e24c0a1 100644 --- a/dev/tests/verification/Resources/ArgumentWithSameNameAsElement.txt +++ b/dev/tests/verification/Resources/ArgumentWithSameNameAsElement.txt @@ -2,8 +2,6 @@ namespace Magento\AcceptanceTest\_default\Backend; use Magento\FunctionalTestingFramework\AcceptanceTester; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\CredentialStore; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\PersistedObjectHandler; use \Codeception\Util\Locator; use Yandex\Allure\Adapter\Annotation\Features; use Yandex\Allure\Adapter\Annotation\Stories; @@ -16,29 +14,28 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @group functional - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/ActionGroupFunctionalTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/ActionGroupFunctionalTest/ArgumentWithSameNameAsElement.xml<br>") */ class ArgumentWithSameNameAsElementCest { + /** + * @var bool + */ + private $isSuccess = false; + /** * @param AcceptanceTester $I * @throws \Exception */ public function _before(AcceptanceTester $I) { - $I->comment("[createPersonParam] create 'ReplacementPerson' entity"); - PersistedObjectHandler::getInstance()->createEntity( - "createPersonParam", - "hook", - "ReplacementPerson", - [], - [] - ); - + $I->comment('[START BEFORE HOOK]'); + $I->createEntity("createPersonParam", "hook", "ReplacementPerson", [], []); // stepKey: createPersonParam $I->comment("Entering Action Group [beforeGroup] FunctionalActionGroup"); $I->fillField("#foo", "myData1"); // stepKey: fillField1BeforeGroup $I->fillField("#bar", "myData2"); // stepKey: fillField2BeforeGroup $I->comment("Exiting Action Group [beforeGroup] FunctionalActionGroup"); + $I->comment('[END BEFORE HOOK]'); } /** @@ -47,10 +44,15 @@ class ArgumentWithSameNameAsElementCest */ public function _after(AcceptanceTester $I) { + $I->comment('[START AFTER HOOK]'); $I->comment("Entering Action Group [afterGroup] FunctionalActionGroup"); $I->fillField("#foo", "myData1"); // stepKey: fillField1AfterGroup $I->fillField("#bar", "myData2"); // stepKey: fillField2AfterGroup $I->comment("Exiting Action Group [afterGroup] FunctionalActionGroup"); + $I->comment('[END AFTER HOOK]'); + if ($this->isSuccess) { + unlink(__FILE__); + } } /** @@ -66,7 +68,6 @@ class ArgumentWithSameNameAsElementCest * @Severity(level = SeverityLevel::CRITICAL) * @Features({"TestModule"}) * @Stories({"MQE-433"}) - * @Parameter(name = "AcceptanceTester", value="$I") * @param AcceptanceTester $I * @return void * @throws \Exception @@ -78,4 +79,10 @@ class ArgumentWithSameNameAsElementCest $I->seeElement("#element .John"); // stepKey: see2ActionGroup1 $I->comment("Exiting Action Group [actionGroup1] FunctionalActionGroupWithTrickyArgument"); } + + public function _passed(AcceptanceTester $I) + { + // Test passed successfully. + $this->isSuccess = true; + } } diff --git a/dev/tests/verification/Resources/AssertTest.txt b/dev/tests/verification/Resources/AssertTest.txt index 28c38bc0b..75c5266bd 100644 --- a/dev/tests/verification/Resources/AssertTest.txt +++ b/dev/tests/verification/Resources/AssertTest.txt @@ -2,8 +2,6 @@ namespace Magento\AcceptanceTest\_default\Backend; use Magento\FunctionalTestingFramework\AcceptanceTester; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\CredentialStore; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\PersistedObjectHandler; use \Codeception\Util\Locator; use Yandex\Allure\Adapter\Annotation\Features; use Yandex\Allure\Adapter\Annotation\Stories; @@ -15,45 +13,46 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/AssertTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/AssertTest.xml<br>") */ class AssertTestCest { + /** + * @var bool + */ + private $isSuccess = false; + /** * @param AcceptanceTester $I * @throws \Exception */ public function _before(AcceptanceTester $I) { - $I->comment("[createData1] create 'ReplacementPerson' entity"); - PersistedObjectHandler::getInstance()->createEntity( - "createData1", - "hook", - "ReplacementPerson", - [], - [] - ); + $I->comment('[START BEFORE HOOK]'); + $I->createEntity("createData1", "hook", "ReplacementPerson", [], []); // stepKey: createData1 + $I->comment('[END BEFORE HOOK]'); + } + /** + * @param AcceptanceTester $I + * @throws \Exception + */ + public function _after(AcceptanceTester $I) + { + if ($this->isSuccess) { + unlink(__FILE__); + } } /** * @Features({"TestModule"}) - * @Parameter(name = "AcceptanceTester", value="$I") * @param AcceptanceTester $I * @return void * @throws \Exception */ public function AssertTest(AcceptanceTester $I) { - $I->comment("[createData2] create 'UniquePerson' entity"); - PersistedObjectHandler::getInstance()->createEntity( - "createData2", - "test", - "UniquePerson", - [], - [] - ); - + $I->createEntity("createData2", "test", "UniquePerson", [], []); // stepKey: createData2 $grabTextFrom1 = $I->grabTextFrom(".copyright>span"); // stepKey: grabTextFrom1 $I->comment("custom asserts"); $I->assertArrayIsSorted(["1", "2", "3", "4", "5"], "asc"); // stepKey: assertSorted1 @@ -61,29 +60,29 @@ class AssertTestCest $I->comment("asserts without variable replacement"); $I->assertArrayHasKey("apple", ['orange' => 2, 'apple' => 1], "pass"); // stepKey: assertArrayHasKey $I->assertArrayNotHasKey("kiwi", ['orange' => 2, 'apple' => 1], "pass"); // stepKey: assertArrayNotHasKey - $I->assertArraySubset([1, 2], [1, 2, 3, 5], "pass"); // stepKey: assertArraySubset $I->assertContains("ab", ['item1' => 'a', 'item2' => 'ab'], "pass"); // stepKey: assertContains + $I->assertStringContainsString("apple", "apple", "pass"); // stepKey: assertStringContainsString + $I->assertStringContainsStringIgnoringCase("Banana", "banana", "pass"); // stepKey: assertStringContainsStringIgnoringCase $I->assertCount(2, ['a', 'b'], "pass"); // stepKey: assertCount $I->assertEmpty([], "pass"); // stepKey: assertEmpty $I->assertEquals($text, "Copyright © 2013-2017 Magento, Inc. All rights reserved.", "pass"); // stepKey: assertEquals1 $I->assertEquals("Copyright © 2013-2017 Magento, Inc. All rights reserved.", $text, "pass"); // stepKey: assertEquals2 + $I->assertEquals(1.5, $text, "pass"); // stepKey: assertFloatTypeIsCorrect $I->assertFalse(false, "pass"); // stepKey: assertFalse1 $I->assertFileNotExists("/out.txt", "pass"); // stepKey: assertFileNotExists1 $I->assertFileNotExists($text, "pass"); // stepKey: assertFileNotExists2 $I->assertGreaterOrEquals(2, 5, "pass"); // stepKey: assertGreaterOrEquals $I->assertGreaterThan(2, 5, "pass"); // stepKey: assertGreaterthan $I->assertGreaterThanOrEqual(2, 5, "pass"); // stepKey: assertGreaterThanOrEqual - $I->assertInternalType("string", "xyz", "pass"); // stepKey: assertInternalType1 - $I->assertInternalType("int", 21, "pass"); // stepKey: assertInternalType2 - $I->assertInternalType("string", $text, "pass"); // stepKey: assertInternalType3 $I->assertLessOrEquals(5, 2, "pass"); // stepKey: assertLessOrEquals $I->assertLessThan(5, 2, "pass"); // stepKey: assertLessThan $I->assertLessThanOrEqual(5, 2, "pass"); // stepKey: assertLessThanOrEquals - $I->assertNotContains("bc", ['item1' => 'a', 'item2' => 'ab'], "pass"); // stepKey: assertNotContains1 - $I->assertNotContains("bc", $text, "pass"); // stepKey: assertNotContains2 + $I->assertNotContains("bc", ['item1' => 'a', 'item2' => 'ab'], "pass"); // stepKey: assertNotContains + $I->assertStringNotContainsString("apple", "banana", "pass"); // stepKey: assertStringNotContainsString + $I->assertStringNotContainsStringIgnoringCase("apple", "banana", "pass"); // stepKey: assertStringNotContainsStringIgnoringCase $I->assertNotEmpty([1, 2], "pass"); // stepKey: assertNotEmpty1 $I->assertNotEmpty($text, "pass"); // stepKey: assertNotEmpty2 - $I->assertNotEquals(2, 5, "pass", 0); // stepKey: assertNotEquals + $I->assertNotEquals(2, 5, "pass"); // stepKey: assertNotEquals $I->assertNotNull("abc", "pass"); // stepKey: assertNotNull1 $I->assertNotNull($text, "pass"); // stepKey: assertNotNull2 $I->assertNotRegExp("/foo/", "bar", "pass"); // stepKey: assertNotRegExp @@ -97,7 +96,6 @@ class AssertTestCest $I->comment("asserts backward compatible"); $I->assertArrayHasKey("apple", ['orange' => 2, 'apple' => 1], "pass"); // stepKey: assertArrayHasKeyBackwardCompatible $I->assertArrayNotHasKey("kiwi", ['orange' => 2, 'apple' => 1], "pass"); // stepKey: assertArrayNotHasKeyBackwardCompatible - $I->assertArraySubset([1, 2], [1, 2, 3, 5], "pass"); // stepKey: assertArraySubsetBackwardCompatible $I->assertContains("ab", ['item1' => 'a', 'item2' => 'ab'], "pass"); // stepKey: assertContainsBackwardCompatible $I->assertCount(2, ['a', 'b'], "pass"); // stepKey: assertCountBackwardCompatible $I->assertEmpty([], "pass"); // stepKey: assertEmptyBackwardCompatible @@ -109,9 +107,6 @@ class AssertTestCest $I->assertGreaterOrEquals(2, 5, "pass"); // stepKey: assertGreaterOrEqualsBackwardCompatible $I->assertGreaterThan(2, 5, "pass"); // stepKey: assertGreaterThanBackwardCompatible $I->assertGreaterThanOrEqual(2, 5, "pass"); // stepKey: assertGreaterThanOrEqualBackwardCompatible - $I->assertInternalType("string", "xyz", "pass"); // stepKey: assertInternalType1BackwardCompatible - $I->assertInternalType("int", 21, "pass"); // stepKey: assertInternalType2BackwardCompatible - $I->assertInternalType("string", $text, "pass"); // stepKey: assertInternalType3BackwardCompatible $I->assertLessOrEquals(5, 2, "pass"); // stepKey: assertLessOrEqualBackwardCompatibles $I->assertLessThan(5, 2, "pass"); // stepKey: assertLessThanBackwardCompatible $I->assertLessThanOrEqual(5, 2, "pass"); // stepKey: assertLessThanOrEqualBackwardCompatible @@ -119,7 +114,7 @@ class AssertTestCest $I->assertNotContains("bc", $text, "pass"); // stepKey: assertNotContains2BackwardCompatible $I->assertNotEmpty([1, 2], "pass"); // stepKey: assertNotEmpty1BackwardCompatible $I->assertNotEmpty($text, "pass"); // stepKey: assertNotEmpty2BackwardCompatible - $I->assertNotEquals(2, 5, "pass", 0); // stepKey: assertNotEqualsBackwardCompatible + $I->assertNotEquals(2, 5, "pass"); // stepKey: assertNotEqualsBackwardCompatible $I->assertNotNull("abc", "pass"); // stepKey: assertNotNull1BackwardCompatible $I->assertNotNull($text, "pass"); // stepKey: assertNotNull2BackwardCompatible $I->assertNotRegExp("/foo/", "bar", "pass"); // stepKey: assertNotRegExpBackwardCompatible @@ -138,15 +133,13 @@ class AssertTestCest $I->expectException(new MyException('exception msg'), function() {$this->doSomethingBad();}); // stepKey: expectExceptionBackwardCompatible $I->comment("string type that use created data"); $I->comment("string type that use created data"); - $I->assertStringStartsWith("D", PersistedObjectHandler::getInstance()->retrieveEntityField('createData1', 'lastname', 'test') . ", " . PersistedObjectHandler::getInstance()->retrieveEntityField('createData1', 'firstname', 'test'), "fail"); // stepKey: assert1 - $I->assertStringStartsNotWith("W", PersistedObjectHandler::getInstance()->retrieveEntityField('createData2', 'firstname', 'test') . ", " . PersistedObjectHandler::getInstance()->retrieveEntityField('createData2', 'lastname', 'test'), "pass"); // stepKey: assert2 - $I->assertEquals(PersistedObjectHandler::getInstance()->retrieveEntityField('createData1', 'lastname', 'test'), PersistedObjectHandler::getInstance()->retrieveEntityField('createData1', 'lastname', 'test'), "pass"); // stepKey: assert5 + $I->assertStringStartsWith("D", $I->retrieveEntityField('createData1', 'lastname', 'test') . ", " . $I->retrieveEntityField('createData1', 'firstname', 'test'), "fail"); // stepKey: assert1 + $I->assertStringStartsNotWith("W", $I->retrieveEntityField('createData2', 'firstname', 'test') . ", " . $I->retrieveEntityField('createData2', 'lastname', 'test'), "pass"); // stepKey: assert2 + $I->assertEquals($I->retrieveEntityField('createData1', 'lastname', 'test'), $I->retrieveEntityField('createData1', 'lastname', 'test'), "pass"); // stepKey: assert5 $I->comment("array type that use created data"); $I->comment("array type that use created data"); - $I->assertArraySubset([PersistedObjectHandler::getInstance()->retrieveEntityField('createData1', 'lastname', 'test'), PersistedObjectHandler::getInstance()->retrieveEntityField('createData1', 'firstname', 'test')], [PersistedObjectHandler::getInstance()->retrieveEntityField('createData1', 'lastname', 'test'), PersistedObjectHandler::getInstance()->retrieveEntityField('createData1', 'firstname', 'test'), "1"], "pass"); // stepKey: assert9 - $I->assertArraySubset([PersistedObjectHandler::getInstance()->retrieveEntityField('createData2', 'firstname', 'test'), PersistedObjectHandler::getInstance()->retrieveEntityField('createData2', 'lastname', 'test')], [PersistedObjectHandler::getInstance()->retrieveEntityField('createData2', 'firstname', 'test'), PersistedObjectHandler::getInstance()->retrieveEntityField('createData2', 'lastname', 'test'), "1"], "pass"); // stepKey: assert10 - $I->assertArrayHasKey("lastname", ['lastname' => PersistedObjectHandler::getInstance()->retrieveEntityField('createData1', 'lastname', 'test'), 'firstname' => PersistedObjectHandler::getInstance()->retrieveEntityField('createData1', 'firstname', 'test')], "pass"); // stepKey: assert3 - $I->assertArrayHasKey("lastname", ['lastname' => PersistedObjectHandler::getInstance()->retrieveEntityField('createData2', 'lastname', 'test'), 'firstname' => PersistedObjectHandler::getInstance()->retrieveEntityField('createData2', 'firstname', 'test')], "pass"); // stepKey: assert4 + $I->assertArrayHasKey("lastname", ['lastname' => $I->retrieveEntityField('createData1', 'lastname', 'test'), 'firstname' => $I->retrieveEntityField('createData1', 'firstname', 'test')], "pass"); // stepKey: assert3 + $I->assertArrayHasKey("lastname", ['lastname' => $I->retrieveEntityField('createData2', 'lastname', 'test'), 'firstname' => $I->retrieveEntityField('createData2', 'firstname', 'test')], "pass"); // stepKey: assert4 $I->comment("this section can only be generated and cannot run"); $I->assertInstanceOf(User::class, $text, "pass"); // stepKey: assertInstanceOf $I->assertNotInstanceOf(User::class, 21, "pass"); // stepKey: assertNotInstanceOf @@ -156,8 +149,8 @@ class AssertTestCest $I->assertNull($text, "pass"); // stepKey: assertNull $I->expectException(new MyException('exception msg'), function() {$this->doSomethingBad();}); // stepKey: expectException $I->fail("fail"); // stepKey: fail - $I->fail(PersistedObjectHandler::getInstance()->retrieveEntityField('createData2', 'firstname', 'test') . " " . PersistedObjectHandler::getInstance()->retrieveEntityField('createData2', 'lastname', 'test')); // stepKey: assert7 - $I->fail(PersistedObjectHandler::getInstance()->retrieveEntityField('createData1', 'firstname', 'test') . " " . PersistedObjectHandler::getInstance()->retrieveEntityField('createData1', 'lastname', 'test')); // stepKey: assert8 + $I->fail($I->retrieveEntityField('createData2', 'firstname', 'test') . " " . $I->retrieveEntityField('createData2', 'lastname', 'test')); // stepKey: assert7 + $I->fail($I->retrieveEntityField('createData1', 'firstname', 'test') . " " . $I->retrieveEntityField('createData1', 'lastname', 'test')); // stepKey: assert8 $I->comment("assertElementContainsAttribute examples"); $I->assertElementContainsAttribute("#username", "class", "admin__control-text"); // stepKey: assertElementContainsAttribute1 $I->assertElementContainsAttribute("#username", "name", "login[username]"); // stepKey: assertElementContainsAttribute2 @@ -165,9 +158,29 @@ class AssertTestCest $I->assertElementContainsAttribute("#username", "data-validate", "{required:true}"); // stepKey: assertElementContainsAttribute4 $I->assertElementContainsAttribute(".admin__menu-overlay", "style", "display: none;"); // stepKey: assertElementContainsAttribute5 $I->assertElementContainsAttribute(".admin__menu-overlay", "border", "0"); // stepKey: assertElementContainsAttribute6 - $I->assertElementContainsAttribute("#username", "value", PersistedObjectHandler::getInstance()->retrieveEntityField('createData2', 'firstname', 'test')); // stepKey: assertElementContainsAttribute7 - $I->assertElementContainsAttribute("#username", "value", PersistedObjectHandler::getInstance()->retrieveEntityField('createData1', 'firstname', 'test')); // stepKey: assertElementContainsAttribute8 + $I->assertElementContainsAttribute("#username", "value", $I->retrieveEntityField('createData2', 'firstname', 'test')); // stepKey: assertElementContainsAttribute7 + $I->assertElementContainsAttribute("#username", "value", $I->retrieveEntityField('createData1', 'firstname', 'test')); // stepKey: assertElementContainsAttribute8 $I->comment("assert entity resolution"); $I->assertEquals("John", "Doe", "pass"); // stepKey: assertEqualsEntity + $I->assertEqualsWithDelta(10.0000, 10.0000, 1, "pass"); // stepKey: a1 + $I->assertNotEqualsWithDelta(10.0000, 12.0000, 1, "pass"); // stepKey: a2 + $I->assertEqualsCanonicalizing(["4", "2", "1", "3"], ["1", "2", "3", "4"], "pass"); // stepKey: a3 + $I->assertNotEqualsCanonicalizing(["5", "8", "7", "9"], ["1", "2", "3", "4"], "pass"); // stepKey: a4 + $I->assertEqualsIgnoringCase("Cat", "cat", "pass"); // stepKey: a5 + $I->assertNotEqualsIgnoringCase("Cat", "Dog", "pass"); // stepKey: a6 + $I->comment("assertions.md examples"); + $I->assertElementContainsAttribute(".admin__menu-overlay", "style", "color: #333;"); // stepKey: assertElementContainsAttribute + $I->assertStringContainsString("Buy 5 for $5.00 each and save 50%", $DropDownTierPriceTextProduct1); // stepKey: assertDropDownTierPriceTextProduct1 + $I->assertEmpty("$grabSearchButtonAttribute"); // stepKey: assertSearchButtonEnabled + $I->assertGreaterThanOrEqual($getOrderStatusFirstRow, $getOrderStatusSecondRow); // stepKey: checkStatusSortOrderAsc + $I->assertNotEquals($grabTotalBefore, $grabTotalAfter); // stepKey: assertNotEqualsExample + $I->assertNotRegExp('/placeholder\/thumbnail\.jpg/', $getSimpleProductThumbnail); // stepKey: simpleThumbnailIsNotDefault + $I->assertRegExp("#var\s+adminAnalyticsMetadata\s+=\s+{\s+(\"[\w_]+\":\s+\"[^\"]*?\",\s+)*?(\"[\w_]+\":\s+\"[^\"]*?\"\s+)};#s", $pageSource, "adminAnalyticsMetadata object is invalid"); // stepKey: validateadminAnalyticsMetadata + } + + public function _passed(AcceptanceTester $I) + { + // Test passed successfully. + $this->isSuccess = true; } } diff --git a/dev/tests/verification/Resources/BasicActionGroupTest.txt b/dev/tests/verification/Resources/BasicActionGroupTest.txt index 4bb9c1a95..5412afa70 100644 --- a/dev/tests/verification/Resources/BasicActionGroupTest.txt +++ b/dev/tests/verification/Resources/BasicActionGroupTest.txt @@ -2,8 +2,6 @@ namespace Magento\AcceptanceTest\_default\Backend; use Magento\FunctionalTestingFramework\AcceptanceTester; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\CredentialStore; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\PersistedObjectHandler; use \Codeception\Util\Locator; use Yandex\Allure\Adapter\Annotation\Features; use Yandex\Allure\Adapter\Annotation\Stories; @@ -16,36 +14,45 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @group functional - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/ActionGroupFunctionalTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/ActionGroupFunctionalTest/BasicActionGroupTest.xml<br>") */ class BasicActionGroupTestCest { + /** + * @var bool + */ + private $isSuccess = false; + /** * @param AcceptanceTester $I * @throws \Exception */ public function _before(AcceptanceTester $I) { - $I->comment("[createPersonParam] create 'ReplacementPerson' entity"); - PersistedObjectHandler::getInstance()->createEntity( - "createPersonParam", - "hook", - "ReplacementPerson", - [], - [] - ); - + $I->comment('[START BEFORE HOOK]'); + $I->createEntity("createPersonParam", "hook", "ReplacementPerson", [], []); // stepKey: createPersonParam $I->comment("Entering Action Group [beforeGroup] FunctionalActionGroup"); $I->fillField("#foo", "myData1"); // stepKey: fillField1BeforeGroup $I->fillField("#bar", "myData2"); // stepKey: fillField2BeforeGroup $I->comment("Exiting Action Group [beforeGroup] FunctionalActionGroup"); + $I->comment('[END BEFORE HOOK]'); + } + + /** + * @param AcceptanceTester $I + * @throws \Exception + */ + public function _after(AcceptanceTester $I) + { + if ($this->isSuccess) { + unlink(__FILE__); + } } /** * @Severity(level = SeverityLevel::CRITICAL) * @Features({"TestModule"}) * @Stories({"MQE-433"}) - * @Parameter(name = "AcceptanceTester", value="$I") * @param AcceptanceTester $I * @return void * @throws \Exception @@ -59,4 +66,10 @@ class BasicActionGroupTestCest $I->comment("Exiting Action Group [actionGroup1] FunctionalActionGroup"); $I->click("loginButton"); // stepKey: step6 } + + public function _passed(AcceptanceTester $I) + { + // Test passed successfully. + $this->isSuccess = true; + } } diff --git a/dev/tests/verification/Resources/BasicFunctionalTest.txt b/dev/tests/verification/Resources/BasicFunctionalTest.txt index f82c84119..d4631b706 100644 --- a/dev/tests/verification/Resources/BasicFunctionalTest.txt +++ b/dev/tests/verification/Resources/BasicFunctionalTest.txt @@ -2,8 +2,6 @@ namespace Magento\AcceptanceTest\_default\Backend; use Magento\FunctionalTestingFramework\AcceptanceTester; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\CredentialStore; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\PersistedObjectHandler; use \Codeception\Util\Locator; use Yandex\Allure\Adapter\Annotation\Features; use Yandex\Allure\Adapter\Annotation\Stories; @@ -17,17 +15,24 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @Title("[NO TESTCASEID]: A Functional Cest") * @group functional - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/BasicFunctionalTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/BasicFunctionalTest/BasicFunctionalTest.xml<br>") */ class BasicFunctionalTestCest { + /** + * @var bool + */ + private $isSuccess = false; + /** * @param AcceptanceTester $I * @throws \Exception */ public function _before(AcceptanceTester $I) { + $I->comment('[START BEFORE HOOK]'); $I->amOnPage("/beforeUrl"); // stepKey: beforeAmOnPageKey + $I->comment('[END BEFORE HOOK]'); } /** @@ -36,7 +41,12 @@ class BasicFunctionalTestCest */ public function _after(AcceptanceTester $I) { + $I->comment('[START AFTER HOOK]'); $I->amOnPage("/afterUrl"); // stepKey: afterAmOnPageKey + $I->comment('[END AFTER HOOK]'); + if ($this->isSuccess) { + unlink(__FILE__); + } } /** @@ -52,7 +62,6 @@ class BasicFunctionalTestCest * @Severity(level = SeverityLevel::CRITICAL) * @Features({"TestModule"}) * @Stories({"MQE-305"}) - * @Parameter(name = "AcceptanceTester", value="$I") * @param AcceptanceTester $I * @return void * @throws \Exception @@ -76,14 +85,8 @@ class BasicFunctionalTestCest $I->clickWithRightButton("#element .4123#element", 200, 300); // stepKey: clickWithRightButtonKeyXY1 $I->closeTab(); // stepKey: closeTabKey1 $I->conditionalClick(".functionalTestSelector", ".functionalDependentTestSelector", true); // stepKey: conditionalClickKey1 - $I->comment("[deleteKey1] delete entity 'createKey1'"); - PersistedObjectHandler::getInstance()->deleteEntity( - "createKey1", - "test" - ); - - $I->deleteEntityByUrl("/V1/categories{$grabbedData}"); - + $I->deleteEntity("createKey1", "test"); // stepKey: deleteKey1 + $I->deleteEntityByUrl("/V1/categories{$grabbedData}"); // stepKey: deleteKey2 $I->dontSee("someInput", ".functionalTestSelector"); // stepKey: dontSeeKey1 $I->dontSeeCheckboxIsChecked(".functionalTestSelector"); // stepKey: dontSeeCheckboxIsCheckedKey1 $I->dontSeeCookie("someInput"); // stepKey: dontSeeCookieKey1 @@ -120,22 +123,36 @@ class BasicFunctionalTestCest $date->setTimezone(new \DateTimeZone("UTC")); $generateDateKey2 = $date->format("H:i:s"); + $getOtp = $I->getOTP(); // stepKey: getOtp + $getOtpWithInput = $I->getOTP("someInput"); // stepKey: getOtpWithInput $grabAttributeFromKey1 = $I->grabAttributeFrom(".functionalTestSelector", "someInput"); // stepKey: grabAttributeFromKey1 $grabCookieKey1 = $I->grabCookie("grabCookieInput", ['domain' => 'www.google.com']); // stepKey: grabCookieKey1 + $grabCookieAttributesKey1 = $I->grabCookieAttributes("grabCookieAttributesInput", ['domain' => 'www.google.com']); // stepKey: grabCookieAttributesKey1 $grabFromCurrentUrlKey1 = $I->grabFromCurrentUrl("/grabCurrentUrl"); // stepKey: grabFromCurrentUrlKey1 $grabMultipleKey1 = $I->grabMultiple(".functionalTestSelector"); // stepKey: grabMultipleKey1 $grabTextFromKey1 = $I->grabTextFrom(".functionalTestSelector"); // stepKey: grabTextFromKey1 $grabValueFromKey1 = $I->grabValueFrom(".functionalTestSelector"); // stepKey: grabValueFromKey1 - $magentoCli1 = $I->magentoCLI("maintenance:enable", "\"stuffHere\""); // stepKey: magentoCli1 + $magentoCli1 = $I->magentoCLI("maintenance:enable", 60, "\"stuffHere\""); // stepKey: magentoCli1 $I->comment($magentoCli1); + $magentoCli2 = $I->magentoCLI("maintenance:enable", 120, "\"stuffHere\""); // stepKey: magentoCli2 + $I->comment($magentoCli2); + $magentoCli3 = $I->magentoCLISecret("config:set somePath " . $I->getSecret("someKey"), 60); // stepKey: magentoCli3 + $I->comment($magentoCli3); // stepKey: magentoCli3 + $magentoCli4 = $I->magentoCLISecret("config:set somePath " . $I->getSecret("someKey"), 120); // stepKey: magentoCli4 + $I->comment($magentoCli4); // stepKey: magentoCli4 + $cronAllGroups = $I->magentoCron("", 70); // stepKey: cronAllGroups + $I->comment($cronAllGroups); + $cronSingleGroup = $I->magentoCron("index", 70); // stepKey: cronSingleGroup + $I->comment($cronSingleGroup); + $cronMultipleGroups = $I->magentoCron("a b c", 70); // stepKey: cronMultipleGroups + $I->comment($cronMultipleGroups); $I->makeScreenshot("screenShotInput"); // stepKey: makeScreenshotKey1 $I->maximizeWindow(); // stepKey: maximizeWindowKey1 $I->moveBack(); // stepKey: moveBackKey1 $I->moveForward(); // stepKey: moveForwardKey1 $I->moveMouseOver(".functionalTestSelector"); // stepKey: moveMouseOverKey1 $I->openNewTab(); // stepKey: openNewTabKey1 - $I->pauseExecution(); // stepKey: pauseExecutionKey1 - $I->performOn("#selector", function(\WebDriverElement $el) {return $el->isDisplayed();}, 10); // stepKey: performOnKey1 + $I->pause(true); // stepKey: pauseKey1 $I->pressKey("#page", "a"); // stepKey: pressKey1 $I->pressKey("#page", ['ctrl', 'a'],'new'); // stepKey: pressKey2 $I->pressKey("#page", ['shift', '111'],'1','x'); // stepKey: pressKey3 @@ -180,4 +197,10 @@ class BasicFunctionalTestCest $I->waitForJS("someJsFunction", 30); // stepKey: waitForJSKey1 $I->waitForText("someInput", 30, ".functionalTestSelector"); // stepKey: waitForText1 } + + public function _passed(AcceptanceTester $I) + { + // Test passed successfully. + $this->isSuccess = true; + } } diff --git a/dev/tests/verification/Resources/BasicMergeTest.txt b/dev/tests/verification/Resources/BasicMergeTest.txt index 5421efe33..df625aa57 100644 --- a/dev/tests/verification/Resources/BasicMergeTest.txt +++ b/dev/tests/verification/Resources/BasicMergeTest.txt @@ -2,8 +2,6 @@ namespace Magento\AcceptanceTest\_default\Backend; use Magento\FunctionalTestingFramework\AcceptanceTester; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\CredentialStore; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\PersistedObjectHandler; use \Codeception\Util\Locator; use Yandex\Allure\Adapter\Annotation\Features; use Yandex\Allure\Adapter\Annotation\Stories; @@ -18,18 +16,25 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; * @Title("[NO TESTCASEID]: BasicMergeTest") * @group functional * @group mergeTest - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/MergeFunctionalTest.xml<br>verification/TestModuleMerged/Test/MergeFunctionalTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/MergeFunctionalTest/BasicMergeTest.xml<br>verification/TestModuleMerged/Test/MergeFunctionalTest/BasicMergeTest.xml<br>") */ class BasicMergeTestCest { + /** + * @var bool + */ + private $isSuccess = false; + /** * @param AcceptanceTester $I * @throws \Exception */ public function _before(AcceptanceTester $I) { + $I->comment('[START BEFORE HOOK]'); $I->amOnPage("/beforeUrl"); // stepKey: before1 $I->see("#before2"); // stepKey: before2 + $I->comment('[END BEFORE HOOK]'); } /** @@ -38,7 +43,12 @@ class BasicMergeTestCest */ public function _after(AcceptanceTester $I) { + $I->comment('[START AFTER HOOK]'); $I->amOnPage("/afterUrl1"); // stepKey: after1 + $I->comment('[END AFTER HOOK]'); + if ($this->isSuccess) { + unlink(__FILE__); + } } /** @@ -54,7 +64,6 @@ class BasicMergeTestCest * @Severity(level = SeverityLevel::CRITICAL) * @Features({"TestModule"}) * @Stories({"MQE-433"}) - * @Parameter(name = "AcceptanceTester", value="$I") * @param AcceptanceTester $I * @return void * @throws \Exception @@ -77,4 +86,10 @@ class BasicMergeTestCest $I->comment("Exiting Action Group [step8Merge] FunctionalActionGroupWithData"); $I->click("#step10MergedInResult"); // stepKey: step10 } + + public function _passed(AcceptanceTester $I) + { + // Test passed successfully. + $this->isSuccess = true; + } } diff --git a/dev/tests/verification/Resources/CharacterReplacementTest.txt b/dev/tests/verification/Resources/CharacterReplacementTest.txt index db3852b17..bf10c94fc 100644 --- a/dev/tests/verification/Resources/CharacterReplacementTest.txt +++ b/dev/tests/verification/Resources/CharacterReplacementTest.txt @@ -2,8 +2,6 @@ namespace Magento\AcceptanceTest\_default\Backend; use Magento\FunctionalTestingFramework\AcceptanceTester; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\CredentialStore; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\PersistedObjectHandler; use \Codeception\Util\Locator; use Yandex\Allure\Adapter\Annotation\Features; use Yandex\Allure\Adapter\Annotation\Stories; @@ -15,13 +13,28 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/CharacterReplacementTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/CharacterReplacementTest.xml<br>") */ class CharacterReplacementTestCest { + /** + * @var bool + */ + private $isSuccess = false; + + /** + * @param AcceptanceTester $I + * @throws \Exception + */ + public function _after(AcceptanceTester $I) + { + if ($this->isSuccess) { + unlink(__FILE__); + } + } + /** * @Features({"TestModule"}) - * @Parameter(name = "AcceptanceTester", value="$I") * @param AcceptanceTester $I * @return void * @throws \Exception @@ -37,4 +50,10 @@ class CharacterReplacementTestCest $I->click("#`~!@#$%^&*()-_=+{}[]|\;:\".,></?() .`~!@#$%^&*()-_=+{}[]|\;:\".,></?()"); // stepKey: allChars5 $I->click("#words, and, commas, and, spaces .words, and, commas, and, spaces"); // stepKey: allChars6 } + + public function _passed(AcceptanceTester $I) + { + // Test passed successfully. + $this->isSuccess = true; + } } diff --git a/dev/tests/verification/Resources/ChildExtendedTestAddHooks.txt b/dev/tests/verification/Resources/ChildExtendedTestAddHooks.txt index e03501707..01fa652d8 100644 --- a/dev/tests/verification/Resources/ChildExtendedTestAddHooks.txt +++ b/dev/tests/verification/Resources/ChildExtendedTestAddHooks.txt @@ -2,8 +2,6 @@ namespace Magento\AcceptanceTest\_default\Backend; use Magento\FunctionalTestingFramework\AcceptanceTester; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\CredentialStore; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\PersistedObjectHandler; use \Codeception\Util\Locator; use Yandex\Allure\Adapter\Annotation\Features; use Yandex\Allure\Adapter\Annotation\Stories; @@ -17,17 +15,24 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @Title("[NO TESTCASEID]: ChildExtendedTestAddHooks") * @group Parent - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/ExtendedFunctionalTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/ExtendedFunctionalTest/ChildExtendedTestAddHooks.xml<br>") */ class ChildExtendedTestAddHooksCest { + /** + * @var bool + */ + private $isSuccess = false; + /** * @param AcceptanceTester $I * @throws \Exception */ public function _before(AcceptanceTester $I) { + $I->comment('[START BEFORE HOOK]'); $I->amOnPage("/beforeUrl"); // stepKey: beforeAmOnPageKey + $I->comment('[END BEFORE HOOK]'); } /** @@ -36,7 +41,12 @@ class ChildExtendedTestAddHooksCest */ public function _after(AcceptanceTester $I) { + $I->comment('[START AFTER HOOK]'); $I->amOnPage("/afterUrl"); // stepKey: afterAmOnPageKey + $I->comment('[END AFTER HOOK]'); + if ($this->isSuccess) { + unlink(__FILE__); + } } /** @@ -52,7 +62,6 @@ class ChildExtendedTestAddHooksCest * @Severity(level = SeverityLevel::MINOR) * @Features({"TestModule"}) * @Stories({"Parent"}) - * @Parameter(name = "AcceptanceTester", value="$I") * @param AcceptanceTester $I * @return void * @throws \Exception @@ -60,4 +69,10 @@ class ChildExtendedTestAddHooksCest public function ChildExtendedTestAddHooks(AcceptanceTester $I) { } + + public function _passed(AcceptanceTester $I) + { + // Test passed successfully. + $this->isSuccess = true; + } } diff --git a/dev/tests/verification/Resources/ChildExtendedTestMerging.txt b/dev/tests/verification/Resources/ChildExtendedTestMerging.txt index a99843b48..8b78c5a5e 100644 --- a/dev/tests/verification/Resources/ChildExtendedTestMerging.txt +++ b/dev/tests/verification/Resources/ChildExtendedTestMerging.txt @@ -2,8 +2,6 @@ namespace Magento\AcceptanceTest\_default\Backend; use Magento\FunctionalTestingFramework\AcceptanceTester; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\CredentialStore; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\PersistedObjectHandler; use \Codeception\Util\Locator; use Yandex\Allure\Adapter\Annotation\Features; use Yandex\Allure\Adapter\Annotation\Stories; @@ -17,19 +15,26 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @Title("[NO TESTCASEID]: ChildExtendedTestMerging") * @group Child - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/ExtendedFunctionalTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/ExtendedFunctionalTest/ChildExtendedTestMerging.xml<br>") */ class ChildExtendedTestMergingCest { + /** + * @var bool + */ + private $isSuccess = false; + /** * @param AcceptanceTester $I * @throws \Exception */ public function _before(AcceptanceTester $I) { + $I->comment('[START BEFORE HOOK]'); $I->amOnPage("/firstUrl"); // stepKey: firstBeforeAmOnPageKey $I->amOnPage("/beforeUrl"); // stepKey: beforeAmOnPageKey $I->amOnPage("/lastUrl"); // stepKey: lastBefore + $I->comment('[END BEFORE HOOK]'); } /** @@ -38,7 +43,12 @@ class ChildExtendedTestMergingCest */ public function _after(AcceptanceTester $I) { + $I->comment('[START AFTER HOOK]'); $I->amOnPage("/afterUrl"); // stepKey: afterAmOnPageKey + $I->comment('[END AFTER HOOK]'); + if ($this->isSuccess) { + unlink(__FILE__); + } } /** @@ -54,7 +64,6 @@ class ChildExtendedTestMergingCest * @Severity(level = SeverityLevel::TRIVIAL) * @Features({"TestModule"}) * @Stories({"Child"}) - * @Parameter(name = "AcceptanceTester", value="$I") * @param AcceptanceTester $I * @return void * @throws \Exception @@ -66,4 +75,10 @@ class ChildExtendedTestMergingCest $I->comment("After Comment"); $I->comment("Last Comment"); } + + public function _passed(AcceptanceTester $I) + { + // Test passed successfully. + $this->isSuccess = true; + } } diff --git a/dev/tests/verification/Resources/ChildExtendedTestNoParent.txt b/dev/tests/verification/Resources/ChildExtendedTestNoParent.txt index 1eec9e48c..1bd027399 100644 --- a/dev/tests/verification/Resources/ChildExtendedTestNoParent.txt +++ b/dev/tests/verification/Resources/ChildExtendedTestNoParent.txt @@ -2,8 +2,6 @@ namespace Magento\AcceptanceTest\_default\Backend; use Magento\FunctionalTestingFramework\AcceptanceTester; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\CredentialStore; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\PersistedObjectHandler; use \Codeception\Util\Locator; use Yandex\Allure\Adapter\Annotation\Features; use Yandex\Allure\Adapter\Annotation\Stories; @@ -17,22 +15,38 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @Title("[NO TESTCASEID]: ChildExtendedTestNoParent") * @group Child - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/ExtendedFunctionalTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/ExtendedFunctionalTest/ChildExtendedTestNoParent.xml<br>") * @group skip */ class ChildExtendedTestNoParentCest { + /** + * @var bool + */ + private $isSuccess = false; + + /** + * @param AcceptanceTester $I + * @throws \Exception + */ + public function _after(AcceptanceTester $I) + { + if ($this->isSuccess) { + unlink(__FILE__); + } + } + /** * @Severity(level = SeverityLevel::CRITICAL) * @Features({"TestModule"}) * @Stories({"Child"}) - * @Parameter(name = "AcceptanceTester", value="$I") * @param AcceptanceTester $I * @return void * @throws \Exception */ public function ChildExtendedTestNoParent(AcceptanceTester $I, \Codeception\Scenario $scenario) { + unlink(__FILE__); $scenario->skip("This test is skipped due to the following issues:No issues have been specified."); } } diff --git a/dev/tests/verification/Resources/ChildExtendedTestRemoveAction.txt b/dev/tests/verification/Resources/ChildExtendedTestRemoveAction.txt index 20aa62e28..859bc8226 100644 --- a/dev/tests/verification/Resources/ChildExtendedTestRemoveAction.txt +++ b/dev/tests/verification/Resources/ChildExtendedTestRemoveAction.txt @@ -2,8 +2,6 @@ namespace Magento\AcceptanceTest\_default\Backend; use Magento\FunctionalTestingFramework\AcceptanceTester; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\CredentialStore; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\PersistedObjectHandler; use \Codeception\Util\Locator; use Yandex\Allure\Adapter\Annotation\Features; use Yandex\Allure\Adapter\Annotation\Stories; @@ -17,17 +15,24 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @Title("[NO TESTCASEID]: ChildExtendedTestRemoveAction") * @group Child - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/ExtendedFunctionalTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/ExtendedFunctionalTest/ChildExtendedTestRemoveAction.xml<br>") */ class ChildExtendedTestRemoveActionCest { + /** + * @var bool + */ + private $isSuccess = false; + /** * @param AcceptanceTester $I * @throws \Exception */ public function _before(AcceptanceTester $I) { + $I->comment('[START BEFORE HOOK]'); $I->amOnPage("/beforeUrl"); // stepKey: beforeAmOnPageKey + $I->comment('[END BEFORE HOOK]'); } /** @@ -36,7 +41,12 @@ class ChildExtendedTestRemoveActionCest */ public function _after(AcceptanceTester $I) { + $I->comment('[START AFTER HOOK]'); $I->amOnPage("/afterUrl"); // stepKey: afterAmOnPageKey + $I->comment('[END AFTER HOOK]'); + if ($this->isSuccess) { + unlink(__FILE__); + } } /** @@ -52,7 +62,6 @@ class ChildExtendedTestRemoveActionCest * @Severity(level = SeverityLevel::CRITICAL) * @Features({"TestModule"}) * @Stories({"Child"}) - * @Parameter(name = "AcceptanceTester", value="$I") * @param AcceptanceTester $I * @return void * @throws \Exception @@ -60,4 +69,10 @@ class ChildExtendedTestRemoveActionCest public function ChildExtendedTestRemoveAction(AcceptanceTester $I) { } + + public function _passed(AcceptanceTester $I) + { + // Test passed successfully. + $this->isSuccess = true; + } } diff --git a/dev/tests/verification/Resources/ChildExtendedTestRemoveHookAction.txt b/dev/tests/verification/Resources/ChildExtendedTestRemoveHookAction.txt index 561fd24fa..d48fb9743 100644 --- a/dev/tests/verification/Resources/ChildExtendedTestRemoveHookAction.txt +++ b/dev/tests/verification/Resources/ChildExtendedTestRemoveHookAction.txt @@ -2,8 +2,6 @@ namespace Magento\AcceptanceTest\_default\Backend; use Magento\FunctionalTestingFramework\AcceptanceTester; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\CredentialStore; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\PersistedObjectHandler; use \Codeception\Util\Locator; use Yandex\Allure\Adapter\Annotation\Features; use Yandex\Allure\Adapter\Annotation\Stories; @@ -17,10 +15,15 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @Title("[NO TESTCASEID]: ChildExtendedTestRemoveHookAction") * @group Child - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/ExtendedFunctionalTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/ExtendedFunctionalTest/ChildExtendedTestRemoveHookAction.xml<br>") */ class ChildExtendedTestRemoveHookActionCest { + /** + * @var bool + */ + private $isSuccess = false; + /** * @param AcceptanceTester $I * @throws \Exception @@ -35,7 +38,12 @@ class ChildExtendedTestRemoveHookActionCest */ public function _after(AcceptanceTester $I) { + $I->comment('[START AFTER HOOK]'); $I->amOnPage("/afterUrl"); // stepKey: afterAmOnPageKey + $I->comment('[END AFTER HOOK]'); + if ($this->isSuccess) { + unlink(__FILE__); + } } /** @@ -51,7 +59,6 @@ class ChildExtendedTestRemoveHookActionCest * @Severity(level = SeverityLevel::CRITICAL) * @Features({"TestModule"}) * @Stories({"Child"}) - * @Parameter(name = "AcceptanceTester", value="$I") * @param AcceptanceTester $I * @return void * @throws \Exception @@ -60,4 +67,10 @@ class ChildExtendedTestRemoveHookActionCest { $I->comment("Parent Comment"); } + + public function _passed(AcceptanceTester $I) + { + // Test passed successfully. + $this->isSuccess = true; + } } diff --git a/dev/tests/verification/Resources/ChildExtendedTestReplace.txt b/dev/tests/verification/Resources/ChildExtendedTestReplace.txt index 8d131a83f..acb8eced9 100644 --- a/dev/tests/verification/Resources/ChildExtendedTestReplace.txt +++ b/dev/tests/verification/Resources/ChildExtendedTestReplace.txt @@ -2,8 +2,6 @@ namespace Magento\AcceptanceTest\_default\Backend; use Magento\FunctionalTestingFramework\AcceptanceTester; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\CredentialStore; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\PersistedObjectHandler; use \Codeception\Util\Locator; use Yandex\Allure\Adapter\Annotation\Features; use Yandex\Allure\Adapter\Annotation\Stories; @@ -17,17 +15,24 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @Title("[NO TESTCASEID]: ChildExtendedTestReplace") * @group Child - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/ExtendedFunctionalTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/ExtendedFunctionalTest/ChildExtendedTestReplace.xml<br>") */ class ChildExtendedTestReplaceCest { + /** + * @var bool + */ + private $isSuccess = false; + /** * @param AcceptanceTester $I * @throws \Exception */ public function _before(AcceptanceTester $I) { + $I->comment('[START BEFORE HOOK]'); $I->amOnPage("/beforeUrl"); // stepKey: beforeAmOnPageKey + $I->comment('[END BEFORE HOOK]'); } /** @@ -36,7 +41,12 @@ class ChildExtendedTestReplaceCest */ public function _after(AcceptanceTester $I) { + $I->comment('[START AFTER HOOK]'); $I->amOnPage("/afterUrl"); // stepKey: afterAmOnPageKey + $I->comment('[END AFTER HOOK]'); + if ($this->isSuccess) { + unlink(__FILE__); + } } /** @@ -52,7 +62,6 @@ class ChildExtendedTestReplaceCest * @Severity(level = SeverityLevel::TRIVIAL) * @Features({"TestModule"}) * @Stories({"Child"}) - * @Parameter(name = "AcceptanceTester", value="$I") * @param AcceptanceTester $I * @return void * @throws \Exception @@ -61,4 +70,10 @@ class ChildExtendedTestReplaceCest { $I->comment("Different Input"); } + + public function _passed(AcceptanceTester $I) + { + // Test passed successfully. + $this->isSuccess = true; + } } diff --git a/dev/tests/verification/Resources/ChildExtendedTestReplaceHook.txt b/dev/tests/verification/Resources/ChildExtendedTestReplaceHook.txt index ae0b02c20..db6912e95 100644 --- a/dev/tests/verification/Resources/ChildExtendedTestReplaceHook.txt +++ b/dev/tests/verification/Resources/ChildExtendedTestReplaceHook.txt @@ -2,8 +2,6 @@ namespace Magento\AcceptanceTest\_default\Backend; use Magento\FunctionalTestingFramework\AcceptanceTester; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\CredentialStore; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\PersistedObjectHandler; use \Codeception\Util\Locator; use Yandex\Allure\Adapter\Annotation\Features; use Yandex\Allure\Adapter\Annotation\Stories; @@ -17,17 +15,24 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @Title("[NO TESTCASEID]: ChildExtendedTestReplaceHook") * @group Child - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/ExtendedFunctionalTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/ExtendedFunctionalTest/ChildExtendedTestReplaceHook.xml<br>") */ class ChildExtendedTestReplaceHookCest { + /** + * @var bool + */ + private $isSuccess = false; + /** * @param AcceptanceTester $I * @throws \Exception */ public function _before(AcceptanceTester $I) { + $I->comment('[START BEFORE HOOK]'); $I->amOnPage("/slightlyDifferentBeforeUrl"); // stepKey: beforeAmOnPageKey + $I->comment('[END BEFORE HOOK]'); } /** @@ -36,7 +41,12 @@ class ChildExtendedTestReplaceHookCest */ public function _after(AcceptanceTester $I) { + $I->comment('[START AFTER HOOK]'); $I->amOnPage("/afterUrl"); // stepKey: afterAmOnPageKey + $I->comment('[END AFTER HOOK]'); + if ($this->isSuccess) { + unlink(__FILE__); + } } /** @@ -52,7 +62,6 @@ class ChildExtendedTestReplaceHookCest * @Severity(level = SeverityLevel::TRIVIAL) * @Features({"TestModule"}) * @Stories({"Child"}) - * @Parameter(name = "AcceptanceTester", value="$I") * @param AcceptanceTester $I * @return void * @throws \Exception @@ -61,4 +70,10 @@ class ChildExtendedTestReplaceHookCest { $I->comment("Parent Comment"); } + + public function _passed(AcceptanceTester $I) + { + // Test passed successfully. + $this->isSuccess = true; + } } diff --git a/dev/tests/verification/Resources/DataActionsTest.txt b/dev/tests/verification/Resources/DataActionsTest.txt index 380db238d..982c680b2 100644 --- a/dev/tests/verification/Resources/DataActionsTest.txt +++ b/dev/tests/verification/Resources/DataActionsTest.txt @@ -2,8 +2,6 @@ namespace Magento\AcceptanceTest\_default\Backend; use Magento\FunctionalTestingFramework\AcceptanceTester; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\CredentialStore; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\PersistedObjectHandler; use \Codeception\Util\Locator; use Yandex\Allure\Adapter\Annotation\Features; use Yandex\Allure\Adapter\Annotation\Stories; @@ -15,86 +13,57 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/DataActionsTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/DataActionsTest.xml<br>") */ class DataActionsTestCest { + /** + * @var bool + */ + private $isSuccess = false; + /** * @param AcceptanceTester $I * @throws \Exception */ public function _before(AcceptanceTester $I) { - $I->comment("[createdInBefore] create 'entity' entity"); - PersistedObjectHandler::getInstance()->createEntity( - "createdInBefore", - "hook", - "entity", - [], - [] - ); - - $I->comment("[updateInBefore] update 'createdInBefore' entity to 'entity'"); - PersistedObjectHandler::getInstance()->updateEntity( - "createdInBefore", - "hook", - "entity", - [] - ); - - $I->comment("[deleteInBefore] delete entity 'createdInBefore'"); - PersistedObjectHandler::getInstance()->deleteEntity( - "createdInBefore", - "hook" - ); + $I->comment('[START BEFORE HOOK]'); + $I->updateEntity("createdInBefore", "hook", "entity",[]); // stepKey: updateInBefore + $I->deleteEntity("createdInBefore", "hook"); // stepKey: deleteInBefore + $I->comment('[END BEFORE HOOK]'); + } + /** + * @param AcceptanceTester $I + * @throws \Exception + */ + public function _after(AcceptanceTester $I) + { + if ($this->isSuccess) { + unlink(__FILE__); + } } /** * @Features({"TestModule"}) - * @Parameter(name = "AcceptanceTester", value="$I") * @param AcceptanceTester $I * @return void * @throws \Exception */ public function DataActionsTest(AcceptanceTester $I) { - $I->comment("[createdInTest] create 'entity' entity"); - PersistedObjectHandler::getInstance()->createEntity( - "createdInTest", - "test", - "entity", - [], - [] - ); - - $I->comment("[updateInTest] update 'createdInTest' entity to 'entity'"); - PersistedObjectHandler::getInstance()->updateEntity( - "createdInTest", - "test", - "entity", - [] - ); - - $I->comment("[deleteInTest] delete entity 'createdInTest'"); - PersistedObjectHandler::getInstance()->deleteEntity( - "createdInTest", - "test" - ); - - $I->comment("[updatedDataOutOfScope] update 'createdInBefore' entity to 'entity'"); - PersistedObjectHandler::getInstance()->updateEntity( - "createdInBefore", - "test", - "entity", - [] - ); - - $I->comment("[deleteDataOutOfScope] delete entity 'createdInBefore'"); - PersistedObjectHandler::getInstance()->deleteEntity( - "createdInBefore", - "test" - ); + $I->waitForElementClickable(".functionalTestSelector"); // stepKey: waitForElementClickable + $I->updateEntity("createdInTest", "test", "entity",[]); // stepKey: updateInTest + $I->deleteEntity("createdInTest", "test"); // stepKey: deleteInTest + $I->updateEntity("createdInBefore", "test", "entity",[]); // stepKey: updatedDataOutOfScope + $I->deleteEntity("createdInBefore", "test"); // stepKey: deleteDataOutOfScope + $I->rapidClick("#functionalTestSelector", "50"); // stepKey: rapidClickTest + } + public function _passed(AcceptanceTester $I) + { + // Test passed successfully. + $this->isSuccess = true; } } diff --git a/dev/tests/verification/Resources/DataReplacementTest.txt b/dev/tests/verification/Resources/DataReplacementTest.txt index 731ae63f8..940504c63 100644 --- a/dev/tests/verification/Resources/DataReplacementTest.txt +++ b/dev/tests/verification/Resources/DataReplacementTest.txt @@ -2,8 +2,6 @@ namespace Magento\AcceptanceTest\_default\Backend; use Magento\FunctionalTestingFramework\AcceptanceTester; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\CredentialStore; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\PersistedObjectHandler; use \Codeception\Util\Locator; use Yandex\Allure\Adapter\Annotation\Features; use Yandex\Allure\Adapter\Annotation\Stories; @@ -15,13 +13,28 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/DataReplacementTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/DataReplacementTest.xml<br>") */ class DataReplacementTestCest { + /** + * @var bool + */ + private $isSuccess = false; + + /** + * @param AcceptanceTester $I + * @throws \Exception + */ + public function _after(AcceptanceTester $I) + { + if ($this->isSuccess) { + unlink(__FILE__); + } + } + /** * @Features({"TestModule"}) - * @Parameter(name = "AcceptanceTester", value="$I") * @param AcceptanceTester $I * @return void * @throws \Exception @@ -52,7 +65,7 @@ class DataReplacementTestCest $I->searchAndMultiSelectOption("#selector", [msq("uniqueData") . "John", "Doe" . msq("uniqueData")]); // stepKey: parameterArrayReplacementMSQBoth $I->selectMultipleOptions("#Doe" . msq("uniqueData"), "#element", [msq("uniqueData") . "John", "Doe" . msq("uniqueData")]); // stepKey: multiSelectDataReplacement $I->fillField(".selector", "0"); // stepKey: insertZero - $insertCommand = $I->magentoCLI("do something Doe" . msq("uniqueData") . " with uniqueness"); // stepKey: insertCommand + $insertCommand = $I->magentoCLI("do something Doe" . msq("uniqueData") . " with uniqueness", 60); // stepKey: insertCommand $I->comment($insertCommand); $I->seeInPageSource("StringBefore John StringAfter"); // stepKey: htmlReplace1 $I->seeInPageSource("#John"); // stepKey: htmlReplace2 @@ -91,4 +104,10 @@ class DataReplacementTestCest $I->dontSeeInSource("#element"); // stepKey: htmlReplace35 $I->dontSeeInSource("StringBefore #element StringAfter"); // stepKey: htmlReplace36 } + + public function _passed(AcceptanceTester $I) + { + // Test passed successfully. + $this->isSuccess = true; + } } diff --git a/dev/tests/verification/Resources/DeprecatedEntitiesTest.txt b/dev/tests/verification/Resources/DeprecatedEntitiesTest.txt new file mode 100644 index 000000000..cc205c438 --- /dev/null +++ b/dev/tests/verification/Resources/DeprecatedEntitiesTest.txt @@ -0,0 +1,55 @@ +<?php +namespace Magento\AcceptanceTest\_default\Backend; + +use Magento\FunctionalTestingFramework\AcceptanceTester; +use \Codeception\Util\Locator; +use Yandex\Allure\Adapter\Annotation\Features; +use Yandex\Allure\Adapter\Annotation\Stories; +use Yandex\Allure\Adapter\Annotation\Title; +use Yandex\Allure\Adapter\Annotation\Description; +use Yandex\Allure\Adapter\Annotation\Parameter; +use Yandex\Allure\Adapter\Annotation\Severity; +use Yandex\Allure\Adapter\Model\SeverityLevel; +use Yandex\Allure\Adapter\Annotation\TestCaseId; + +/** + * @Description("<h3 class='y-label y-label_status_broken'>Deprecated Notice(s):</h3><ul><li>DEPRECATED ACTION GROUP in Test: DeprecatedActionGroup Deprecated action group</li><li>DEPRECATED SECTION in Test: {{DeprecatedSection.deprecatedElement}} Deprecated section</li><li>DEPRECATED ELEMENT in Test: {{DeprecatedSection.deprecatedElement}} Deprecated element</li><li>DEPRECATED DATA ENTITY in Test: {{DeprecatedData.field}} Data entity deprecated</li><li>DEPRECATED PAGE in Test: {{DeprecatedPage.url}} Deprecated page</li></ul><h3>Test files</h3>verification/TestModule/Test/DeprecatedEntitiesTest.xml<br>") + */ +class DeprecatedEntitiesTestCest +{ + /** + * @var bool + */ + private $isSuccess = false; + + /** + * @param AcceptanceTester $I + * @throws \Exception + */ + public function _after(AcceptanceTester $I) + { + if ($this->isSuccess) { + unlink(__FILE__); + } + } + + /** + * @Features({"TestModule"}) + * @param AcceptanceTester $I + * @return void + * @throws \Exception + */ + public function DeprecatedEntitiesTest(AcceptanceTester $I) + { + $I->comment("Entering Action Group [deprecatedActionGroup] DeprecatedActionGroup"); + $I->see("deprecated", "#element"); // stepKey: deprecatedSeeDeprecatedActionGroup + $I->comment("Exiting Action Group [deprecatedActionGroup] DeprecatedActionGroup"); + $I->amOnPage("/test.html"); // stepKey: amOnPage + } + + public function _passed(AcceptanceTester $I) + { + // Test passed successfully. + $this->isSuccess = true; + } +} diff --git a/dev/tests/verification/Resources/DeprecatedTest.txt b/dev/tests/verification/Resources/DeprecatedTest.txt new file mode 100644 index 000000000..7e34131d5 --- /dev/null +++ b/dev/tests/verification/Resources/DeprecatedTest.txt @@ -0,0 +1,55 @@ +<?php +namespace Magento\AcceptanceTest\_default\Backend; + +use Magento\FunctionalTestingFramework\AcceptanceTester; +use \Codeception\Util\Locator; +use Yandex\Allure\Adapter\Annotation\Features; +use Yandex\Allure\Adapter\Annotation\Stories; +use Yandex\Allure\Adapter\Annotation\Title; +use Yandex\Allure\Adapter\Annotation\Description; +use Yandex\Allure\Adapter\Annotation\Parameter; +use Yandex\Allure\Adapter\Annotation\Severity; +use Yandex\Allure\Adapter\Model\SeverityLevel; +use Yandex\Allure\Adapter\Annotation\TestCaseId; + +/** + * @Description("<h3 class='y-label y-label_status_broken'>Deprecated Notice(s):</h3><ul><li>Test is deprecated</li><li>DEPRECATED ACTION GROUP in Test: DeprecatedActionGroup Deprecated action group</li><li>DEPRECATED SECTION in Test: {{DeprecatedSection.deprecatedElement}} Deprecated section</li><li>DEPRECATED ELEMENT in Test: {{DeprecatedSection.deprecatedElement}} Deprecated element</li><li>DEPRECATED DATA ENTITY in Test: {{DeprecatedData.field}} Data entity deprecated</li><li>DEPRECATED PAGE in Test: {{DeprecatedPage.url}} Deprecated page</li></ul><h3>Test files</h3>verification/TestModule/Test/DeprecatedTest.xml<br>") + */ +class DeprecatedTestCest +{ + /** + * @var bool + */ + private $isSuccess = false; + + /** + * @param AcceptanceTester $I + * @throws \Exception + */ + public function _after(AcceptanceTester $I) + { + if ($this->isSuccess) { + unlink(__FILE__); + } + } + + /** + * @Features({"TestModule"}) + * @param AcceptanceTester $I + * @return void + * @throws \Exception + */ + public function DeprecatedTest(AcceptanceTester $I) + { + $I->comment("Entering Action Group [deprecatedActionGroup] DeprecatedActionGroup"); + $I->see("deprecated", "#element"); // stepKey: deprecatedSeeDeprecatedActionGroup + $I->comment("Exiting Action Group [deprecatedActionGroup] DeprecatedActionGroup"); + $I->amOnPage("/test.html"); // stepKey: amOnPage + } + + public function _passed(AcceptanceTester $I) + { + // Test passed successfully. + $this->isSuccess = true; + } +} diff --git a/dev/tests/verification/Resources/ExecuteInSeleniumTest.txt b/dev/tests/verification/Resources/ExecuteInSeleniumTest.txt deleted file mode 100644 index 6b709a0cb..000000000 --- a/dev/tests/verification/Resources/ExecuteInSeleniumTest.txt +++ /dev/null @@ -1,33 +0,0 @@ -<?php -namespace Magento\AcceptanceTest\_default\Backend; - -use Magento\FunctionalTestingFramework\AcceptanceTester; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\CredentialStore; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\PersistedObjectHandler; -use \Codeception\Util\Locator; -use Yandex\Allure\Adapter\Annotation\Features; -use Yandex\Allure\Adapter\Annotation\Stories; -use Yandex\Allure\Adapter\Annotation\Title; -use Yandex\Allure\Adapter\Annotation\Description; -use Yandex\Allure\Adapter\Annotation\Parameter; -use Yandex\Allure\Adapter\Annotation\Severity; -use Yandex\Allure\Adapter\Model\SeverityLevel; -use Yandex\Allure\Adapter\Annotation\TestCaseId; - -/** - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/ExecuteInSeleniumTest.xml<br>") - */ -class ExecuteInSeleniumTestCest -{ - /** - * @Features({"TestModule"}) - * @Parameter(name = "AcceptanceTester", value="$I") - * @param AcceptanceTester $I - * @return void - * @throws \Exception - */ - public function ExecuteInSeleniumTest(AcceptanceTester $I) - { - $I->executeInSelenium(function ($webdriver) { return "Hello, World!"}); // stepKey: executeInSeleniumStep - } -} diff --git a/dev/tests/verification/Resources/ExecuteJsEscapingTest.txt b/dev/tests/verification/Resources/ExecuteJsEscapingTest.txt index f7618cbf9..8718d188b 100644 --- a/dev/tests/verification/Resources/ExecuteJsEscapingTest.txt +++ b/dev/tests/verification/Resources/ExecuteJsEscapingTest.txt @@ -2,8 +2,6 @@ namespace Magento\AcceptanceTest\_default\Backend; use Magento\FunctionalTestingFramework\AcceptanceTester; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\CredentialStore; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\PersistedObjectHandler; use \Codeception\Util\Locator; use Yandex\Allure\Adapter\Annotation\Features; use Yandex\Allure\Adapter\Annotation\Stories; @@ -15,13 +13,28 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/ExecuteJsTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/ExecuteJsTest.xml<br>") */ class ExecuteJsEscapingTestCest { + /** + * @var bool + */ + private $isSuccess = false; + + /** + * @param AcceptanceTester $I + * @throws \Exception + */ + public function _after(AcceptanceTester $I) + { + if ($this->isSuccess) { + unlink(__FILE__); + } + } + /** * @Features({"TestModule"}) - * @Parameter(name = "AcceptanceTester", value="$I") * @param AcceptanceTester $I * @return void * @throws \Exception @@ -30,8 +43,14 @@ class ExecuteJsEscapingTestCest { $javaVariableEscape = $I->executeJS("return \$javascriptVariable"); // stepKey: javaVariableEscape $mftfVariableNotEscaped = $I->executeJS("return {$doNotEscape}"); // stepKey: mftfVariableNotEscaped - $persistedDataNotEscaped = $I->executeJS("return " . PersistedObjectHandler::getInstance()->retrieveEntityField('persisted', 'data', 'test')); // stepKey: persistedDataNotEscaped - $hookPersistedDataNotEscaped = $I->executeJS("return " . PersistedObjectHandler::getInstance()->retrieveEntityField('persisted', 'data', 'test')); // stepKey: hookPersistedDataNotEscaped - $addNewAttributeForRule = $I->executeJS("document.querySelector('entity option[value=" . PersistedObjectHandler::getInstance()->retrieveEntityField('productAttribute', 'attribute_code', 'test') . "]').setAttribute('selected', 'selected')"); // stepKey: addNewAttributeForRule + $persistedDataNotEscaped = $I->executeJS("return " . $I->retrieveEntityField('persisted', 'data', 'test')); // stepKey: persistedDataNotEscaped + $hookPersistedDataNotEscaped = $I->executeJS("return " . $I->retrieveEntityField('persisted', 'data', 'test')); // stepKey: hookPersistedDataNotEscaped + $addNewAttributeForRule = $I->executeJS("document.querySelector('entity option[value=" . $I->retrieveEntityField('productAttribute', 'attribute_code', 'test') . "]').setAttribute('selected', 'selected')"); // stepKey: addNewAttributeForRule + } + + public function _passed(AcceptanceTester $I) + { + // Test passed successfully. + $this->isSuccess = true; } } diff --git a/dev/tests/verification/Resources/ExtendParentDataTest.txt b/dev/tests/verification/Resources/ExtendParentDataTest.txt index fb90599e3..93ea3b754 100644 --- a/dev/tests/verification/Resources/ExtendParentDataTest.txt +++ b/dev/tests/verification/Resources/ExtendParentDataTest.txt @@ -2,8 +2,6 @@ namespace Magento\AcceptanceTest\_default\Backend; use Magento\FunctionalTestingFramework\AcceptanceTester; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\CredentialStore; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\PersistedObjectHandler; use \Codeception\Util\Locator; use Yandex\Allure\Adapter\Annotation\Features; use Yandex\Allure\Adapter\Annotation\Stories; @@ -15,32 +13,45 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/ExtendedDataTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/ExtendedDataTest.xml<br>") */ class ExtendParentDataTestCest { + /** + * @var bool + */ + private $isSuccess = false; + + /** + * @param AcceptanceTester $I + * @throws \Exception + */ + public function _after(AcceptanceTester $I) + { + if ($this->isSuccess) { + unlink(__FILE__); + } + } + /** * @Features({"TestModule"}) - * @Parameter(name = "AcceptanceTester", value="$I") * @param AcceptanceTester $I * @return void * @throws \Exception */ public function ExtendParentDataTest(AcceptanceTester $I) { - $I->comment("[simpleDataKey] create 'extendParentData' entity"); - PersistedObjectHandler::getInstance()->createEntity( - "simpleDataKey", - "test", - "extendParentData", - [], - [] - ); - + $I->createEntity("simpleDataKey", "test", "extendParentData", [], []); // stepKey: simpleDataKey $I->searchAndMultiSelectOption("#selector", ["otherName"]); // stepKey: getName $I->searchAndMultiSelectOption("#selector", ["extendName"]); // stepKey: getNameExtend $I->searchAndMultiSelectOption("#selector", ["item"]); // stepKey: emptyPost $I->searchAndMultiSelectOption("#selector", [msq("extendParentData") . "prename"]); // stepKey: originalPre $I->searchAndMultiSelectOption("#selector", ["postnameExtend" . msq("extendParentData")]); // stepKey: secondUniquePre } + + public function _passed(AcceptanceTester $I) + { + // Test passed successfully. + $this->isSuccess = true; + } } diff --git a/dev/tests/verification/Resources/ExtendedActionGroup.txt b/dev/tests/verification/Resources/ExtendedActionGroup.txt index 7456d99ce..2d2373c0d 100644 --- a/dev/tests/verification/Resources/ExtendedActionGroup.txt +++ b/dev/tests/verification/Resources/ExtendedActionGroup.txt @@ -2,8 +2,6 @@ namespace Magento\AcceptanceTest\_default\Backend; use Magento\FunctionalTestingFramework\AcceptanceTester; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\CredentialStore; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\PersistedObjectHandler; use \Codeception\Util\Locator; use Yandex\Allure\Adapter\Annotation\Features; use Yandex\Allure\Adapter\Annotation\Stories; @@ -15,13 +13,28 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/ActionGroupTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/ActionGroupTest/ExtendedActionGroup.xml<br>") */ class ExtendedActionGroupCest { + /** + * @var bool + */ + private $isSuccess = false; + + /** + * @param AcceptanceTester $I + * @throws \Exception + */ + public function _after(AcceptanceTester $I) + { + if ($this->isSuccess) { + unlink(__FILE__); + } + } + /** * @Features({"TestModule"}) - * @Parameter(name = "AcceptanceTester", value="$I") * @param AcceptanceTester $I * @return void * @throws \Exception @@ -36,4 +49,10 @@ class ExtendedActionGroupCest $I->assertCount(8000, $grabProductsActionGroup); // stepKey: assertSecondCountActionGroup $I->comment("Exiting Action Group [actionGroup] extendTestActionGroup"); } + + public function _passed(AcceptanceTester $I) + { + // Test passed successfully. + $this->isSuccess = true; + } } diff --git a/dev/tests/verification/Resources/ExtendedActionGroupReturningValueTest.txt b/dev/tests/verification/Resources/ExtendedActionGroupReturningValueTest.txt new file mode 100644 index 000000000..4c19fea5e --- /dev/null +++ b/dev/tests/verification/Resources/ExtendedActionGroupReturningValueTest.txt @@ -0,0 +1,61 @@ +<?php +namespace Magento\AcceptanceTest\_default\Backend; + +use Magento\FunctionalTestingFramework\AcceptanceTester; +use \Codeception\Util\Locator; +use Yandex\Allure\Adapter\Annotation\Features; +use Yandex\Allure\Adapter\Annotation\Stories; +use Yandex\Allure\Adapter\Annotation\Title; +use Yandex\Allure\Adapter\Annotation\Description; +use Yandex\Allure\Adapter\Annotation\Parameter; +use Yandex\Allure\Adapter\Annotation\Severity; +use Yandex\Allure\Adapter\Model\SeverityLevel; +use Yandex\Allure\Adapter\Annotation\TestCaseId; + +/** + * @Title("[NO TESTCASEID]: Extended ActionGroup Returning Value Test") + * @Description("<h3>Test files</h3>verification/TestModule/Test/ActionGroupTest/ExtendedActionGroupReturningValueTest.xml<br>") + */ +class ExtendedActionGroupReturningValueTestCest +{ + /** + * @var bool + */ + private $isSuccess = false; + + /** + * @param AcceptanceTester $I + * @throws \Exception + */ + public function _after(AcceptanceTester $I) + { + if ($this->isSuccess) { + unlink(__FILE__); + } + } + + /** + * @Severity(level = SeverityLevel::CRITICAL) + * @Features({"TestModule"}) + * @param AcceptanceTester $I + * @return void + * @throws \Exception + */ + public function ExtendedActionGroupReturningValueTest(AcceptanceTester $I) + { + $I->comment("Entering Action Group [actionGroupReturningValue] ActionGroupReturningValueActionGroup"); + $grabProducts1ActionGroupReturningValue = $I->grabMultiple("selector"); // stepKey: grabProducts1ActionGroupReturningValue + $I->assertCount(99, $grabProducts1ActionGroupReturningValue); // stepKey: assertCountActionGroupReturningValue + $actionGroupReturningValue = $I->return($grabProducts1ActionGroupReturningValue); // stepKey: returnProducts1ActionGroupReturningValue + $I->comment("Exiting Action Group [actionGroupReturningValue] ActionGroupReturningValueActionGroup"); + $I->comment("Entering Action Group [actionGroupWithStringUsage1] actionGroupWithStringUsage"); + $I->see($actionGroupReturningValue, "#element .{$actionGroupReturningValue}"); // stepKey: see1ActionGroupWithStringUsage1 + $I->comment("Exiting Action Group [actionGroupWithStringUsage1] actionGroupWithStringUsage"); + } + + public function _passed(AcceptanceTester $I) + { + // Test passed successfully. + $this->isSuccess = true; + } +} diff --git a/dev/tests/verification/Resources/ExtendedChildActionGroupReturningValueTest.txt b/dev/tests/verification/Resources/ExtendedChildActionGroupReturningValueTest.txt new file mode 100644 index 000000000..a25ba031d --- /dev/null +++ b/dev/tests/verification/Resources/ExtendedChildActionGroupReturningValueTest.txt @@ -0,0 +1,64 @@ +<?php +namespace Magento\AcceptanceTest\_default\Backend; + +use Magento\FunctionalTestingFramework\AcceptanceTester; +use \Codeception\Util\Locator; +use Yandex\Allure\Adapter\Annotation\Features; +use Yandex\Allure\Adapter\Annotation\Stories; +use Yandex\Allure\Adapter\Annotation\Title; +use Yandex\Allure\Adapter\Annotation\Description; +use Yandex\Allure\Adapter\Annotation\Parameter; +use Yandex\Allure\Adapter\Annotation\Severity; +use Yandex\Allure\Adapter\Model\SeverityLevel; +use Yandex\Allure\Adapter\Annotation\TestCaseId; + +/** + * @Title("[NO TESTCASEID]: Extended Child ActionGroup Returning Value Test") + * @Description("<h3>Test files</h3>verification/TestModule/Test/ActionGroupTest/ExtendedChildActionGroupReturningValueTest.xml<br>") + */ +class ExtendedChildActionGroupReturningValueTestCest +{ + /** + * @var bool + */ + private $isSuccess = false; + + /** + * @param AcceptanceTester $I + * @throws \Exception + */ + public function _after(AcceptanceTester $I) + { + if ($this->isSuccess) { + unlink(__FILE__); + } + } + + /** + * @Severity(level = SeverityLevel::CRITICAL) + * @Features({"TestModule"}) + * @param AcceptanceTester $I + * @return void + * @throws \Exception + */ + public function ExtendedChildActionGroupReturningValueTest(AcceptanceTester $I) + { + $I->comment("Entering Action Group [extendedActionGroupReturningValue] ExtendedActionGroupReturningValueActionGroup"); + $grabProducts1ExtendedActionGroupReturningValue = $I->grabMultiple("selector"); // stepKey: grabProducts1ExtendedActionGroupReturningValue + $I->assertCount(99, $grabProducts1ExtendedActionGroupReturningValue); // stepKey: assertCountExtendedActionGroupReturningValue + $extendedActionGroupReturningValue = $I->return($grabProducts1ExtendedActionGroupReturningValue); // stepKey: returnProducts1ExtendedActionGroupReturningValue + $grabProducts2ExtendedActionGroupReturningValue = $I->grabMultiple("otherSelector"); // stepKey: grabProducts2ExtendedActionGroupReturningValue + $I->assertCount(8000, $grabProducts2ExtendedActionGroupReturningValue); // stepKey: assertSecondCountExtendedActionGroupReturningValue + $extendedActionGroupReturningValue = $I->return($grabProducts2ExtendedActionGroupReturningValue); // stepKey: returnProducts2ExtendedActionGroupReturningValue + $I->comment("Exiting Action Group [extendedActionGroupReturningValue] ExtendedActionGroupReturningValueActionGroup"); + $I->comment("Entering Action Group [actionGroupWithStringUsage1] actionGroupWithStringUsage"); + $I->see($extendedActionGroupReturningValue, "#element .{$extendedActionGroupReturningValue}"); // stepKey: see1ActionGroupWithStringUsage1 + $I->comment("Exiting Action Group [actionGroupWithStringUsage1] actionGroupWithStringUsage"); + } + + public function _passed(AcceptanceTester $I) + { + // Test passed successfully. + $this->isSuccess = true; + } +} diff --git a/dev/tests/verification/Resources/ExtendedChildTestInSuiteCest.txt b/dev/tests/verification/Resources/ExtendedChildTestInSuiteCest.txt index f52a46c2f..c46076353 100644 --- a/dev/tests/verification/Resources/ExtendedChildTestInSuiteCest.txt +++ b/dev/tests/verification/Resources/ExtendedChildTestInSuiteCest.txt @@ -2,8 +2,6 @@ namespace Magento\AcceptanceTest\_suiteExtends\Backend; use Magento\FunctionalTestingFramework\AcceptanceTester; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\CredentialStore; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\PersistedObjectHandler; use \Codeception\Util\Locator; use Yandex\Allure\Adapter\Annotation\Features; use Yandex\Allure\Adapter\Annotation\Stories; @@ -17,17 +15,24 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @Title("[NO TESTCASEID]: ExtendedChildTestInSuite") * @group ExtendedTestInSuite - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/ExtendedFunctionalTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/ExtendedFunctionalTest/ExtendedChildTestInSuite.xml<br>") */ class ExtendedChildTestInSuiteCest { + /** + * @var bool + */ + private $isSuccess = false; + /** * @param AcceptanceTester $I * @throws \Exception */ public function _before(AcceptanceTester $I) { + $I->comment('[START BEFORE HOOK]'); $I->amOnPage("/beforeUrl"); // stepKey: beforeAmOnPageKey + $I->comment('[END BEFORE HOOK]'); } /** @@ -36,7 +41,12 @@ class ExtendedChildTestInSuiteCest */ public function _after(AcceptanceTester $I) { + $I->comment('[START AFTER HOOK]'); $I->amOnPage("/afterUrl"); // stepKey: afterAmOnPageKey + $I->comment('[END AFTER HOOK]'); + if ($this->isSuccess) { + unlink(__FILE__); + } } /** @@ -52,7 +62,6 @@ class ExtendedChildTestInSuiteCest * @Severity(level = SeverityLevel::TRIVIAL) * @Features({"TestModule"}) * @Stories({"ExtendedChildTestInSuite"}) - * @Parameter(name = "AcceptanceTester", value="$I") * @param AcceptanceTester $I * @return void * @throws \Exception @@ -61,4 +70,10 @@ class ExtendedChildTestInSuiteCest { $I->comment("Different Input"); } + + public function _passed(AcceptanceTester $I) + { + // Test passed successfully. + $this->isSuccess = true; + } } diff --git a/dev/tests/verification/Resources/ExtendedChildTestNotInSuite.txt b/dev/tests/verification/Resources/ExtendedChildTestNotInSuite.txt index 3dfeac8f3..38ba62faf 100644 --- a/dev/tests/verification/Resources/ExtendedChildTestNotInSuite.txt +++ b/dev/tests/verification/Resources/ExtendedChildTestNotInSuite.txt @@ -2,8 +2,6 @@ namespace Magento\AcceptanceTest\_default\Backend; use Magento\FunctionalTestingFramework\AcceptanceTester; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\CredentialStore; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\PersistedObjectHandler; use \Codeception\Util\Locator; use Yandex\Allure\Adapter\Annotation\Features; use Yandex\Allure\Adapter\Annotation\Stories; @@ -16,17 +14,24 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @Title("[NO TESTCASEID]: ExtendedChildTestNotInSuite") - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/ExtendedFunctionalTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/ExtendedFunctionalTest/ExtendedChildTestNotInSuite.xml<br>") */ class ExtendedChildTestNotInSuiteCest { + /** + * @var bool + */ + private $isSuccess = false; + /** * @param AcceptanceTester $I * @throws \Exception */ public function _before(AcceptanceTester $I) { + $I->comment('[START BEFORE HOOK]'); $I->amOnPage("/beforeUrl"); // stepKey: beforeAmOnPageKey + $I->comment('[END BEFORE HOOK]'); } /** @@ -35,7 +40,12 @@ class ExtendedChildTestNotInSuiteCest */ public function _after(AcceptanceTester $I) { + $I->comment('[START AFTER HOOK]'); $I->amOnPage("/afterUrl"); // stepKey: afterAmOnPageKey + $I->comment('[END AFTER HOOK]'); + if ($this->isSuccess) { + unlink(__FILE__); + } } /** @@ -51,7 +61,6 @@ class ExtendedChildTestNotInSuiteCest * @Severity(level = SeverityLevel::TRIVIAL) * @Features({"TestModule"}) * @Stories({"ExtendedChildTestNotInSuite"}) - * @Parameter(name = "AcceptanceTester", value="$I") * @param AcceptanceTester $I * @return void * @throws \Exception @@ -60,4 +69,10 @@ class ExtendedChildTestNotInSuiteCest { $I->comment("Different Input"); } + + public function _passed(AcceptanceTester $I) + { + // Test passed successfully. + $this->isSuccess = true; + } } diff --git a/dev/tests/verification/Resources/ExtendedParameterArrayTest.txt b/dev/tests/verification/Resources/ExtendedParameterArrayTest.txt index 21212ba1a..21d711c28 100644 --- a/dev/tests/verification/Resources/ExtendedParameterArrayTest.txt +++ b/dev/tests/verification/Resources/ExtendedParameterArrayTest.txt @@ -2,8 +2,6 @@ namespace Magento\AcceptanceTest\_default\Backend; use Magento\FunctionalTestingFramework\AcceptanceTester; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\CredentialStore; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\PersistedObjectHandler; use \Codeception\Util\Locator; use Yandex\Allure\Adapter\Annotation\Features; use Yandex\Allure\Adapter\Annotation\Stories; @@ -18,27 +16,41 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; */ class ExtendParentDataTestCest { + /** + * @var bool + */ + private $isSuccess = false; + + /** + * @param AcceptanceTester $I + * @throws \Exception + */ + public function _after(AcceptanceTester $I) + { + if ($this->isSuccess) { + unlink(__FILE__); + } + } + /** * @Features({"TestModule"}) - * @Parameter(name = "AcceptanceTester", value="$I") * @param AcceptanceTester $I * @return void * @throws \Exception */ public function ExtendParentDataTest(AcceptanceTester $I) { - $I->amGoingTo("create entity that has the stepKey: simpleDataKey"); - PersistedObjectHandler::getInstance()->createEntity( - "simpleDataKey", - "test", - "extendParentData", - [], - [] - ); + $I->createEntity("simpleDataKey", "test", "extendParentData", [], []); // stepKey: simpleDataKey $I->searchAndMultiSelectOption("#selector", ["otherName"]); $I->searchAndMultiSelectOption("#selector", ["extendName"]); $I->searchAndMultiSelectOption("#selector", ["item"]); $I->searchAndMultiSelectOption("#selector", [msq("extendParentData") . "prename"]); $I->searchAndMultiSelectOption("#selector", ["postnameExtend" . msq("extendParentData")]); } + + public function _passed(AcceptanceTester $I) + { + // Test passed successfully. + $this->isSuccess = true; + } } diff --git a/dev/tests/verification/Resources/ExtendedRemoveActionGroup.txt b/dev/tests/verification/Resources/ExtendedRemoveActionGroup.txt index a9c971b96..809fd2874 100644 --- a/dev/tests/verification/Resources/ExtendedRemoveActionGroup.txt +++ b/dev/tests/verification/Resources/ExtendedRemoveActionGroup.txt @@ -2,8 +2,6 @@ namespace Magento\AcceptanceTest\_default\Backend; use Magento\FunctionalTestingFramework\AcceptanceTester; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\CredentialStore; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\PersistedObjectHandler; use \Codeception\Util\Locator; use Yandex\Allure\Adapter\Annotation\Features; use Yandex\Allure\Adapter\Annotation\Stories; @@ -15,13 +13,28 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/ActionGroupTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/ActionGroupTest/ExtendedRemoveActionGroup.xml<br>") */ class ExtendedRemoveActionGroupCest { + /** + * @var bool + */ + private $isSuccess = false; + + /** + * @param AcceptanceTester $I + * @throws \Exception + */ + public function _after(AcceptanceTester $I) + { + if ($this->isSuccess) { + unlink(__FILE__); + } + } + /** * @Features({"TestModule"}) - * @Parameter(name = "AcceptanceTester", value="$I") * @param AcceptanceTester $I * @return void * @throws \Exception @@ -31,4 +44,10 @@ class ExtendedRemoveActionGroupCest $I->comment("Entering Action Group [actionGroup] extendRemoveTestActionGroup"); $I->comment("Exiting Action Group [actionGroup] extendRemoveTestActionGroup"); } + + public function _passed(AcceptanceTester $I) + { + // Test passed successfully. + $this->isSuccess = true; + } } diff --git a/dev/tests/verification/Resources/ExtendingSkippedTest.txt b/dev/tests/verification/Resources/ExtendingSkippedTest.txt index 681af0fef..522ba7620 100644 --- a/dev/tests/verification/Resources/ExtendingSkippedTest.txt +++ b/dev/tests/verification/Resources/ExtendingSkippedTest.txt @@ -2,8 +2,6 @@ namespace Magento\AcceptanceTest\_default\Backend; use Magento\FunctionalTestingFramework\AcceptanceTester; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\CredentialStore; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\PersistedObjectHandler; use \Codeception\Util\Locator; use Yandex\Allure\Adapter\Annotation\Features; use Yandex\Allure\Adapter\Annotation\Stories; @@ -17,49 +15,26 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @Title("[NO TESTCASEID]: ChildExtendedTestSkippedParent") * @group Child - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/ExtendedFunctionalTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/ExtendedFunctionalTest/ExtendingSkippedTest.xml<br>") */ class ExtendingSkippedTestCest { /** - * @param AcceptanceTester $I - * @throws \Exception - */ - public function _before(AcceptanceTester $I) - { - $I->amOnPage("/beforeUrl"); // stepKey: beforeAmOnPageKey - } - - /** - * @param AcceptanceTester $I - * @throws \Exception - */ - public function _after(AcceptanceTester $I) - { - $I->amOnPage("/afterUrl"); // stepKey: afterAmOnPageKey - } - - /** - * @param AcceptanceTester $I - * @throws \Exception - */ - public function _failed(AcceptanceTester $I) - { - $I->saveScreenshot(); // stepKey: saveScreenshot - } + * @var bool + */ + private $isSuccess = false; /** * @Severity(level = SeverityLevel::CRITICAL) * @Features({"TestModule"}) * @Stories({"Child"}) - * @Parameter(name = "AcceptanceTester", value="$I") * @param AcceptanceTester $I * @return void * @throws \Exception */ - public function ExtendingSkippedTest(AcceptanceTester $I) + public function ExtendingSkippedTest(AcceptanceTester $I, \Codeception\Scenario $scenario) { - $I->comment("text"); - $I->comment("child"); + unlink(__FILE__); + $scenario->skip("This test is skipped due to the following issues:\nParentTestIsSkipped"); } } diff --git a/dev/tests/verification/Resources/GroupSkipGenerationTest.txt b/dev/tests/verification/Resources/GroupSkipGenerationTest.txt new file mode 100644 index 000000000..034307e37 --- /dev/null +++ b/dev/tests/verification/Resources/GroupSkipGenerationTest.txt @@ -0,0 +1,55 @@ +<?php +namespace Magento\AcceptanceTest\_default\Backend; + +use Magento\FunctionalTestingFramework\AcceptanceTester; +use \Codeception\Util\Locator; +use Yandex\Allure\Adapter\Annotation\Features; +use Yandex\Allure\Adapter\Annotation\Stories; +use Yandex\Allure\Adapter\Annotation\Title; +use Yandex\Allure\Adapter\Annotation\Description; +use Yandex\Allure\Adapter\Annotation\Parameter; +use Yandex\Allure\Adapter\Annotation\Severity; +use Yandex\Allure\Adapter\Model\SeverityLevel; +use Yandex\Allure\Adapter\Annotation\TestCaseId; + +/** + * @Title("[NO TESTCASEID]: GroupSkipGenerationTestTitle") + * @Description("GroupSkipGenerationTestDescription<h3>Test files</h3>verification/TestModule/Test/GroupSkipGenerationTest.xml<br>") + * @group skip + */ +class GroupSkipGenerationTestCest +{ + /** + * @var bool + */ + private $isSuccess = false; + + /** + * @param AcceptanceTester $I + * @throws \Exception + */ + public function _after(AcceptanceTester $I) + { + if ($this->isSuccess) { + unlink(__FILE__); + } + } + + /** + * @Stories({"GroupSkipGenerationTestStory"}) + * @Severity(level = SeverityLevel::MINOR) + * @Features({"TestModule"}) + * @param AcceptanceTester $I + * @return void + * @throws \Exception + */ + public function GroupSkipGenerationTest(AcceptanceTester $I) + { + } + + public function _passed(AcceptanceTester $I) + { + // Test passed successfully. + $this->isSuccess = true; + } +} diff --git a/dev/tests/verification/Resources/HookActionsTest.txt b/dev/tests/verification/Resources/HookActionsTest.txt index 231353bc9..6ff14a09c 100644 --- a/dev/tests/verification/Resources/HookActionsTest.txt +++ b/dev/tests/verification/Resources/HookActionsTest.txt @@ -2,8 +2,6 @@ namespace Magento\AcceptanceTest\_default\Backend; use Magento\FunctionalTestingFramework\AcceptanceTester; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\CredentialStore; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\PersistedObjectHandler; use \Codeception\Util\Locator; use Yandex\Allure\Adapter\Annotation\Features; use Yandex\Allure\Adapter\Annotation\Stories; @@ -15,40 +13,21 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/HookActionsTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/HookActionsTest.xml<br>") */ class HookActionsTestCest { + /** + * @var bool + */ + private $isSuccess = false; + /** * @param AcceptanceTester $I * @throws \Exception */ public function _before(AcceptanceTester $I) { - $I->comment("[sampleCreateBefore] create 'sampleCreatedEntity' entity"); - PersistedObjectHandler::getInstance()->createEntity( - "sampleCreateBefore", - "hook", - "sampleCreatedEntity", - [], - [] - ); - - $I->comment("[sampleDeleteBefore] delete entity 'sampleCreateBefore'"); - PersistedObjectHandler::getInstance()->deleteEntity( - "sampleCreateBefore", - "hook" - ); - - $I->comment("[sampleCreateForAfter] create 'sampleCreatedEntity' entity"); - PersistedObjectHandler::getInstance()->createEntity( - "sampleCreateForAfter", - "hook", - "sampleCreatedEntity", - [], - [] - ); - } /** @@ -57,21 +36,12 @@ class HookActionsTestCest */ public function _after(AcceptanceTester $I) { - $I->comment("[sampleCreateAfter] create 'sampleCreatedEntity' entity"); - PersistedObjectHandler::getInstance()->createEntity( - "sampleCreateAfter", - "hook", - "sampleCreatedEntity", - [], - [] - ); - - $I->comment("[sampleDeleteAfter] delete entity 'sampleCreateForAfter'"); - PersistedObjectHandler::getInstance()->deleteEntity( - "sampleCreateForAfter", - "hook" - ); - + $I->comment('[START AFTER HOOK]'); + $I->deleteEntity("sampleCreateForAfter", "hook"); // stepKey: sampleDeleteAfter + $I->comment('[END AFTER HOOK]'); + if ($this->isSuccess) { + unlink(__FILE__); + } } /** @@ -85,7 +55,6 @@ class HookActionsTestCest /** * @Features({"TestModule"}) - * @Parameter(name = "AcceptanceTester", value="$I") * @param AcceptanceTester $I * @return void * @throws \Exception @@ -93,4 +62,10 @@ class HookActionsTestCest public function HookActionsTest(AcceptanceTester $I) { } + + public function _passed(AcceptanceTester $I) + { + // Test passed successfully. + $this->isSuccess = true; + } } diff --git a/dev/tests/verification/Resources/LocatorFunctionTest.txt b/dev/tests/verification/Resources/LocatorFunctionTest.txt index 00baeeac5..f26f2aa4f 100644 --- a/dev/tests/verification/Resources/LocatorFunctionTest.txt +++ b/dev/tests/verification/Resources/LocatorFunctionTest.txt @@ -2,8 +2,6 @@ namespace Magento\AcceptanceTest\_default\Backend; use Magento\FunctionalTestingFramework\AcceptanceTester; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\CredentialStore; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\PersistedObjectHandler; use \Codeception\Util\Locator; use Yandex\Allure\Adapter\Annotation\Features; use Yandex\Allure\Adapter\Annotation\Stories; @@ -15,39 +13,52 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/LocatorFunctionTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/LocatorFunctionTest.xml<br>") */ class LocatorFunctionTestCest { + /** + * @var bool + */ + private $isSuccess = false; + + /** + * @param AcceptanceTester $I + * @throws \Exception + */ + public function _after(AcceptanceTester $I) + { + if ($this->isSuccess) { + unlink(__FILE__); + } + } + /** * @Features({"TestModule"}) - * @Parameter(name = "AcceptanceTester", value="$I") * @param AcceptanceTester $I * @return void * @throws \Exception */ public function LocatorFunctionTest(AcceptanceTester $I) { - $I->comment("[data] create 'ReplacementPerson' entity"); - PersistedObjectHandler::getInstance()->createEntity( - "data", - "test", - "ReplacementPerson", - [], - [] - ); - + $I->createEntity("data", "test", "ReplacementPerson", [], []); // stepKey: data $I->click(Locator::contains("'label'", "'Name'")); // stepKey: SimpleLocator $I->click(Locator::contains("'label'", "'Name'")); // stepKey: SimpleLocatorNonShorthand $I->click(Locator::find("'img'", ['title' => 'diagram'])); // stepKey: ArrayLocator $I->click(Locator::contains("string", "'Name'")); // stepKey: OneParamLiteral $I->click(Locator::contains("John", "'Name'")); // stepKey: OneParamData - $I->click(Locator::contains(PersistedObjectHandler::getInstance()->retrieveEntityField('data', 'key', 'test'), "'Name'")); // stepKey: OneParamPersisted + $I->click(Locator::contains($I->retrieveEntityField('data', 'key', 'test'), "'Name'")); // stepKey: OneParamPersisted $I->click(Locator::contains("string1", "string2")); // stepKey: TwoParamLiteral $I->click(Locator::contains("John", "Doe")); // stepKey: TwoParamData - $I->click(Locator::contains(PersistedObjectHandler::getInstance()->retrieveEntityField('data', 'key1', 'test'), PersistedObjectHandler::getInstance()->retrieveEntityField('data', 'key2', 'test'))); // stepKey: TwoParamPersisted + $I->click(Locator::contains($I->retrieveEntityField('data', 'key1', 'test'), $I->retrieveEntityField('data', 'key2', 'test'))); // stepKey: TwoParamPersisted $I->click(Locator::contains("string1", "John")); // stepKey: TwoParamMix1 - $I->click(Locator::contains("string1", PersistedObjectHandler::getInstance()->retrieveEntityField('data', 'key1', 'test'))); // stepKey: TwoParamMix2 - $I->click(Locator::contains("John", PersistedObjectHandler::getInstance()->retrieveEntityField('data', 'key1', 'test'))); // stepKey: TwoParamMix3 + $I->click(Locator::contains("string1", $I->retrieveEntityField('data', 'key1', 'test'))); // stepKey: TwoParamMix2 + $I->click(Locator::contains("John", $I->retrieveEntityField('data', 'key1', 'test'))); // stepKey: TwoParamMix3 + } + + public function _passed(AcceptanceTester $I) + { + // Test passed successfully. + $this->isSuccess = true; } } diff --git a/dev/tests/verification/Resources/MergeMassViaInsertAfter.txt b/dev/tests/verification/Resources/MergeMassViaInsertAfter.txt index 7d3bfec09..60cfdb410 100644 --- a/dev/tests/verification/Resources/MergeMassViaInsertAfter.txt +++ b/dev/tests/verification/Resources/MergeMassViaInsertAfter.txt @@ -2,8 +2,6 @@ namespace Magento\AcceptanceTest\_default\Backend; use Magento\FunctionalTestingFramework\AcceptanceTester; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\CredentialStore; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\PersistedObjectHandler; use \Codeception\Util\Locator; use Yandex\Allure\Adapter\Annotation\Features; use Yandex\Allure\Adapter\Annotation\Stories; @@ -15,13 +13,28 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/BasicFunctionalTest.xml<br>verification/TestModule/Test/MergeFunctionalTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/BasicFunctionalTest/MergeMassViaInsertAfter.xml<br>verification/TestModule/Test/MergeFunctionalTest/MergeMassViaInsertAfter.xml<br>") */ class MergeMassViaInsertAfterCest { + /** + * @var bool + */ + private $isSuccess = false; + + /** + * @param AcceptanceTester $I + * @throws \Exception + */ + public function _after(AcceptanceTester $I) + { + if ($this->isSuccess) { + unlink(__FILE__); + } + } + /** * @Features({"TestModule"}) - * @Parameter(name = "AcceptanceTester", value="$I") * @param AcceptanceTester $I * @return void * @throws \Exception @@ -35,4 +48,10 @@ class MergeMassViaInsertAfterCest $I->click("#mergeThree"); // stepKey: clickThree $I->fillField("#baz", "baz"); // stepKey: fillField3 } + + public function _passed(AcceptanceTester $I) + { + // Test passed successfully. + $this->isSuccess = true; + } } diff --git a/dev/tests/verification/Resources/MergeMassViaInsertBefore.txt b/dev/tests/verification/Resources/MergeMassViaInsertBefore.txt index 42a0fcf89..eb6be579b 100644 --- a/dev/tests/verification/Resources/MergeMassViaInsertBefore.txt +++ b/dev/tests/verification/Resources/MergeMassViaInsertBefore.txt @@ -2,8 +2,6 @@ namespace Magento\AcceptanceTest\_default\Backend; use Magento\FunctionalTestingFramework\AcceptanceTester; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\CredentialStore; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\PersistedObjectHandler; use \Codeception\Util\Locator; use Yandex\Allure\Adapter\Annotation\Features; use Yandex\Allure\Adapter\Annotation\Stories; @@ -15,13 +13,28 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/BasicFunctionalTest.xml<br>verification/TestModule/Test/MergeFunctionalTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/BasicFunctionalTest/MergeMassViaInsertBefore.xml<br>verification/TestModule/Test/MergeFunctionalTest/MergeMassViaInsertBefore.xml<br>") */ class MergeMassViaInsertBeforeCest { + /** + * @var bool + */ + private $isSuccess = false; + + /** + * @param AcceptanceTester $I + * @throws \Exception + */ + public function _after(AcceptanceTester $I) + { + if ($this->isSuccess) { + unlink(__FILE__); + } + } + /** * @Features({"TestModule"}) - * @Parameter(name = "AcceptanceTester", value="$I") * @param AcceptanceTester $I * @return void * @throws \Exception @@ -35,4 +48,10 @@ class MergeMassViaInsertBeforeCest $I->fillField("#bar", "bar"); // stepKey: fillField2 $I->fillField("#baz", "baz"); // stepKey: fillField3 } + + public function _passed(AcceptanceTester $I) + { + // Test passed successfully. + $this->isSuccess = true; + } } diff --git a/dev/tests/verification/Resources/MergeSkip.txt b/dev/tests/verification/Resources/MergeSkip.txt index fdfbdbfe4..498aa1054 100644 --- a/dev/tests/verification/Resources/MergeSkip.txt +++ b/dev/tests/verification/Resources/MergeSkip.txt @@ -2,8 +2,6 @@ namespace Magento\AcceptanceTest\_default\Backend; use Magento\FunctionalTestingFramework\AcceptanceTester; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\CredentialStore; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\PersistedObjectHandler; use \Codeception\Util\Locator; use Yandex\Allure\Adapter\Annotation\Features; use Yandex\Allure\Adapter\Annotation\Stories; @@ -15,19 +13,24 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/MergeFunctionalTest.xml<br>verification/TestModuleMerged/Test/MergeFunctionalTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/MergeFunctionalTest/MergeSkip.xml<br>verification/TestModuleMerged/Test/MergeFunctionalTest/MergeSkip.xml<br>") */ class MergeSkipCest { + /** + * @var bool + */ + private $isSuccess = false; + /** * @Features({"TestModule"}) - * @Parameter(name = "AcceptanceTester", value="$I") * @param AcceptanceTester $I * @return void * @throws \Exception */ public function MergeSkip(AcceptanceTester $I, \Codeception\Scenario $scenario) { + unlink(__FILE__); $scenario->skip("This test is skipped due to the following issues:\nIssue5"); } } diff --git a/dev/tests/verification/Resources/MergedActionGroupReturningValueTest.txt b/dev/tests/verification/Resources/MergedActionGroupReturningValueTest.txt new file mode 100644 index 000000000..68fec4cfa --- /dev/null +++ b/dev/tests/verification/Resources/MergedActionGroupReturningValueTest.txt @@ -0,0 +1,94 @@ +<?php +namespace Magento\AcceptanceTest\_default\Backend; + +use Magento\FunctionalTestingFramework\AcceptanceTester; +use \Codeception\Util\Locator; +use Yandex\Allure\Adapter\Annotation\Features; +use Yandex\Allure\Adapter\Annotation\Stories; +use Yandex\Allure\Adapter\Annotation\Title; +use Yandex\Allure\Adapter\Annotation\Description; +use Yandex\Allure\Adapter\Annotation\Parameter; +use Yandex\Allure\Adapter\Annotation\Severity; +use Yandex\Allure\Adapter\Model\SeverityLevel; +use Yandex\Allure\Adapter\Annotation\TestCaseId; + +/** + * @group functional + * @Description("<h3>Test files</h3>verification/TestModule/Test/MergeFunctionalTest/MergedActionGroupReturningValueTest.xml<br>") + */ +class MergedActionGroupReturningValueTestCest +{ + /** + * @var bool + */ + private $isSuccess = false; + + /** + * @param AcceptanceTester $I + * @throws \Exception + */ + public function _before(AcceptanceTester $I) + { + $I->comment('[START BEFORE HOOK]'); + $I->createEntity("createPersonParam", "hook", "ReplacementPerson", [], []); // stepKey: createPersonParam + $I->comment("Entering Action Group [beforeGroup] FunctionalActionGroup"); + $I->fillField("#foo", "myData1"); // stepKey: fillField1BeforeGroup + $I->fillField("#bar", "myData2"); // stepKey: fillField2BeforeGroup + $I->comment("Exiting Action Group [beforeGroup] FunctionalActionGroup"); + $I->comment('[END BEFORE HOOK]'); + } + + /** + * @param AcceptanceTester $I + * @throws \Exception + */ + public function _after(AcceptanceTester $I) + { + $I->comment('[START AFTER HOOK]'); + $I->comment("Entering Action Group [afterGroup] FunctionalActionGroup"); + $I->fillField("#foo", "myData1"); // stepKey: fillField1AfterGroup + $I->fillField("#bar", "myData2"); // stepKey: fillField2AfterGroup + $I->comment("Exiting Action Group [afterGroup] FunctionalActionGroup"); + $I->comment('[END AFTER HOOK]'); + if ($this->isSuccess) { + unlink(__FILE__); + } + } + + /** + * @param AcceptanceTester $I + * @throws \Exception + */ + public function _failed(AcceptanceTester $I) + { + $I->saveScreenshot(); // stepKey: saveScreenshot + } + + /** + * @Severity(level = SeverityLevel::CRITICAL) + * @Features({"TestModule"}) + * @Stories({"MQE-433"}) + * @param AcceptanceTester $I + * @return void + * @throws \Exception + */ + public function MergedActionGroupReturningValueTest(AcceptanceTester $I) + { + $I->amOnPage("/someUrl"); // stepKey: step1 + $I->comment("Entering Action Group [actionGroupWithReturnValue1] MergeActionGroupReturningValueActionGroup"); + $I->amOnPage("/Jane/Dane.html"); // stepKey: amOnPage1ActionGroupWithReturnValue1 + $I->click(".merge .Jane"); // stepKey: myMergedClickActionGroupWithReturnValue1 + $grabMultiple1ActionGroupWithReturnValue1 = $I->grabMultiple("#foo"); // stepKey: grabMultiple1ActionGroupWithReturnValue1 + $actionGroupWithReturnValue1 = $I->return($grabMultiple1ActionGroupWithReturnValue1); // stepKey: returnValueActionGroupWithReturnValue1 + $I->comment("Exiting Action Group [actionGroupWithReturnValue1] MergeActionGroupReturningValueActionGroup"); + $I->comment("Entering Action Group [actionGroupWithStringUsage1] actionGroupWithStringUsage"); + $I->see($actionGroupWithReturnValue1, "#element .{$actionGroupWithReturnValue1}"); // stepKey: see1ActionGroupWithStringUsage1 + $I->comment("Exiting Action Group [actionGroupWithStringUsage1] actionGroupWithStringUsage"); + } + + public function _passed(AcceptanceTester $I) + { + // Test passed successfully. + $this->isSuccess = true; + } +} diff --git a/dev/tests/verification/Resources/MergedActionGroupTest.txt b/dev/tests/verification/Resources/MergedActionGroupTest.txt index 636c28627..a4abf2820 100644 --- a/dev/tests/verification/Resources/MergedActionGroupTest.txt +++ b/dev/tests/verification/Resources/MergedActionGroupTest.txt @@ -2,8 +2,6 @@ namespace Magento\AcceptanceTest\_default\Backend; use Magento\FunctionalTestingFramework\AcceptanceTester; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\CredentialStore; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\PersistedObjectHandler; use \Codeception\Util\Locator; use Yandex\Allure\Adapter\Annotation\Features; use Yandex\Allure\Adapter\Annotation\Stories; @@ -16,29 +14,28 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @group functional - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/ActionGroupFunctionalTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/ActionGroupFunctionalTest/MergedActionGroupTest.xml<br>") */ class MergedActionGroupTestCest { + /** + * @var bool + */ + private $isSuccess = false; + /** * @param AcceptanceTester $I * @throws \Exception */ public function _before(AcceptanceTester $I) { - $I->comment("[createPersonParam] create 'ReplacementPerson' entity"); - PersistedObjectHandler::getInstance()->createEntity( - "createPersonParam", - "hook", - "ReplacementPerson", - [], - [] - ); - + $I->comment('[START BEFORE HOOK]'); + $I->createEntity("createPersonParam", "hook", "ReplacementPerson", [], []); // stepKey: createPersonParam $I->comment("Entering Action Group [beforeGroup] FunctionalActionGroup"); $I->fillField("#foo", "myData1"); // stepKey: fillField1BeforeGroup $I->fillField("#bar", "myData2"); // stepKey: fillField2BeforeGroup $I->comment("Exiting Action Group [beforeGroup] FunctionalActionGroup"); + $I->comment('[END BEFORE HOOK]'); } /** @@ -47,10 +44,15 @@ class MergedActionGroupTestCest */ public function _after(AcceptanceTester $I) { + $I->comment('[START AFTER HOOK]'); $I->comment("Entering Action Group [afterGroup] FunctionalActionGroup"); $I->fillField("#foo", "myData1"); // stepKey: fillField1AfterGroup $I->fillField("#bar", "myData2"); // stepKey: fillField2AfterGroup $I->comment("Exiting Action Group [afterGroup] FunctionalActionGroup"); + $I->comment('[END AFTER HOOK]'); + if ($this->isSuccess) { + unlink(__FILE__); + } } /** @@ -66,7 +68,6 @@ class MergedActionGroupTestCest * @Severity(level = SeverityLevel::CRITICAL) * @Features({"TestModule"}) * @Stories({"MQE-433"}) - * @Parameter(name = "AcceptanceTester", value="$I") * @param AcceptanceTester $I * @return void * @throws \Exception @@ -80,4 +81,10 @@ class MergedActionGroupTestCest $I->click(".merge .Dane"); // stepKey: myMergedClickActionGroupForMerge $I->comment("Exiting Action Group [actionGroupForMerge] FunctionalActionGroupForMerge"); } + + public function _passed(AcceptanceTester $I) + { + // Test passed successfully. + $this->isSuccess = true; + } } diff --git a/dev/tests/verification/Resources/MergedReferencesTest.txt b/dev/tests/verification/Resources/MergedReferencesTest.txt index 794ba6370..40c9b3cdb 100644 --- a/dev/tests/verification/Resources/MergedReferencesTest.txt +++ b/dev/tests/verification/Resources/MergedReferencesTest.txt @@ -2,8 +2,6 @@ namespace Magento\AcceptanceTest\_default\Backend; use Magento\FunctionalTestingFramework\AcceptanceTester; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\CredentialStore; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\PersistedObjectHandler; use \Codeception\Util\Locator; use Yandex\Allure\Adapter\Annotation\Features; use Yandex\Allure\Adapter\Annotation\Stories; @@ -17,17 +15,24 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @Title("[NO TESTCASEID]: MergedReferencesTest") * @group functional - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/MergeFunctionalTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/MergeFunctionalTest/MergedReferencesTest.xml<br>") */ class MergedReferencesTestCest { + /** + * @var bool + */ + private $isSuccess = false; + /** * @param AcceptanceTester $I * @throws \Exception */ public function _before(AcceptanceTester $I) { + $I->comment('[START BEFORE HOOK]'); $I->amOnPage("/beforeUrl"); // stepKey: before1 + $I->comment('[END BEFORE HOOK]'); } /** @@ -36,7 +41,12 @@ class MergedReferencesTestCest */ public function _after(AcceptanceTester $I) { + $I->comment('[START AFTER HOOK]'); $I->amOnPage("/afterUrl"); // stepKey: after1 + $I->comment('[END AFTER HOOK]'); + if ($this->isSuccess) { + unlink(__FILE__); + } } /** @@ -52,7 +62,6 @@ class MergedReferencesTestCest * @Severity(level = SeverityLevel::CRITICAL) * @Features({"TestModule"}) * @Stories({"MQE-433"}) - * @Parameter(name = "AcceptanceTester", value="$I") * @param AcceptanceTester $I * @return void * @throws \Exception @@ -62,4 +71,10 @@ class MergedReferencesTestCest $I->fillField("#merge", "merged"); // stepKey: fillField1 $I->fillField("#newElement", "newField"); // stepKey: fillField2 } + + public function _passed(AcceptanceTester $I) + { + // Test passed successfully. + $this->isSuccess = true; + } } diff --git a/dev/tests/verification/Resources/MultipleActionGroupsTest.txt b/dev/tests/verification/Resources/MultipleActionGroupsTest.txt index 186cd404c..02ffcdf84 100644 --- a/dev/tests/verification/Resources/MultipleActionGroupsTest.txt +++ b/dev/tests/verification/Resources/MultipleActionGroupsTest.txt @@ -2,8 +2,6 @@ namespace Magento\AcceptanceTest\_default\Backend; use Magento\FunctionalTestingFramework\AcceptanceTester; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\CredentialStore; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\PersistedObjectHandler; use \Codeception\Util\Locator; use Yandex\Allure\Adapter\Annotation\Features; use Yandex\Allure\Adapter\Annotation\Stories; @@ -16,29 +14,28 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @group functional - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/ActionGroupFunctionalTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/ActionGroupFunctionalTest/MultipleActionGroupsTest.xml<br>") */ class MultipleActionGroupsTestCest { + /** + * @var bool + */ + private $isSuccess = false; + /** * @param AcceptanceTester $I * @throws \Exception */ public function _before(AcceptanceTester $I) { - $I->comment("[createPersonParam] create 'ReplacementPerson' entity"); - PersistedObjectHandler::getInstance()->createEntity( - "createPersonParam", - "hook", - "ReplacementPerson", - [], - [] - ); - + $I->comment('[START BEFORE HOOK]'); + $I->createEntity("createPersonParam", "hook", "ReplacementPerson", [], []); // stepKey: createPersonParam $I->comment("Entering Action Group [beforeGroup] FunctionalActionGroup"); $I->fillField("#foo", "myData1"); // stepKey: fillField1BeforeGroup $I->fillField("#bar", "myData2"); // stepKey: fillField2BeforeGroup $I->comment("Exiting Action Group [beforeGroup] FunctionalActionGroup"); + $I->comment('[END BEFORE HOOK]'); } /** @@ -47,10 +44,15 @@ class MultipleActionGroupsTestCest */ public function _after(AcceptanceTester $I) { + $I->comment('[START AFTER HOOK]'); $I->comment("Entering Action Group [afterGroup] FunctionalActionGroup"); $I->fillField("#foo", "myData1"); // stepKey: fillField1AfterGroup $I->fillField("#bar", "myData2"); // stepKey: fillField2AfterGroup $I->comment("Exiting Action Group [afterGroup] FunctionalActionGroup"); + $I->comment('[END AFTER HOOK]'); + if ($this->isSuccess) { + unlink(__FILE__); + } } /** @@ -66,7 +68,6 @@ class MultipleActionGroupsTestCest * @Severity(level = SeverityLevel::CRITICAL) * @Features({"TestModule"}) * @Stories({"MQE-433"}) - * @Parameter(name = "AcceptanceTester", value="$I") * @param AcceptanceTester $I * @return void * @throws \Exception @@ -90,4 +91,10 @@ class MultipleActionGroupsTestCest $I->see("#element .John"); // stepKey: see1ActionGroupWithDataOverride2 $I->comment("Exiting Action Group [actionGroupWithDataOverride2] FunctionalActionGroupWithData"); } + + public function _passed(AcceptanceTester $I) + { + // Test passed successfully. + $this->isSuccess = true; + } } diff --git a/dev/tests/verification/Resources/PageReplacementTest.txt b/dev/tests/verification/Resources/PageReplacementTest.txt index f6c6b1bac..097a8be02 100644 --- a/dev/tests/verification/Resources/PageReplacementTest.txt +++ b/dev/tests/verification/Resources/PageReplacementTest.txt @@ -2,8 +2,6 @@ namespace Magento\AcceptanceTest\_default\Backend; use Magento\FunctionalTestingFramework\AcceptanceTester; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\CredentialStore; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\PersistedObjectHandler; use \Codeception\Util\Locator; use Yandex\Allure\Adapter\Annotation\Features; use Yandex\Allure\Adapter\Annotation\Stories; @@ -15,38 +13,51 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/PageReplacementTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/PageReplacementTest.xml<br>") */ class PageReplacementTestCest { + /** + * @var bool + */ + private $isSuccess = false; + + /** + * @param AcceptanceTester $I + * @throws \Exception + */ + public function _after(AcceptanceTester $I) + { + if ($this->isSuccess) { + unlink(__FILE__); + } + } + /** * @Features({"TestModule"}) - * @Parameter(name = "AcceptanceTester", value="$I") * @param AcceptanceTester $I * @return void * @throws \Exception */ public function PageReplacementTest(AcceptanceTester $I) { - $I->comment("[datakey] create 'simpleData' entity"); - PersistedObjectHandler::getInstance()->createEntity( - "datakey", - "test", - "simpleData", - [], - [] - ); - + $I->createEntity("datakey", "test", "simpleData", [], []); // stepKey: datakey $I->amOnPage("/page.html"); // stepKey: noParamPage $I->amOnPage("/StringLiteral/page.html"); // stepKey: oneParamPageString $I->amOnPage("/John/page.html"); // stepKey: oneParamPageData - $I->amOnPage("/" . PersistedObjectHandler::getInstance()->retrieveEntityField('datakey', 'firstname', 'test') . "/page.html"); // stepKey: oneParamPagePersist + $I->amOnPage("/" . $I->retrieveEntityField('datakey', 'firstname', 'test') . "/page.html"); // stepKey: oneParamPagePersist $I->amOnPage("/StringLiteral1/StringLiteral2.html"); // stepKey: twoParamPageString $I->amOnPage("/John/StringLiteral2.html"); // stepKey: twoParamPageStringData - $I->amOnPage("/John/" . PersistedObjectHandler::getInstance()->retrieveEntityField('datakey', 'firstname', 'test') . ".html"); // stepKey: twoParamPageDataPersist - $I->amOnPage("/" . PersistedObjectHandler::getInstance()->retrieveEntityField('datakey', 'firstname', 'test') . "/StringLiteral2.html"); // stepKey: twoParamPagePersistString - $I->amOnPage("/" . getenv("MAGENTO_BACKEND_NAME") . "/backend"); // stepKey: onAdminPage - $I->amOnPage("/" . getenv("MAGENTO_BACKEND_NAME") . "/StringLiteral/page.html"); // stepKey: oneParamAdminPageString + $I->amOnPage("/John/" . $I->retrieveEntityField('datakey', 'firstname', 'test') . ".html"); // stepKey: twoParamPageDataPersist + $I->amOnPage("/" . $I->retrieveEntityField('datakey', 'firstname', 'test') . "/StringLiteral2.html"); // stepKey: twoParamPagePersistString + $I->amOnPage((getenv("MAGENTO_BACKEND_BASE_URL") ? rtrim(getenv("MAGENTO_BACKEND_BASE_URL"), "/") : "") . "/" . getenv("MAGENTO_BACKEND_NAME") . "/backend"); // stepKey: onAdminPage + $I->amOnPage((getenv("MAGENTO_BACKEND_BASE_URL") ? rtrim(getenv("MAGENTO_BACKEND_BASE_URL"), "/") : "") . "/" . getenv("MAGENTO_BACKEND_NAME") . "/StringLiteral/page.html"); // stepKey: oneParamAdminPageString $I->amOnUrl("http://myFullUrl.com/"); // stepKey: onExternalPage } + + public function _passed(AcceptanceTester $I) + { + // Test passed successfully. + $this->isSuccess = true; + } } diff --git a/dev/tests/verification/Resources/ParameterArrayTest.txt b/dev/tests/verification/Resources/ParameterArrayTest.txt index 52332217b..1439dacdc 100644 --- a/dev/tests/verification/Resources/ParameterArrayTest.txt +++ b/dev/tests/verification/Resources/ParameterArrayTest.txt @@ -2,8 +2,6 @@ namespace Magento\AcceptanceTest\_default\Backend; use Magento\FunctionalTestingFramework\AcceptanceTester; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\CredentialStore; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\PersistedObjectHandler; use \Codeception\Util\Locator; use Yandex\Allure\Adapter\Annotation\Features; use Yandex\Allure\Adapter\Annotation\Stories; @@ -15,34 +13,41 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/ParameterArrayTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/ParameterArrayTest.xml<br>") */ class ParameterArrayTestCest { + /** + * @var bool + */ + private $isSuccess = false; + + /** + * @param AcceptanceTester $I + * @throws \Exception + */ + public function _after(AcceptanceTester $I) + { + if ($this->isSuccess) { + unlink(__FILE__); + } + } + /** * @Features({"TestModule"}) - * @Parameter(name = "AcceptanceTester", value="$I") * @param AcceptanceTester $I * @return void * @throws \Exception */ public function ParameterArrayTest(AcceptanceTester $I) { - $I->comment("[simpleDataKey] create 'simpleParamData' entity"); - PersistedObjectHandler::getInstance()->createEntity( - "simpleDataKey", - "test", - "simpleParamData", - [], - [] - ); - + $I->createEntity("simpleDataKey", "test", "simpleParamData", [], []); // stepKey: simpleDataKey $I->searchAndMultiSelectOption("#selector", ["name"]); // stepKey: xmlSimpleReplace $I->searchAndMultiSelectOption("#selector", [msq("simpleParamData") . "prename"]); // stepKey: xmlPrefix $I->searchAndMultiSelectOption("#selector", ["postname" . msq("simpleParamData")]); // stepKey: xmlSuffix - $I->searchAndMultiSelectOption("#selector", [PersistedObjectHandler::getInstance()->retrieveEntityField('simpleDataKey', 'name', 'test')]); // stepKey: persistSimple - $I->searchAndMultiSelectOption("#selector", ["name", PersistedObjectHandler::getInstance()->retrieveEntityField('simpleDataKey', 'name', 'test')]); // stepKey: persistXmlSimple - $I->searchAndMultiSelectOption("#selector", ['someKey' => PersistedObjectHandler::getInstance()->retrieveEntityField('simpleDataKey', 'name', 'test')]); // stepKey: literalKeyToPersist + $I->searchAndMultiSelectOption("#selector", [$I->retrieveEntityField('simpleDataKey', 'name', 'test')]); // stepKey: persistSimple + $I->searchAndMultiSelectOption("#selector", ["name", $I->retrieveEntityField('simpleDataKey', 'name', 'test')]); // stepKey: persistXmlSimple + $I->searchAndMultiSelectOption("#selector", ['someKey' => $I->retrieveEntityField('simpleDataKey', 'name', 'test')]); // stepKey: literalKeyToPersist $I->searchAndMultiSelectOption("#selector", ['someKey' => "name"]); // stepKey: literalKeyToStatic $I->searchAndMultiSelectOption("#selector", ['someKey' => msq("simpleParamData") . "prename"]); // stepKey: literalKeyToPrefixUnique $I->searchAndMultiSelectOption("#selector", ['someKey' => "postname" . msq("simpleParamData")]); // stepKey: literalKeyToSuffixUnique @@ -51,13 +56,19 @@ class ParameterArrayTestCest $I->unselectOption("#selector", ["name"]); // stepKey: 002 $I->unselectOption("#selector", [msq("simpleParamData") . "prename"]); // stepKey: 003 $I->unselectOption("#selector", ["postname" . msq("simpleParamData")]); // stepKey: 004 - $I->unselectOption("#selector", [PersistedObjectHandler::getInstance()->retrieveEntityField('simpleDataKey', 'name', 'test')]); // stepKey: 005 - $I->unselectOption("#selector", ["name", PersistedObjectHandler::getInstance()->retrieveEntityField('simpleDataKey', 'name', 'test')]); // stepKey: 006 - $I->pressKey("#selector", PersistedObjectHandler::getInstance()->retrieveEntityField('simpleDataKey', 'name', 'test'), ['ctrl', 'a'],\Facebook\WebDriver\WebDriverKeys::DELETE,PersistedObjectHandler::getInstance()->retrieveEntityField('simpleDataKey', 'name', 'test')); // stepKey: pressKey001 - $I->pressKey("#selector", ['ctrl', 'a'], 10, 20,\Facebook\WebDriver\WebDriverKeys::DELETE,PersistedObjectHandler::getInstance()->retrieveEntityField('simpleDataKey', 'name', 'test')); // stepKey: pressKey002 - $I->pressKey("#selector", ['ctrl', 'a'],'new', 10, 20,\Facebook\WebDriver\WebDriverKeys::DELETE,PersistedObjectHandler::getInstance()->retrieveEntityField('simpleDataKey', 'name', 'test')); // stepKey: pressKey003 - $I->pressKey("#selector", ['ctrl', 'a'],'new', 1, ['ctrl'], ['shift', 'ctrl', 'del'], [PersistedObjectHandler::getInstance()->retrieveEntityField('simpleDataKey', 'name', 'test'), 'a', "name"]); // stepKey: pressKey004 - $I->pressKey("#selector", ['ctrl', 'a'],'new', 1, ['ctrl'], ['shift', 'ctrl', 'del'], 0, [PersistedObjectHandler::getInstance()->retrieveEntityField('simpleDataKey', 'name', 'test'), PersistedObjectHandler::getInstance()->retrieveEntityField('simpleDataKey', 'name', 'test')]); // stepKey: pressKey005 + $I->unselectOption("#selector", [$I->retrieveEntityField('simpleDataKey', 'name', 'test')]); // stepKey: 005 + $I->unselectOption("#selector", ["name", $I->retrieveEntityField('simpleDataKey', 'name', 'test')]); // stepKey: 006 + $I->pressKey("#selector", $I->retrieveEntityField('simpleDataKey', 'name', 'test'), ['ctrl', 'a'],\Facebook\WebDriver\WebDriverKeys::DELETE,$I->retrieveEntityField('simpleDataKey', 'name', 'test')); // stepKey: pressKey001 + $I->pressKey("#selector", ['ctrl', 'a'], 10, 20,\Facebook\WebDriver\WebDriverKeys::DELETE,$I->retrieveEntityField('simpleDataKey', 'name', 'test')); // stepKey: pressKey002 + $I->pressKey("#selector", ['ctrl', 'a'],'new', 10, 20,\Facebook\WebDriver\WebDriverKeys::DELETE,$I->retrieveEntityField('simpleDataKey', 'name', 'test')); // stepKey: pressKey003 + $I->pressKey("#selector", ['ctrl', 'a'],'new', 1, ['ctrl'], ['shift', 'ctrl', 'del'], [$I->retrieveEntityField('simpleDataKey', 'name', 'test'), 'a', "name"]); // stepKey: pressKey004 + $I->pressKey("#selector", ['ctrl', 'a'],'new', 1, ['ctrl'], ['shift', 'ctrl', 'del'], 0, [$I->retrieveEntityField('simpleDataKey', 'name', 'test'), $I->retrieveEntityField('simpleDataKey', 'name', 'test')]); // stepKey: pressKey005 $I->pressKey("#selector", ['ctrl', 'a'],'new', 1, ['ctrl'], ['shift', 'ctrl', 'del'], [msq("simpleParamData") . "prename", "postname" . msq("simpleParamData")]); // stepKey: pressKey006 } + + public function _passed(AcceptanceTester $I) + { + // Test passed successfully. + $this->isSuccess = true; + } } diff --git a/dev/tests/verification/Resources/ParentExtendedTest.txt b/dev/tests/verification/Resources/ParentExtendedTest.txt index aeae567a2..a06efd830 100644 --- a/dev/tests/verification/Resources/ParentExtendedTest.txt +++ b/dev/tests/verification/Resources/ParentExtendedTest.txt @@ -2,8 +2,6 @@ namespace Magento\AcceptanceTest\_default\Backend; use Magento\FunctionalTestingFramework\AcceptanceTester; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\CredentialStore; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\PersistedObjectHandler; use \Codeception\Util\Locator; use Yandex\Allure\Adapter\Annotation\Features; use Yandex\Allure\Adapter\Annotation\Stories; @@ -17,17 +15,24 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @Title("[NO TESTCASEID]: ParentExtendedTest") * @group Parent - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/ExtendedFunctionalTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/ExtendedFunctionalTest/ParentExtendedTest.xml<br>") */ class ParentExtendedTestCest { + /** + * @var bool + */ + private $isSuccess = false; + /** * @param AcceptanceTester $I * @throws \Exception */ public function _before(AcceptanceTester $I) { + $I->comment('[START BEFORE HOOK]'); $I->amOnPage("/beforeUrl"); // stepKey: beforeAmOnPageKey + $I->comment('[END BEFORE HOOK]'); } /** @@ -36,7 +41,12 @@ class ParentExtendedTestCest */ public function _after(AcceptanceTester $I) { + $I->comment('[START AFTER HOOK]'); $I->amOnPage("/afterUrl"); // stepKey: afterAmOnPageKey + $I->comment('[END AFTER HOOK]'); + if ($this->isSuccess) { + unlink(__FILE__); + } } /** @@ -52,7 +62,6 @@ class ParentExtendedTestCest * @Severity(level = SeverityLevel::MINOR) * @Features({"TestModule"}) * @Stories({"Parent"}) - * @Parameter(name = "AcceptanceTester", value="$I") * @param AcceptanceTester $I * @return void * @throws \Exception @@ -61,4 +70,10 @@ class ParentExtendedTestCest { $I->comment("Parent Comment"); } + + public function _passed(AcceptanceTester $I) + { + // Test passed successfully. + $this->isSuccess = true; + } } diff --git a/dev/tests/verification/Resources/PersistedAndXmlEntityArguments.txt b/dev/tests/verification/Resources/PersistedAndXmlEntityArguments.txt index c6c09c9c7..35eb126cd 100644 --- a/dev/tests/verification/Resources/PersistedAndXmlEntityArguments.txt +++ b/dev/tests/verification/Resources/PersistedAndXmlEntityArguments.txt @@ -2,8 +2,6 @@ namespace Magento\AcceptanceTest\_default\Backend; use Magento\FunctionalTestingFramework\AcceptanceTester; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\CredentialStore; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\PersistedObjectHandler; use \Codeception\Util\Locator; use Yandex\Allure\Adapter\Annotation\Features; use Yandex\Allure\Adapter\Annotation\Stories; @@ -15,13 +13,28 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/ActionGroupFunctionalTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/ActionGroupFunctionalTest/PersistedAndXmlEntityArguments.xml<br>") */ class PersistedAndXmlEntityArgumentsCest { + /** + * @var bool + */ + private $isSuccess = false; + + /** + * @param AcceptanceTester $I + * @throws \Exception + */ + public function _after(AcceptanceTester $I) + { + if ($this->isSuccess) { + unlink(__FILE__); + } + } + /** * @Features({"TestModule"}) - * @Parameter(name = "AcceptanceTester", value="$I") * @param AcceptanceTester $I * @return void * @throws \Exception @@ -29,7 +42,13 @@ class PersistedAndXmlEntityArgumentsCest public function PersistedAndXmlEntityArguments(AcceptanceTester $I) { $I->comment("Entering Action Group [afterGroup] FunctionalActionGroupWithXmlAndPersistedData"); - $I->seeInCurrentUrl("/" . PersistedObjectHandler::getInstance()->retrieveEntityField('persistedInTest', 'urlKey', 'test') . ".html?___store=" . msq("uniqueData") . "John"); // stepKey: checkUrlAfterGroup + $I->seeInCurrentUrl("/" . $I->retrieveEntityField('persistedInTest', 'urlKey', 'test') . ".html?___store=" . msq("uniqueData") . "John"); // stepKey: checkUrlAfterGroup $I->comment("Exiting Action Group [afterGroup] FunctionalActionGroupWithXmlAndPersistedData"); } + + public function _passed(AcceptanceTester $I) + { + // Test passed successfully. + $this->isSuccess = true; + } } diff --git a/dev/tests/verification/Resources/PersistedReplacementTest.txt b/dev/tests/verification/Resources/PersistedReplacementTest.txt index eb3642ed1..0763d5da2 100644 --- a/dev/tests/verification/Resources/PersistedReplacementTest.txt +++ b/dev/tests/verification/Resources/PersistedReplacementTest.txt @@ -2,8 +2,6 @@ namespace Magento\AcceptanceTest\_default\Backend; use Magento\FunctionalTestingFramework\AcceptanceTester; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\CredentialStore; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\PersistedObjectHandler; use \Codeception\Util\Locator; use Yandex\Allure\Adapter\Annotation\Features; use Yandex\Allure\Adapter\Annotation\Stories; @@ -15,66 +13,73 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/PersistedReplacementTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/PersistedReplacementTest.xml<br>") */ class PersistedReplacementTestCest { + /** + * @var bool + */ + private $isSuccess = false; + /** * @param AcceptanceTester $I * @throws \Exception */ public function _before(AcceptanceTester $I) { - $I->comment("[createData1] create 'ReplacementPerson' entity"); - PersistedObjectHandler::getInstance()->createEntity( - "createData1", - "hook", - "ReplacementPerson", - [], - [] - ); + $I->comment('[START BEFORE HOOK]'); + $I->createEntity("createData1", "hook", "ReplacementPerson", [], []); // stepKey: createData1 + $I->comment('[END BEFORE HOOK]'); + } + /** + * @param AcceptanceTester $I + * @throws \Exception + */ + public function _after(AcceptanceTester $I) + { + if ($this->isSuccess) { + unlink(__FILE__); + } } /** * @Features({"TestModule"}) - * @Parameter(name = "AcceptanceTester", value="$I") * @param AcceptanceTester $I * @return void * @throws \Exception */ public function PersistedReplacementTest(AcceptanceTester $I) { - $I->comment("[createdData] create 'simpleData' entity"); - PersistedObjectHandler::getInstance()->createEntity( - "createdData", - "test", - "simpleData", - [], - [] - ); + $I->createEntity("createdData", "test", "simpleData", [], []); // stepKey: createdData + $I->fillField("#selector", "StringBefore " . $I->retrieveEntityField('createdData', 'firstname', 'test') . " StringAfter"); // stepKey: inputReplace + $I->fillField("#" . $I->retrieveEntityField('createdData', 'firstname', 'test'), "input"); // stepKey: selectorReplace + $I->fillField("#" . getenv("MAGENTO_BASE_URL") . "#" . $I->retrieveEntityField('createdData', 'firstname', 'test'), "input"); // stepKey: selectorReplace2 + $I->fillSecretField("#" . $I->getSecret("SECRET_PARAM") . "#" . $I->retrieveEntityField('createdData', 'firstname', 'test'), "input"); // stepKey: selectorReplace3 + $I->dragAndDrop("#" . $I->retrieveEntityField('createdData', 'firstname', 'test'), $I->retrieveEntityField('createdData', 'lastname', 'test')); // stepKey: selector12Replace + $I->conditionalClick($I->retrieveEntityField('createdData', 'lastname', 'test'), "#" . $I->retrieveEntityField('createdData', 'firstname', 'test'), true); // stepKey: dependentSelectorReplace + $I->amOnUrl($I->retrieveEntityField('createdData', 'firstname', 'test') . ".html"); // stepKey: urlReplace + $I->searchAndMultiSelectOption("#selector", [$I->retrieveEntityField('createdData', 'firstname', 'test'), $I->retrieveEntityField('createdData', 'lastname', 'test')]); // stepKey: parameterArrayReplacement + $I->fillField("#selector", "John " . $I->retrieveEntityField('createdData', 'firstname', 'test') . " stringLiteral"); // stepKey: allTypesMixed + $I->searchAndMultiSelectOption("#selector", [$I->retrieveEntityField('createdData', 'firstname', 'test'), "John", "stringLiteral"]); // stepKey: parameterArrayMixed + $I->seeInPageSource("StringBefore " . $I->retrieveEntityField('createdData', 'firstname', 'test') . " StringAfter"); // stepKey: htmlReplace1 + $I->seeInPageSource("StringBefore " . $I->retrieveEntityField('createData1', 'firstname', 'test') . " StringAfter"); // stepKey: htmlReplace2 + $I->seeInPageSource("#" . getenv("MAGENTO_BASE_URL") . "#" . $I->retrieveEntityField('createdData', 'firstname', 'test')); // stepKey: htmlReplace3 + $I->dontSeeInPageSource("StringBefore " . $I->retrieveEntityField('createdData', 'firstname', 'test') . " StringAfter"); // stepKey: htmlReplace4 + $I->dontSeeInPageSource("StringBefore " . $I->retrieveEntityField('createData1', 'firstname', 'test') . " StringAfter"); // stepKey: htmlReplace5 + $I->dontSeeInPageSource("#" . getenv("MAGENTO_BASE_URL") . "#" . $I->retrieveEntityField('createdData', 'firstname', 'test')); // stepKey: htmlReplace6 + $I->seeInSource("StringBefore " . $I->retrieveEntityField('createdData', 'firstname', 'test') . " StringAfter"); // stepKey: htmlReplace7 + $I->seeInSource("StringBefore " . $I->retrieveEntityField('createData1', 'firstname', 'test') . " StringAfter"); // stepKey: htmlReplace8 + $I->seeInSource("#" . getenv("MAGENTO_BASE_URL") . "#" . $I->retrieveEntityField('createdData', 'firstname', 'test')); // stepKey: htmlReplace9 + $I->dontSeeInSource("StringBefore " . $I->retrieveEntityField('createdData', 'firstname', 'test') . " StringAfter"); // stepKey: htmlReplace10 + $I->dontSeeInSource("StringBefore " . $I->retrieveEntityField('createData1', 'firstname', 'test') . " StringAfter"); // stepKey: htmlReplace11 + $I->dontSeeInSource("#" . getenv("MAGENTO_BASE_URL") . "#" . $I->retrieveEntityField('createdData', 'firstname', 'test')); // stepKey: htmlReplace12 + } - $I->fillField("#selector", "StringBefore " . PersistedObjectHandler::getInstance()->retrieveEntityField('createdData', 'firstname', 'test') . " StringAfter"); // stepKey: inputReplace - $I->fillField("#" . PersistedObjectHandler::getInstance()->retrieveEntityField('createdData', 'firstname', 'test'), "input"); // stepKey: selectorReplace - $I->fillField("#" . getenv("MAGENTO_BASE_URL") . "#" . PersistedObjectHandler::getInstance()->retrieveEntityField('createdData', 'firstname', 'test'), "input"); // stepKey: selectorReplace2 - $I->fillSecretField("#" . CredentialStore::getInstance()->getSecret("SECRET_PARAM") . "#" . PersistedObjectHandler::getInstance()->retrieveEntityField('createdData', 'firstname', 'test'), "input"); // stepKey: selectorReplace3 - $I->dragAndDrop("#" . PersistedObjectHandler::getInstance()->retrieveEntityField('createdData', 'firstname', 'test'), PersistedObjectHandler::getInstance()->retrieveEntityField('createdData', 'lastname', 'test')); // stepKey: selector12Replace - $I->conditionalClick(PersistedObjectHandler::getInstance()->retrieveEntityField('createdData', 'lastname', 'test'), "#" . PersistedObjectHandler::getInstance()->retrieveEntityField('createdData', 'firstname', 'test'), true); // stepKey: dependentSelectorReplace - $I->amOnUrl(PersistedObjectHandler::getInstance()->retrieveEntityField('createdData', 'firstname', 'test') . ".html"); // stepKey: urlReplace - $I->searchAndMultiSelectOption("#selector", [PersistedObjectHandler::getInstance()->retrieveEntityField('createdData', 'firstname', 'test'), PersistedObjectHandler::getInstance()->retrieveEntityField('createdData', 'lastname', 'test')]); // stepKey: parameterArrayReplacement - $I->fillField("#selector", "John " . PersistedObjectHandler::getInstance()->retrieveEntityField('createdData', 'firstname', 'test') . " stringLiteral"); // stepKey: allTypesMixed - $I->searchAndMultiSelectOption("#selector", [PersistedObjectHandler::getInstance()->retrieveEntityField('createdData', 'firstname', 'test'), "John", "stringLiteral"]); // stepKey: parameterArrayMixed - $I->seeInPageSource("StringBefore " . PersistedObjectHandler::getInstance()->retrieveEntityField('createdData', 'firstname', 'test') . " StringAfter"); // stepKey: htmlReplace1 - $I->seeInPageSource("StringBefore " . PersistedObjectHandler::getInstance()->retrieveEntityField('createData1', 'firstname', 'test') . " StringAfter"); // stepKey: htmlReplace2 - $I->seeInPageSource("#" . getenv("MAGENTO_BASE_URL") . "#" . PersistedObjectHandler::getInstance()->retrieveEntityField('createdData', 'firstname', 'test')); // stepKey: htmlReplace3 - $I->dontSeeInPageSource("StringBefore " . PersistedObjectHandler::getInstance()->retrieveEntityField('createdData', 'firstname', 'test') . " StringAfter"); // stepKey: htmlReplace4 - $I->dontSeeInPageSource("StringBefore " . PersistedObjectHandler::getInstance()->retrieveEntityField('createData1', 'firstname', 'test') . " StringAfter"); // stepKey: htmlReplace5 - $I->dontSeeInPageSource("#" . getenv("MAGENTO_BASE_URL") . "#" . PersistedObjectHandler::getInstance()->retrieveEntityField('createdData', 'firstname', 'test')); // stepKey: htmlReplace6 - $I->seeInSource("StringBefore " . PersistedObjectHandler::getInstance()->retrieveEntityField('createdData', 'firstname', 'test') . " StringAfter"); // stepKey: htmlReplace7 - $I->seeInSource("StringBefore " . PersistedObjectHandler::getInstance()->retrieveEntityField('createData1', 'firstname', 'test') . " StringAfter"); // stepKey: htmlReplace8 - $I->seeInSource("#" . getenv("MAGENTO_BASE_URL") . "#" . PersistedObjectHandler::getInstance()->retrieveEntityField('createdData', 'firstname', 'test')); // stepKey: htmlReplace9 - $I->dontSeeInSource("StringBefore " . PersistedObjectHandler::getInstance()->retrieveEntityField('createdData', 'firstname', 'test') . " StringAfter"); // stepKey: htmlReplace10 - $I->dontSeeInSource("StringBefore " . PersistedObjectHandler::getInstance()->retrieveEntityField('createData1', 'firstname', 'test') . " StringAfter"); // stepKey: htmlReplace11 - $I->dontSeeInSource("#" . getenv("MAGENTO_BASE_URL") . "#" . PersistedObjectHandler::getInstance()->retrieveEntityField('createdData', 'firstname', 'test')); // stepKey: htmlReplace12 + public function _passed(AcceptanceTester $I) + { + // Test passed successfully. + $this->isSuccess = true; } } diff --git a/dev/tests/verification/Resources/PersistenceActionGroupAppendingTest.txt b/dev/tests/verification/Resources/PersistenceActionGroupAppendingTest.txt index 95aab913f..4901e3341 100644 --- a/dev/tests/verification/Resources/PersistenceActionGroupAppendingTest.txt +++ b/dev/tests/verification/Resources/PersistenceActionGroupAppendingTest.txt @@ -2,8 +2,6 @@ namespace Magento\AcceptanceTest\_default\Backend; use Magento\FunctionalTestingFramework\AcceptanceTester; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\CredentialStore; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\PersistedObjectHandler; use \Codeception\Util\Locator; use Yandex\Allure\Adapter\Annotation\Features; use Yandex\Allure\Adapter\Annotation\Stories; @@ -15,56 +13,45 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/PersistenceActionGroupAppendingTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/PersistenceActionGroupAppendingTest.xml<br>") */ class PersistenceActionGroupAppendingTestCest { + /** + * @var bool + */ + private $isSuccess = false; + /** * @param AcceptanceTester $I * @throws \Exception */ public function _before(AcceptanceTester $I) { + $I->comment('[START BEFORE HOOK]'); $I->comment("Entering Action Group [ACTIONGROUPBEFORE] DataPersistenceAppendingActionGroup"); - $I->comment("[createDataACTIONGROUPBEFORE] create 'entity' entity"); - PersistedObjectHandler::getInstance()->createEntity( - "createDataACTIONGROUPBEFORE", - "hook", - "entity", - [], - [] - ); - - $I->comment("[updateDataACTIONGROUPBEFORE] update 'createDataACTIONGROUPBEFORE' entity to 'newEntity'"); - PersistedObjectHandler::getInstance()->updateEntity( - "createDataACTIONGROUPBEFORE", - "hook", - "newEntity", - [] - ); - - $I->comment("[deleteDataACTIONGROUPBEFORE] delete entity 'createDataACTIONGROUPBEFORE'"); - PersistedObjectHandler::getInstance()->deleteEntity( - "createDataACTIONGROUPBEFORE", - "hook" - ); - - $I->comment("[getDataACTIONGROUPBEFORE] get 'someEneity' entity"); - PersistedObjectHandler::getInstance()->getEntity( - "getDataACTIONGROUPBEFORE", - "hook", - "someEneity", - [], - null - ); - - $I->comment(PersistedObjectHandler::getInstance()->retrieveEntityField('createData', 'field', 'hook')); + $I->createEntity("createDataACTIONGROUPBEFORE", "hook", "DefaultPerson", [], []); // stepKey: createDataACTIONGROUPBEFORE + $I->updateEntity("createDataACTIONGROUPBEFORE", "hook", "newEntity",[]); // stepKey: updateDataACTIONGROUPBEFORE + $I->deleteEntity("createDataACTIONGROUPBEFORE", "hook"); // stepKey: deleteDataACTIONGROUPBEFORE + $I->getEntity("getDataACTIONGROUPBEFORE", "hook", "someEneity", [], null); // stepKey: getDataACTIONGROUPBEFORE + $I->comment($I->retrieveEntityField('createData', 'field', 'hook')); $I->comment("Exiting Action Group [ACTIONGROUPBEFORE] DataPersistenceAppendingActionGroup"); + $I->comment('[END BEFORE HOOK]'); + } + + /** + * @param AcceptanceTester $I + * @throws \Exception + */ + public function _after(AcceptanceTester $I) + { + if ($this->isSuccess) { + unlink(__FILE__); + } } /** * @Features({"TestModule"}) - * @Parameter(name = "AcceptanceTester", value="$I") * @param AcceptanceTester $I * @return void * @throws \Exception @@ -72,39 +59,17 @@ class PersistenceActionGroupAppendingTestCest public function PersistenceActionGroupAppendingTest(AcceptanceTester $I) { $I->comment("Entering Action Group [ACTIONGROUP] DataPersistenceAppendingActionGroup"); - $I->comment("[createDataACTIONGROUP] create 'entity' entity"); - PersistedObjectHandler::getInstance()->createEntity( - "createDataACTIONGROUP", - "test", - "entity", - [], - [] - ); - - $I->comment("[updateDataACTIONGROUP] update 'createDataACTIONGROUP' entity to 'newEntity'"); - PersistedObjectHandler::getInstance()->updateEntity( - "createDataACTIONGROUP", - "test", - "newEntity", - [] - ); - - $I->comment("[deleteDataACTIONGROUP] delete entity 'createDataACTIONGROUP'"); - PersistedObjectHandler::getInstance()->deleteEntity( - "createDataACTIONGROUP", - "test" - ); - - $I->comment("[getDataACTIONGROUP] get 'someEneity' entity"); - PersistedObjectHandler::getInstance()->getEntity( - "getDataACTIONGROUP", - "test", - "someEneity", - [], - null - ); - - $I->comment(PersistedObjectHandler::getInstance()->retrieveEntityField('createDataACTIONGROUP', 'field', 'test')); + $I->createEntity("createDataACTIONGROUP", "test", "DefaultPerson", [], []); // stepKey: createDataACTIONGROUP + $I->updateEntity("createDataACTIONGROUP", "test", "newEntity",[]); // stepKey: updateDataACTIONGROUP + $I->deleteEntity("createDataACTIONGROUP", "test"); // stepKey: deleteDataACTIONGROUP + $I->getEntity("getDataACTIONGROUP", "test", "someEneity", [], null); // stepKey: getDataACTIONGROUP + $I->comment($I->retrieveEntityField('createDataACTIONGROUP', 'field', 'test')); $I->comment("Exiting Action Group [ACTIONGROUP] DataPersistenceAppendingActionGroup"); } + + public function _passed(AcceptanceTester $I) + { + // Test passed successfully. + $this->isSuccess = true; + } } diff --git a/dev/tests/verification/Resources/PersistenceCustomFieldsTest.txt b/dev/tests/verification/Resources/PersistenceCustomFieldsTest.txt index 38dc364e7..5a3270922 100644 --- a/dev/tests/verification/Resources/PersistenceCustomFieldsTest.txt +++ b/dev/tests/verification/Resources/PersistenceCustomFieldsTest.txt @@ -2,8 +2,6 @@ namespace Magento\AcceptanceTest\_default\Backend; use Magento\FunctionalTestingFramework\AcceptanceTester; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\CredentialStore; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\PersistedObjectHandler; use \Codeception\Util\Locator; use Yandex\Allure\Adapter\Annotation\Features; use Yandex\Allure\Adapter\Annotation\Stories; @@ -15,101 +13,55 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/PersistenceCustomFieldsTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/PersistenceCustomFieldsTest.xml<br>") */ class PersistenceCustomFieldsTestCest { + /** + * @var bool + */ + private $isSuccess = false; + /** * @param AcceptanceTester $I * @throws \Exception */ public function _before(AcceptanceTester $I) { + $I->comment('[START BEFORE HOOK]'); $createData1Fields['firstname'] = "Mac"; - $createData1Fields['lastname'] = "Doe"; - $I->comment("[createData1] create 'DefaultPerson' entity"); - PersistedObjectHandler::getInstance()->createEntity( - "createData1", - "hook", - "DefaultPerson", - [], - $createData1Fields - ); - - $createData2Fields['firstname'] = PersistedObjectHandler::getInstance()->retrieveEntityField('createData1', 'firstname', 'hook'); - $I->comment("[createData2] create 'uniqueData' entity"); - PersistedObjectHandler::getInstance()->createEntity( - "createData2", - "hook", - "uniqueData", - ["createData1"], - $createData2Fields - ); + $createData1Fields['lastname'] = "Bar"; + $I->createEntity("createData1", "hook", "DefaultPerson", [], $createData1Fields); // stepKey: createData1 + $I->comment('[END BEFORE HOOK]'); + } + /** + * @param AcceptanceTester $I + * @throws \Exception + */ + public function _after(AcceptanceTester $I) + { + if ($this->isSuccess) { + unlink(__FILE__); + } } /** * @Features({"TestModule"}) - * @Parameter(name = "AcceptanceTester", value="$I") * @param AcceptanceTester $I * @return void * @throws \Exception */ public function PersistenceCustomFieldsTest(AcceptanceTester $I) { - $createdDataFields['favoriteIndex'] = "1"; - $createdDataFields['middlename'] = "Kovacs"; - $I->comment("[createdData] create 'simpleData' entity"); - PersistedObjectHandler::getInstance()->createEntity( - "createdData", - "test", - "simpleData", - [], - $createdDataFields - ); - $createdData3Fields['firstname'] = "Takeshi"; $createdData3Fields['lastname'] = "Kovacs"; - $I->comment("[createdData3] create 'UniquePerson' entity"); - PersistedObjectHandler::getInstance()->createEntity( - "createdData3", - "test", - "UniquePerson", - ["createdData"], - $createdData3Fields - ); - - $I->comment("Entering Action Group [createdAG] PersistenceActionGroup"); - $createDataAG1CreatedAGFields['firstname'] = "string1"; - $I->comment("[createDataAG1CreatedAG] create 'simpleData' entity"); - PersistedObjectHandler::getInstance()->createEntity( - "createDataAG1CreatedAG", - "test", - "simpleData", - [], - $createDataAG1CreatedAGFields - ); - - $createDataAG2CreatedAGFields['firstname'] = "Jane"; - $I->comment("[createDataAG2CreatedAG] create 'simpleData' entity"); - PersistedObjectHandler::getInstance()->createEntity( - "createDataAG2CreatedAG", - "test", - "simpleData", - [], - $createDataAG2CreatedAGFields - ); - - $createDataAG3CreatedAGFields['firstname'] = PersistedObjectHandler::getInstance()->retrieveEntityField('createdData3', 'firstname', 'test'); - $I->comment("[createDataAG3CreatedAG] create 'simpleData' entity"); - PersistedObjectHandler::getInstance()->createEntity( - "createDataAG3CreatedAG", - "test", - "simpleData", - [], - $createDataAG3CreatedAGFields - ); + $I->createEntity("createdData3", "test", "UniquePerson", ["createdData"], $createdData3Fields); // stepKey: createdData3 + } - $I->comment("Exiting Action Group [createdAG] PersistenceActionGroup"); + public function _passed(AcceptanceTester $I) + { + // Test passed successfully. + $this->isSuccess = true; } } diff --git a/dev/tests/verification/Resources/SectionReplacementTest.txt b/dev/tests/verification/Resources/SectionReplacementTest.txt index 5089cfbfd..586280197 100644 --- a/dev/tests/verification/Resources/SectionReplacementTest.txt +++ b/dev/tests/verification/Resources/SectionReplacementTest.txt @@ -2,8 +2,6 @@ namespace Magento\AcceptanceTest\_default\Backend; use Magento\FunctionalTestingFramework\AcceptanceTester; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\CredentialStore; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\PersistedObjectHandler; use \Codeception\Util\Locator; use Yandex\Allure\Adapter\Annotation\Features; use Yandex\Allure\Adapter\Annotation\Stories; @@ -15,13 +13,28 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/SectionReplacementTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/SectionReplacementTest.xml<br>") */ class SectionReplacementTestCest { + /** + * @var bool + */ + private $isSuccess = false; + + /** + * @param AcceptanceTester $I + * @throws \Exception + */ + public function _after(AcceptanceTester $I) + { + if ($this->isSuccess) { + unlink(__FILE__); + } + } + /** * @Features({"TestModule"}) - * @Parameter(name = "AcceptanceTester", value="$I") * @param AcceptanceTester $I * @return void * @throws \Exception @@ -47,30 +60,28 @@ class SectionReplacementTestCest $I->click("#Doe" . msq("uniqueData") . " .stringLiteral2"); // stepKey: selectorReplaceTwoParamDataRefMSQSuffix $I->click("#Doe" . msq("uniqueData") . "-stringLiteral2 .stringLiteral3"); // stepKey: selectorReplaceThreeParamDataRefMSQSuffix $I->click("#Doe" . msq("uniqueData") . "-stringLiteral2 .Doe" . msq("uniqueData") . " [stringLiteral3]"); // stepKey: selectorReplaceThreeParamOneDupeDataRefMSQSuffix - $I->comment("[createdData] create 'simpleData' entity"); - PersistedObjectHandler::getInstance()->createEntity( - "createdData", - "test", - "simpleData", - [], - [] - ); - - $I->click("#element ." . PersistedObjectHandler::getInstance()->retrieveEntityField('createdData', 'firstname', 'test')); // stepKey: selectorReplaceOneParamPersisted - $I->click("#" . PersistedObjectHandler::getInstance()->retrieveEntityField('createdData', 'firstname', 'test') . " .stringLiteral2"); // stepKey: selectorReplaceTwoParamPersisted - $I->click("#" . PersistedObjectHandler::getInstance()->retrieveEntityField('createdData', 'firstname', 'test') . "-stringLiteral2 .stringLiteral3"); // stepKey: selectorReplaceThreeParamPersisted - $I->click("#" . PersistedObjectHandler::getInstance()->retrieveEntityField('createdData', 'firstname', 'test') . "-stringLiteral2 ." . PersistedObjectHandler::getInstance()->retrieveEntityField('createdData', 'firstname', 'test') . " [stringLiteral3]"); // stepKey: selectorReplaceThreeParamOneDupePersisted + $I->createEntity("createdData", "test", "simpleData", [], []); // stepKey: createdData + $I->click("#element ." . $I->retrieveEntityField('createdData', 'firstname', 'test')); // stepKey: selectorReplaceOneParamPersisted + $I->click("#" . $I->retrieveEntityField('createdData', 'firstname', 'test') . " .stringLiteral2"); // stepKey: selectorReplaceTwoParamPersisted + $I->click("#" . $I->retrieveEntityField('createdData', 'firstname', 'test') . "-stringLiteral2 .stringLiteral3"); // stepKey: selectorReplaceThreeParamPersisted + $I->click("#" . $I->retrieveEntityField('createdData', 'firstname', 'test') . "-stringLiteral2 ." . $I->retrieveEntityField('createdData', 'firstname', 'test') . " [stringLiteral3]"); // stepKey: selectorReplaceThreeParamOneDupePersisted $I->click("#element .{$data}"); // stepKey: selectorReplaceOneParamVariable $I->click("#{$data1} .{$data2}"); // stepKey: selectorReplaceTwoParamVariable $I->click("#{$data1}-{$data2} .{$data3}"); // stepKey: selectorReplaceThreeParamVariable $I->click("#John-Doe .John [Tiberius]"); // stepKey: selectorReplaceThreeParamVariableOneDupe - $I->click("#stringLiteral1-" . PersistedObjectHandler::getInstance()->retrieveEntityField('createdData', 'firstname', 'test') . " .John"); // stepKey: selectorReplaceThreeParamMixed1 - $I->click("#stringLiteral1-" . PersistedObjectHandler::getInstance()->retrieveEntityField('createdData', 'firstname', 'test') . " .{$data}"); // stepKey: selectorReplaceThreeParamMixed2 - $I->click("#stringLiteral1-" . PersistedObjectHandler::getInstance()->retrieveEntityField('createdData', 'firstname', 'test') . " ." . msq("uniqueData") . "John"); // stepKey: selectorReplaceThreeParamMixedMSQPrefix - $I->click("#stringLiteral1-" . PersistedObjectHandler::getInstance()->retrieveEntityField('createdData', 'firstname', 'test') . " .Doe" . msq("uniqueData")); // stepKey: selectorReplaceThreeParamMixedMSQSuffix + $I->click("#stringLiteral1-" . $I->retrieveEntityField('createdData', 'firstname', 'test') . " .John"); // stepKey: selectorReplaceThreeParamMixed1 + $I->click("#stringLiteral1-" . $I->retrieveEntityField('createdData', 'firstname', 'test') . " .{$data}"); // stepKey: selectorReplaceThreeParamMixed2 + $I->click("#stringLiteral1-" . $I->retrieveEntityField('createdData', 'firstname', 'test') . " ." . msq("uniqueData") . "John"); // stepKey: selectorReplaceThreeParamMixedMSQPrefix + $I->click("#stringLiteral1-" . $I->retrieveEntityField('createdData', 'firstname', 'test') . " .Doe" . msq("uniqueData")); // stepKey: selectorReplaceThreeParamMixedMSQSuffix $I->click("#element .1#element .2"); // stepKey: selectorReplaceTwoParamElements $I->click("#element .1#element .{$data}"); // stepKey: selectorReplaceTwoParamMixedTypes $I->click("(//div[@data-role='slide'])[1]/a[@data-element='link'][contains(@href,'')]"); // stepKey: selectorParamWithEmptyString $I->click("(//div[@data-role='slide'])[1]/a[@data-element='link'][contains(@href,' ')]"); // stepKey: selectorParamWithASpace } + + public function _passed(AcceptanceTester $I) + { + // Test passed successfully. + $this->isSuccess = true; + } } diff --git a/dev/tests/verification/Resources/SkippedTest.txt b/dev/tests/verification/Resources/SkippedTest.txt index 08ff4fd43..2e7decf9d 100644 --- a/dev/tests/verification/Resources/SkippedTest.txt +++ b/dev/tests/verification/Resources/SkippedTest.txt @@ -2,8 +2,6 @@ namespace Magento\AcceptanceTest\_default\Backend; use Magento\FunctionalTestingFramework\AcceptanceTester; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\CredentialStore; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\PersistedObjectHandler; use \Codeception\Util\Locator; use Yandex\Allure\Adapter\Annotation\Features; use Yandex\Allure\Adapter\Annotation\Stories; @@ -16,21 +14,26 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @Title("[NO TESTCASEID]: skippedTest") - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/SkippedTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/SkippedTest/SkippedTest.xml<br>") */ class SkippedTestCest { + /** + * @var bool + */ + private $isSuccess = false; + /** * @Stories({"skipped"}) * @Severity(level = SeverityLevel::MINOR) * @Features({"TestModule"}) - * @Parameter(name = "AcceptanceTester", value="$I") * @param AcceptanceTester $I * @return void * @throws \Exception */ public function SkippedTest(AcceptanceTester $I, \Codeception\Scenario $scenario) { + unlink(__FILE__); $scenario->skip("This test is skipped due to the following issues:\nSkippedValue"); } } diff --git a/dev/tests/verification/Resources/SkippedTestTwoIssues.txt b/dev/tests/verification/Resources/SkippedTestTwoIssues.txt index 3de81e02f..2deaa9beb 100644 --- a/dev/tests/verification/Resources/SkippedTestTwoIssues.txt +++ b/dev/tests/verification/Resources/SkippedTestTwoIssues.txt @@ -2,8 +2,6 @@ namespace Magento\AcceptanceTest\_default\Backend; use Magento\FunctionalTestingFramework\AcceptanceTester; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\CredentialStore; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\PersistedObjectHandler; use \Codeception\Util\Locator; use Yandex\Allure\Adapter\Annotation\Features; use Yandex\Allure\Adapter\Annotation\Stories; @@ -16,21 +14,26 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @Title("[NO TESTCASEID]: skippedMultipleIssuesTest") - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/SkippedTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/SkippedTest/SkippedTestTwoIssues.xml<br>") */ class SkippedTestTwoIssuesCest { + /** + * @var bool + */ + private $isSuccess = false; + /** * @Stories({"skippedMultiple"}) * @Severity(level = SeverityLevel::MINOR) * @Features({"TestModule"}) - * @Parameter(name = "AcceptanceTester", value="$I") * @param AcceptanceTester $I * @return void * @throws \Exception */ public function SkippedTestTwoIssues(AcceptanceTester $I, \Codeception\Scenario $scenario) { + unlink(__FILE__); $scenario->skip("This test is skipped due to the following issues:\nSkippedValue\nSecondSkippedValue"); } } diff --git a/dev/tests/verification/Resources/SkippedTestWithHooks.txt b/dev/tests/verification/Resources/SkippedTestWithHooks.txt index a86924b94..0299eb67a 100644 --- a/dev/tests/verification/Resources/SkippedTestWithHooks.txt +++ b/dev/tests/verification/Resources/SkippedTestWithHooks.txt @@ -2,8 +2,6 @@ namespace Magento\AcceptanceTest\_default\Backend; use Magento\FunctionalTestingFramework\AcceptanceTester; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\CredentialStore; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\PersistedObjectHandler; use \Codeception\Util\Locator; use Yandex\Allure\Adapter\Annotation\Features; use Yandex\Allure\Adapter\Annotation\Stories; @@ -16,21 +14,26 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @Title("[NO TESTCASEID]: skippedTestWithHooks") - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/SkippedTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/SkippedTest/SkippedTestWithHooks.xml<br>") */ class SkippedTestWithHooksCest { + /** + * @var bool + */ + private $isSuccess = false; + /** * @Stories({"skippedWithHooks"}) * @Severity(level = SeverityLevel::MINOR) * @Features({"TestModule"}) - * @Parameter(name = "AcceptanceTester", value="$I") * @param AcceptanceTester $I * @return void * @throws \Exception */ public function SkippedTestWithHooks(AcceptanceTester $I, \Codeception\Scenario $scenario) { + unlink(__FILE__); $scenario->skip("This test is skipped due to the following issues:\nSkippedValue"); } } diff --git a/dev/tests/verification/Resources/SkippedTestNoIssues.txt b/dev/tests/verification/Resources/SkippedTestWithIssueMustGetSkippedWithoutErrorExitCode.txt similarity index 56% rename from dev/tests/verification/Resources/SkippedTestNoIssues.txt rename to dev/tests/verification/Resources/SkippedTestWithIssueMustGetSkippedWithoutErrorExitCode.txt index 168077458..f35d7ca74 100644 --- a/dev/tests/verification/Resources/SkippedTestNoIssues.txt +++ b/dev/tests/verification/Resources/SkippedTestWithIssueMustGetSkippedWithoutErrorExitCode.txt @@ -2,8 +2,6 @@ namespace Magento\AcceptanceTest\_default\Backend; use Magento\FunctionalTestingFramework\AcceptanceTester; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\CredentialStore; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\PersistedObjectHandler; use \Codeception\Util\Locator; use Yandex\Allure\Adapter\Annotation\Features; use Yandex\Allure\Adapter\Annotation\Stories; @@ -15,23 +13,27 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** - * @Title("[NO TESTCASEID]: skippedNoIssuesTest") - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/SkippedTest.xml<br>") - * @group skip + * @Title("[NO TESTCASEID]: skippedTest") + * @Description("<h3>Test files</h3>verification/TestModule/Test/SkippedTest/SkippedTestWithIssueMustGetSkippedWithoutErrorExitCode.xml<br>") */ -class SkippedTestNoIssuesCest +class SkippedTestWithIssueMustGetSkippedWithoutErrorExitCodeCest { /** - * @Stories({"skippedNo"}) + * @var bool + */ + private $isSuccess = false; + + /** + * @Stories({"skipped"}) * @Severity(level = SeverityLevel::MINOR) * @Features({"TestModule"}) - * @Parameter(name = "AcceptanceTester", value="$I") * @param AcceptanceTester $I * @return void * @throws \Exception */ - public function SkippedTestNoIssues(AcceptanceTester $I, \Codeception\Scenario $scenario) + public function SkippedTestWithIssueMustGetSkippedWithoutErrorExitCode(AcceptanceTester $I, \Codeception\Scenario $scenario) { - $scenario->skip("This test is skipped due to the following issues:\nNo issues have been specified."); + unlink(__FILE__); + $scenario->skip("This test is skipped due to the following issues:\nSkippedValue"); } } diff --git a/dev/tests/verification/Resources/StaticChecks/mftf-deprecated-entity-usage-checks.txt b/dev/tests/verification/Resources/StaticChecks/mftf-deprecated-entity-usage-checks.txt new file mode 100644 index 000000000..63e025470 --- /dev/null +++ b/dev/tests/verification/Resources/StaticChecks/mftf-deprecated-entity-usage-checks.txt @@ -0,0 +1,23 @@ + +File "/verification/DeprecationCheckModule/Test/DeprecationCheckTest.xml" contains: + - Deprecated Page(s): + "DeprecationCheckPage" in /verification/DeprecationCheckModule/Page/DeprecationCheckPage.xml + - Deprecated ActionGroup(s): + "DeprecationCheckDeprecatedActionGroup" in /verification/DeprecationCheckModule/ActionGroup/DeprecationCheckDeprecatedActionGroup.xml + - Deprecated Data(s): + "DeprecationCheckData" in /verification/DeprecationCheckModule/Data/DeprecationCheckData.xml + - "DeprecationCheckData" references deprecated: + "type1" in metadata xml file + + +File "/verification/DeprecationCheckModule/ActionGroup/DeprecationCheckActionGroup.xml" contains: + - Deprecated Section(s): + "DeprecationCheckSection" in /verification/DeprecationCheckModule/Section/DeprecationCheckSection.xml + - Deprecated Element(s): + "DeprecationCheckSection.deprecationCheckElement" in /verification/DeprecationCheckModule/Section/DeprecationCheckSection.xml + + +File "/verification/DeprecationCheckModule/Suite/deprecationCheckSuite.xml" contains: + - Deprecated Test(s): + "DeprecationCheckDeprecatedTest" in /verification/DeprecationCheckModule/Test/DeprecationCheckDeprecatedTest.xml + diff --git a/dev/tests/verification/Resources/StaticChecks/mftf-pause-action-usage-checks.txt b/dev/tests/verification/Resources/StaticChecks/mftf-pause-action-usage-checks.txt new file mode 100644 index 000000000..fd042a24c --- /dev/null +++ b/dev/tests/verification/Resources/StaticChecks/mftf-pause-action-usage-checks.txt @@ -0,0 +1,30 @@ + +File "/verification/PauseCheckModule/ActionGroup/ActionGroupWithMultiplePausesActionGroup.xml" +contains pause action(s): + + ActionGroupWithMultiplePausesActionGroup has pause action at stepKey(s): pauseAfterFillField1, pauseAfterFillField2, pauseAfterFillField3 + +File "/verification/PauseCheckModule/ActionGroup/ActionGroupWithPauseActionGroup.xml" +contains pause action(s): + + ActionGroupWithPauseActionGroup has pause action at stepKey(s): pauseAfterFillField2 + +File "/verification/PauseCheckModule/Test/TestWithMultiplePauseActionsTest.xml" +contains pause action(s): + + TestWithMultiplePauseActionsTest has pause action at stepKey(s): pauseBeforeAmOnPageKey, pauseAfterStep2, pauseAfterAmOnPageKey + +File "/verification/PauseCheckModule/Test/TestWithPauseActionTest.xml" +contains pause action(s): + + TestWithPauseActionTest has pause action at stepKey(s): pauseAfterStep3 + +File "/verification/PauseCheckModule/Suite/suiteWithMultiplePauseActionsSuite.xml" +contains pause action(s): + + suiteWithMultiplePauseActionsSuite has pause action at stepKey(s): pauseCreate1, pauseFillAfter + +File "/verification/PauseCheckModule/Suite/suiteWithPauseActionSuite.xml" +contains pause action(s): + + suiteWithPauseActionSuite has pause action at stepKey(s): pauseSuite diff --git a/dev/tests/verification/Resources/XmlCommentedActionGroupTest.txt b/dev/tests/verification/Resources/XmlCommentedActionGroupTest.txt index 7e43433a1..c406af2d5 100644 --- a/dev/tests/verification/Resources/XmlCommentedActionGroupTest.txt +++ b/dev/tests/verification/Resources/XmlCommentedActionGroupTest.txt @@ -2,8 +2,6 @@ namespace Magento\AcceptanceTest\_default\Backend; use Magento\FunctionalTestingFramework\AcceptanceTester; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\CredentialStore; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\PersistedObjectHandler; use \Codeception\Util\Locator; use Yandex\Allure\Adapter\Annotation\Features; use Yandex\Allure\Adapter\Annotation\Stories; @@ -16,14 +14,29 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @Title("[NO TESTCASEID]: Action Group With comment block in arguments and action group body") - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/XmlCommentedActionGroupTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/XmlCommentedActionGroupTest.xml<br>") */ class XmlCommentedActionGroupTestCest { + /** + * @var bool + */ + private $isSuccess = false; + + /** + * @param AcceptanceTester $I + * @throws \Exception + */ + public function _after(AcceptanceTester $I) + { + if ($this->isSuccess) { + unlink(__FILE__); + } + } + /** * @Severity(level = SeverityLevel::CRITICAL) * @Features({"TestModule"}) - * @Parameter(name = "AcceptanceTester", value="$I") * @param AcceptanceTester $I * @return void * @throws \Exception @@ -35,4 +48,10 @@ class XmlCommentedActionGroupTestCest $I->see("John", "#element .test1"); // stepKey: seeFirstNameActionGroup $I->comment("Exiting Action Group [actionGroup] XmlCommentedActionGroup"); } + + public function _passed(AcceptanceTester $I) + { + // Test passed successfully. + $this->isSuccess = true; + } } diff --git a/dev/tests/verification/Resources/XmlCommentedTest.txt b/dev/tests/verification/Resources/XmlCommentedTest.txt index f77aa5fe4..26563b7f4 100644 --- a/dev/tests/verification/Resources/XmlCommentedTest.txt +++ b/dev/tests/verification/Resources/XmlCommentedTest.txt @@ -2,8 +2,6 @@ namespace Magento\AcceptanceTest\_default\Backend; use Magento\FunctionalTestingFramework\AcceptanceTester; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\CredentialStore; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\PersistedObjectHandler; use \Codeception\Util\Locator; use Yandex\Allure\Adapter\Annotation\Features; use Yandex\Allure\Adapter\Annotation\Stories; @@ -16,19 +14,26 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @Title("[NO TESTCASEID]: Test With comment blocks in root element 'tests', in annotations and in test body.") - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/XmlCommentedTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/XmlCommentedTest.xml<br>") */ class XmlCommentedTestCest { + /** + * @var bool + */ + private $isSuccess = false; + /** * @param AcceptanceTester $I * @throws \Exception */ public function _before(AcceptanceTester $I) { + $I->comment('[START BEFORE HOOK]'); $I->comment("< > & \$abc \" abc ' <click stepKey=\"click\" userInput=\"\$\$createDataHook.firstname\$\$\" selector=\"#id_1\">/"); $I->amOnPage("/beforeUrl"); // stepKey: beforeAmOnPageKey $I->comment("< > & \$abc \" abc ' <click stepKey=\"click\" userInput=\"\$\$createDataHook.firstname\$\$\" selector=\"#id_2\">/"); + $I->comment('[END BEFORE HOOK]'); } /** @@ -37,9 +42,14 @@ class XmlCommentedTestCest */ public function _after(AcceptanceTester $I) { + $I->comment('[START AFTER HOOK]'); $I->comment("< > & \$abc \" abc ' <click stepKey=\"click\" userInput=\"\$\$createDataHook.firstname\$\$\" selector=\"#id_1\">/"); $I->amOnPage("/afterUrl"); // stepKey: afterAmOnPageKey $I->comment("< > & \$abc \" abc ' <click stepKey=\"click\" userInput=\"\$\$createDataHook.firstname\$\$\" selector=\"#id_2\">/"); + $I->comment('[END AFTER HOOK]'); + if ($this->isSuccess) { + unlink(__FILE__); + } } /** @@ -54,7 +64,6 @@ class XmlCommentedTestCest /** * @Severity(level = SeverityLevel::CRITICAL) * @Features({"TestModule"}) - * @Parameter(name = "AcceptanceTester", value="$I") * @param AcceptanceTester $I * @return void * @throws \Exception @@ -72,4 +81,10 @@ class XmlCommentedTestCest $I->comment("<cancelPopup stepKey=\"cancelPopupKey1\"/>"); $I->checkOption(".functionalTestSelector"); // stepKey: checkOptionKey1 } + + public function _passed(AcceptanceTester $I) + { + // Test passed successfully. + $this->isSuccess = true; + } } diff --git a/dev/tests/verification/Resources/functionalSuiteHooks.txt b/dev/tests/verification/Resources/functionalSuiteHooks.txt index 05ada44f1..243b7fecf 100644 --- a/dev/tests/verification/Resources/functionalSuiteHooks.txt +++ b/dev/tests/verification/Resources/functionalSuiteHooks.txt @@ -2,8 +2,15 @@ namespace Group; +use Facebook\WebDriver\Remote\RemoteWebDriver; use Magento\FunctionalTestingFramework\DataGenerator\Handlers\PersistedObjectHandler; use Magento\FunctionalTestingFramework\DataGenerator\Handlers\CredentialStore; +use Magento\FunctionalTestingFramework\Module\MagentoWebDriver; +use Magento\FunctionalTestingFramework\Module\MagentoAssert; +use Magento\FunctionalTestingFramework\Module\MagentoActionProxies; +use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; +use Codeception\Lib\ModuleContainer; +use Codeception\Module; /** * Group class is Codeception Extension which is allowed to handle to all internal events. @@ -22,9 +29,15 @@ class functionalSuiteHooks extends \Codeception\GroupObject private $currentTestRun = 0; private static $HOOK_EXECUTION_INIT = "\n/******** Beginning execution of functionalSuiteHooks suite %s block ********/\n"; private static $HOOK_EXECUTION_END = "\n/******** Execution of functionalSuiteHooks suite %s block complete ********/\n"; + /** @var MagentoWebDriver */ + private $webDriver; + /** @var ModuleContainer */ + private $moduleContainer; public function _before(\Codeception\Event\TestEvent $e) { + $this->webDriver = $this->getModule('\Magento\FunctionalTestingFramework\Module\MagentoWebDriver'); + $this->moduleContainer = $this->webDriver->getModuleContainer(); // increment test count per execution $this->currentTestRun++; $this->executePreConditions(); @@ -35,45 +48,59 @@ class functionalSuiteHooks extends \Codeception\GroupObject } } - private function executePreConditions() { if ($this->currentTestRun == 1) { + $this->testCount = $this->getTestCount(); + print sprintf(self::$HOOK_EXECUTION_INIT, "before"); try { - $webDriver = $this->getModule('\Magento\FunctionalTestingFramework\Module\MagentoWebDriver'); - - // close any open sessions - if ($webDriver->webDriver != null) { - $webDriver->webDriver->close(); - $webDriver->webDriver = null; + if ($this->webDriver->webDriver != null) { + $this->webDriver->_restart(); + } else { + $this->webDriver->_initializeSession(); } - - // initialize the webdriver session - $webDriver->_initializeSession(); - $webDriver->amOnPage("some.url"); // stepKey: before - $createFields['someKey'] = "dataHere"; + $this->getModuleForAction("amOnPage")->amOnPage("some.url"); // stepKey: before + $createOneFields['someKey'] = "dataHere"; + PersistedObjectHandler::getInstance()->createEntity( + "createOne", + "suite", + "createEntityOne", + [], + $createOneFields + ); + PersistedObjectHandler::getInstance()->createEntity( + "createTwo", + "suite", + "createEntityTwo", + ["createEntityOne"] + ); PersistedObjectHandler::getInstance()->createEntity( - "create", + "createThree", "suite", - "createThis", - $createFields + "createEntityThree", + [] ); - $webDriver->click(PersistedObjectHandler::getInstance()->retrieveEntityField('create', 'data', 'suite')); // stepKey: clickWithData + PersistedObjectHandler::getInstance()->createEntity( + "createFour", + "suite", + "createEntityFour", + ["createEntityTwo", "createEntityThree"] + ); + $this->getModuleForAction("click")->click(PersistedObjectHandler::getInstance()->retrieveEntityField('createTwo', 'data', 'suite')); // stepKey: clickWithData print("Entering Action Group [AC] actionGroupWithTwoArguments"); - $webDriver->see("John", msq("uniqueData") . "John"); // stepKey: seeFirstNameAC + $this->getModuleForAction("see")->see("John", msq("uniqueData") . "John"); // stepKey: seeFirstNameAC print("Exiting Action Group [AC] actionGroupWithTwoArguments"); - - // reset configuration and close session - $this->getModule('\Magento\FunctionalTestingFramework\Module\MagentoWebDriver')->_resetConfig(); - $webDriver->webDriver->close(); - $webDriver->webDriver = null; - } catch (\Exception $exception) { $this->preconditionFailure = $exception->getMessage(); } + // reset configuration and close session + $this->webDriver->_resetConfig(); + $this->webDriver->webDriver->close(); + $this->webDriver->webDriver = null; + print sprintf(self::$HOOK_EXECUTION_END, "before"); } } @@ -83,7 +110,6 @@ class functionalSuiteHooks extends \Codeception\GroupObject $this->executePostConditions($e); } - private function executePostConditions(\Codeception\Event\TestEvent $e) { if ($this->currentTestRun == $this->testCount) { @@ -111,27 +137,91 @@ class functionalSuiteHooks extends \Codeception\GroupObject } } } - $webDriver = $this->getModule('\Magento\FunctionalTestingFramework\Module\MagentoWebDriver'); - - // close any open sessions - if ($webDriver->webDriver != null) { - $webDriver->webDriver->close(); - $webDriver->webDriver = null; + if ($this->webDriver->webDriver != null) { + $this->webDriver->_restart(); + } else { + $this->webDriver->_initializeSession(); } - - // initialize the webdriver session - $webDriver->_initializeSession(); - $webDriver->amOnPage("some.url"); // stepKey: after - $webDriver->deleteEntityByUrl("deleteThis"); + $this->getModuleForAction("amOnPage")->amOnPage("some.url"); // stepKey: after + $this->getModuleForAction("deleteEntityByUrl")->deleteEntityByUrl("deleteThis"); // stepKey: delete print("Entering Action Group [AC] actionGroupWithTwoArguments"); - $webDriver->see("John", msq("uniqueData") . "John"); // stepKey: seeFirstNameAC + $this->getModuleForAction("see")->see("John", msq("uniqueData") . "John"); // stepKey: seeFirstNameAC print("Exiting Action Group [AC] actionGroupWithTwoArguments"); } catch (\Exception $exception) { print $exception->getMessage(); } PersistedObjectHandler::getInstance()->clearSuiteObjects(); + + $this->closeSession($this->webDriver); + print sprintf(self::$HOOK_EXECUTION_END, "after"); } } + + /** + * Close session method closes current session. + * If config 'close_all_sessions' is set to 'true' all sessions will be closed. + * + * return void + */ + private function closeSession(): void + { + $webDriverConfig = $this->webDriver->_getConfig(); + $this->webDriver->_closeSession(); + if (isset($webDriverConfig['close_all_sessions']) && $webDriverConfig['close_all_sessions'] === "true") { + $wdHost = sprintf( + '%s://%s:%s%s', + $webDriverConfig['protocol'], + $webDriverConfig['host'], + $webDriverConfig['port'], + $webDriverConfig['path'] + ); + $availableSessions = RemoteWebDriver::getAllSessions($wdHost); + foreach ($availableSessions as $session) { + $remoteWebDriver = RemoteWebDriver::createBySessionID($session['id'], $wdHost); + $remoteWebDriver->quit(); + } + } + } + + /** + * Return the module for an action. + * + * @param string $action + * @return Module + * @throws \Exception + */ + private function getModuleForAction($action) + { + $module = $this->moduleContainer->moduleForAction($action); + if ($module === null) { + throw new TestFrameworkException('Invalid action "' . $action . '"' . PHP_EOL); + } + return $module; + } + + /** + * Counts how many tests in group. + * + * @return integer + */ + private function getTestCount() + { + $config = $this->getGlobalConfig(); + if (empty($config['groups']) || empty($config['groups'][self::$group])) { + return $this->testCount; + } + $pathToGroupDir = TESTS_BP . DIRECTORY_SEPARATOR . array_first($config['groups'][self::$group]); + $pathToGroupCests = $pathToGroupDir . "*Cest.php"; + + $files = glob($pathToGroupCests); + if (is_array($files)) { + $qty = count($files); + print('In a group "' . self::$group . '" suite executor found ' . $qty . ' tests.' . PHP_EOL); + return $qty; + } + + return $this->testCount; + } } diff --git a/dev/tests/verification/Resources/functionalSuiteWithComments.txt b/dev/tests/verification/Resources/functionalSuiteWithComments.txt index 0e15b7909..186a2ae63 100644 --- a/dev/tests/verification/Resources/functionalSuiteWithComments.txt +++ b/dev/tests/verification/Resources/functionalSuiteWithComments.txt @@ -2,8 +2,15 @@ namespace Group; +use Facebook\WebDriver\Remote\RemoteWebDriver; use Magento\FunctionalTestingFramework\DataGenerator\Handlers\PersistedObjectHandler; use Magento\FunctionalTestingFramework\DataGenerator\Handlers\CredentialStore; +use Magento\FunctionalTestingFramework\Module\MagentoWebDriver; +use Magento\FunctionalTestingFramework\Module\MagentoAssert; +use Magento\FunctionalTestingFramework\Module\MagentoActionProxies; +use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; +use Codeception\Lib\ModuleContainer; +use Codeception\Module; /** * Group class is Codeception Extension which is allowed to handle to all internal events. @@ -22,9 +29,15 @@ class functionalSuiteWithComments extends \Codeception\GroupObject private $currentTestRun = 0; private static $HOOK_EXECUTION_INIT = "\n/******** Beginning execution of functionalSuiteWithComments suite %s block ********/\n"; private static $HOOK_EXECUTION_END = "\n/******** Execution of functionalSuiteWithComments suite %s block complete ********/\n"; + /** @var MagentoWebDriver */ + private $webDriver; + /** @var ModuleContainer */ + private $moduleContainer; public function _before(\Codeception\Event\TestEvent $e) { + $this->webDriver = $this->getModule('\Magento\FunctionalTestingFramework\Module\MagentoWebDriver'); + $this->moduleContainer = $this->webDriver->getModuleContainer(); // increment test count per execution $this->currentTestRun++; $this->executePreConditions(); @@ -35,47 +48,43 @@ class functionalSuiteWithComments extends \Codeception\GroupObject } } - private function executePreConditions() { if ($this->currentTestRun == 1) { + $this->testCount = $this->getTestCount(); + print sprintf(self::$HOOK_EXECUTION_INIT, "before"); try { - $webDriver = $this->getModule('\Magento\FunctionalTestingFramework\Module\MagentoWebDriver'); - - // close any open sessions - if ($webDriver->webDriver != null) { - $webDriver->webDriver->close(); - $webDriver->webDriver = null; + if ($this->webDriver->webDriver != null) { + $this->webDriver->_restart(); + } else { + $this->webDriver->_initializeSession(); } - - // initialize the webdriver session - $webDriver->_initializeSession(); print("Comment in Before"); - $webDriver->amOnPage("some.url"); // stepKey: before + $this->getModuleForAction("amOnPage")->amOnPage("some.url"); // stepKey: before $createFields['someKey'] = "dataHere"; PersistedObjectHandler::getInstance()->createEntity( "create", "suite", "createThis", - $createFields + [], + $createFields ); print("<click stepKey=\"comment with element\" userInput=\"helloworld\"/>"); - $webDriver->click(PersistedObjectHandler::getInstance()->retrieveEntityField('create', 'data', 'suite')); // stepKey: clickWithData + $this->getModuleForAction("click")->click(PersistedObjectHandler::getInstance()->retrieveEntityField('create', 'data', 'suite')); // stepKey: clickWithData print("Entering Action Group [AC] actionGroupWithTwoArguments"); - $webDriver->see("John", msq("uniqueData") . "John"); // stepKey: seeFirstNameAC + $this->getModuleForAction("see")->see("John", msq("uniqueData") . "John"); // stepKey: seeFirstNameAC print("Exiting Action Group [AC] actionGroupWithTwoArguments"); - - // reset configuration and close session - $this->getModule('\Magento\FunctionalTestingFramework\Module\MagentoWebDriver')->_resetConfig(); - $webDriver->webDriver->close(); - $webDriver->webDriver = null; - } catch (\Exception $exception) { $this->preconditionFailure = $exception->getMessage(); } + // reset configuration and close session + $this->webDriver->_resetConfig(); + $this->webDriver->webDriver->close(); + $this->webDriver->webDriver = null; + print sprintf(self::$HOOK_EXECUTION_END, "before"); } } @@ -85,7 +94,6 @@ class functionalSuiteWithComments extends \Codeception\GroupObject $this->executePostConditions($e); } - private function executePostConditions(\Codeception\Event\TestEvent $e) { if ($this->currentTestRun == $this->testCount) { @@ -113,23 +121,87 @@ class functionalSuiteWithComments extends \Codeception\GroupObject } } } - $webDriver = $this->getModule('\Magento\FunctionalTestingFramework\Module\MagentoWebDriver'); - - // close any open sessions - if ($webDriver->webDriver != null) { - $webDriver->webDriver->close(); - $webDriver->webDriver = null; + if ($this->webDriver->webDriver != null) { + $this->webDriver->_restart(); + } else { + $this->webDriver->_initializeSession(); } - - // initialize the webdriver session - $webDriver->_initializeSession(); print("afterBlock"); } catch (\Exception $exception) { print $exception->getMessage(); } PersistedObjectHandler::getInstance()->clearSuiteObjects(); + + $this->closeSession($this->webDriver); + print sprintf(self::$HOOK_EXECUTION_END, "after"); } } + + /** + * Close session method closes current session. + * If config 'close_all_sessions' is set to 'true' all sessions will be closed. + * + * return void + */ + private function closeSession(): void + { + $webDriverConfig = $this->webDriver->_getConfig(); + $this->webDriver->_closeSession(); + if (isset($webDriverConfig['close_all_sessions']) && $webDriverConfig['close_all_sessions'] === "true") { + $wdHost = sprintf( + '%s://%s:%s%s', + $webDriverConfig['protocol'], + $webDriverConfig['host'], + $webDriverConfig['port'], + $webDriverConfig['path'] + ); + $availableSessions = RemoteWebDriver::getAllSessions($wdHost); + foreach ($availableSessions as $session) { + $remoteWebDriver = RemoteWebDriver::createBySessionID($session['id'], $wdHost); + $remoteWebDriver->quit(); + } + } + } + + /** + * Return the module for an action. + * + * @param string $action + * @return Module + * @throws \Exception + */ + private function getModuleForAction($action) + { + $module = $this->moduleContainer->moduleForAction($action); + if ($module === null) { + throw new TestFrameworkException('Invalid action "' . $action . '"' . PHP_EOL); + } + return $module; + } + + /** + * Counts how many tests in group. + * + * @return integer + */ + private function getTestCount() + { + $config = $this->getGlobalConfig(); + if (empty($config['groups']) || empty($config['groups'][self::$group])) { + return $this->testCount; + } + $pathToGroupDir = TESTS_BP . DIRECTORY_SEPARATOR . array_first($config['groups'][self::$group]); + $pathToGroupCests = $pathToGroupDir . "*Cest.php"; + + $files = glob($pathToGroupCests); + if (is_array($files)) { + $qty = count($files); + print('In a group "' . self::$group . '" suite executor found ' . $qty . ' tests.' . PHP_EOL); + return $qty; + } + + return $this->testCount; + } } diff --git a/dev/tests/verification/TestModule/ActionGroup/BasicActionGroup.xml b/dev/tests/verification/TestModule/ActionGroup/BasicActionGroup.xml deleted file mode 100644 index 45ccdbd24..000000000 --- a/dev/tests/verification/TestModule/ActionGroup/BasicActionGroup.xml +++ /dev/null @@ -1,125 +0,0 @@ -<?xml version="1.0" encoding="UTF-8" ?> -<!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ ---> -<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> - <actionGroup name="actionGroupWithoutArguments"> - <wait time="1" stepKey="waitForNothing" /> - </actionGroup> - - <actionGroup name="actionGroupWithDefaultArgumentAndStringSelectorParam"> - <arguments> - <argument name="someArgument" defaultValue="ReplacementPerson" /> - </arguments> - - <see selector="{{SampleSection.oneParamElement('test1')}}" userInput="{{someArgument.firstname}}" stepKey="seeFirstName" /> - </actionGroup> - - <actionGroup name="actionGroupWithTwoArguments"> - <arguments> - <argument name="somePerson"/> - <argument name="anotherPerson"/> - </arguments> - - <see selector="{{anotherPerson.firstname}}" userInput="{{somePerson.firstname}}" stepKey="seeFirstName" /> - </actionGroup> - - <actionGroup name="actionGroupWithSingleParameterSelectorFromArgument"> - <arguments> - <argument name="someArgument" defaultValue="ReplacementPerson" /> - </arguments> - - <see selector="{{SampleSection.oneParamElement(someArgument.firstname)}}" userInput="{{someArgument.lastname}}" stepKey="seeLastName" /> - </actionGroup> - - <actionGroup name="actionGroupWithMultipleParameterSelectorsFromArgument"> - <arguments> - <argument name="someArgument" defaultValue="ReplacementPerson" /> - </arguments> - - <see selector="{{SampleSection.threeParamElement(someArgument.firstname, someArgument.lastname, 'test')}}" userInput="{{someArgument.lastname}}" stepKey="seeLastName" /> - </actionGroup> - - <actionGroup name="actionGroupWithStringUsage"> - <arguments> - <argument name="someArgument" type="string" defaultValue="stringLiteral"/> - </arguments> - <see selector="{{SampleSection.oneParamElement(someArgument)}}" userInput="{{someArgument}}" stepKey="see1" /> - </actionGroup> - - <actionGroup name="actionGroupWithEntityUsage"> - <arguments> - <argument name="someArgument" type="entity" defaultValue="stringLiteral"/> - </arguments> - <see selector="{{SampleSection.oneParamElement(someArgument)}}" userInput="{{someArgument}}" stepKey="see1" /> - </actionGroup> - - <actionGroup name="actionGroupWithNestedArgument"> - <arguments> - <argument name="count" defaultValue="10" type="string"/> - </arguments> - <grabMultiple selector="selector" stepKey="grabProducts"/> - <assertCount stepKey="assertCount"> - <expectedResult type="int">{{count}}</expectedResult> - <actualResult type="variable">grabProducts</actualResult> - </assertCount> - </actionGroup> - - <actionGroup name="ActionGroupToExtend"> - <arguments> - <argument name="count" type="string"/> - </arguments> - <grabMultiple selector="selector" stepKey="grabProducts"/> - <assertCount stepKey="assertCount"> - <expectedResult type="int">{{count}}</expectedResult> - <actualResult type="variable">grabProducts</actualResult> - </assertCount> - </actionGroup> - - <actionGroup name="extendTestActionGroup" extends="ActionGroupToExtend"> - <arguments> - <argument name="otherCount" type="string"/> - </arguments> - <grabMultiple selector="notASelector" stepKey="grabProducts"/> - <comment userInput="New Input After" stepKey="afterGrabProducts" after="grabProducts"/> - <comment userInput="New Input Before" stepKey="beforeGrabProducts" before="grabProducts"/> - <assertCount stepKey="assertSecondCount"> - <expectedResult type="int">{{otherCount}}</expectedResult> - <actualResult type="variable">grabProducts</actualResult> - </assertCount> - </actionGroup> - - <actionGroup name="extendBasicActionGroup"> - <comment stepKey="removeMe" userInput="This Should Be Removed"/> - </actionGroup> - - <actionGroup name="extendRemoveTestActionGroup" extends="extendBasicActionGroup"> - <remove keyForRemoval="removeMe"/> - </actionGroup> - - <actionGroup name="actionGroupWithCreateData"> - <createData entity="ApiCategory" stepKey="createCategory"/> - <createData entity="ApiConfigurableProduct" stepKey="createConfigProduct"> - <requiredEntity createDataKey="createCategory"/> - </createData> - </actionGroup> - - <actionGroup name="actionGroupContainsStepKeyInArgValue"> - <arguments> - <argument name="sameStepKeyAsArg" type="string" defaultValue="stringLiteral"/> - </arguments> - <see selector=".selector" userInput="{{sameStepKeyAsArg}}" stepKey="arg1" /> - </actionGroup> - - <actionGroup name="actionGroupWithSectionAndData"> - <arguments> - <argument name="content" type="string"/> - <argument name="section"/> - </arguments> - <waitForElementVisible selector="{{section.oneParamElement(content)}}" stepKey="arg1"/> - </actionGroup> -</actionGroups> diff --git a/dev/tests/verification/TestModule/ActionGroup/BasicActionGroup/ActionGroupContainsStepKeyInArgValueActionGroup.xml b/dev/tests/verification/TestModule/ActionGroup/BasicActionGroup/ActionGroupContainsStepKeyInArgValueActionGroup.xml new file mode 100644 index 000000000..afc7f0e04 --- /dev/null +++ b/dev/tests/verification/TestModule/ActionGroup/BasicActionGroup/ActionGroupContainsStepKeyInArgValueActionGroup.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="actionGroupContainsStepKeyInArgValue"> + <arguments> + <argument name="sameStepKeyAsArg" type="string" defaultValue="stringLiteral"/> + </arguments> + <see selector=".selector" userInput="{{sameStepKeyAsArg}}" stepKey="arg1" /> + </actionGroup> +</actionGroups> diff --git a/dev/tests/verification/TestModule/ActionGroup/BasicActionGroup/ActionGroupReturningValueActionGroup.xml b/dev/tests/verification/TestModule/ActionGroup/BasicActionGroup/ActionGroupReturningValueActionGroup.xml new file mode 100644 index 000000000..ddb93c0c1 --- /dev/null +++ b/dev/tests/verification/TestModule/ActionGroup/BasicActionGroup/ActionGroupReturningValueActionGroup.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="ActionGroupReturningValueActionGroup"> + <arguments> + <argument name="count" type="string"/> + </arguments> + <grabMultiple selector="selector" stepKey="grabProducts1"/> + <assertCount stepKey="assertCount"> + <expectedResult type="int">{{count}}</expectedResult> + <actualResult type="variable">grabProducts1</actualResult> + </assertCount> + <return value="{$grabProducts1}" stepKey="returnProducts1"/> + </actionGroup> +</actionGroups> diff --git a/dev/tests/verification/TestModule/ActionGroup/BasicActionGroup/ActionGroupToExtendActionGroup.xml b/dev/tests/verification/TestModule/ActionGroup/BasicActionGroup/ActionGroupToExtendActionGroup.xml new file mode 100644 index 000000000..8e87f46c5 --- /dev/null +++ b/dev/tests/verification/TestModule/ActionGroup/BasicActionGroup/ActionGroupToExtendActionGroup.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="ActionGroupToExtend"> + <arguments> + <argument name="count" type="string"/> + </arguments> + <grabMultiple selector="selector" stepKey="grabProducts"/> + <assertCount stepKey="assertCount"> + <expectedResult type="int">{{count}}</expectedResult> + <actualResult type="variable">grabProducts</actualResult> + </assertCount> + </actionGroup> +</actionGroups> diff --git a/dev/tests/verification/TestModule/ActionGroup/BasicActionGroup/ActionGroupWithCreateDataActionGroup.xml b/dev/tests/verification/TestModule/ActionGroup/BasicActionGroup/ActionGroupWithCreateDataActionGroup.xml new file mode 100644 index 000000000..7bf83c230 --- /dev/null +++ b/dev/tests/verification/TestModule/ActionGroup/BasicActionGroup/ActionGroupWithCreateDataActionGroup.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="actionGroupWithCreateData"> + <createData entity="TestData" stepKey="createConfigProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + </actionGroup> +</actionGroups> diff --git a/dev/tests/verification/TestModule/ActionGroup/BasicActionGroup/ActionGroupWithDefaultArgumentAndStringSelectorParamActionGroup.xml b/dev/tests/verification/TestModule/ActionGroup/BasicActionGroup/ActionGroupWithDefaultArgumentAndStringSelectorParamActionGroup.xml new file mode 100644 index 000000000..6a397120c --- /dev/null +++ b/dev/tests/verification/TestModule/ActionGroup/BasicActionGroup/ActionGroupWithDefaultArgumentAndStringSelectorParamActionGroup.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="actionGroupWithDefaultArgumentAndStringSelectorParam"> + <arguments> + <argument name="someArgument" defaultValue="ReplacementPerson" /> + </arguments> + + <see selector="{{SampleSection.oneParamElement('test1')}}" userInput="{{someArgument.firstname}}" stepKey="seeFirstName" /> + </actionGroup> +</actionGroups> diff --git a/dev/tests/verification/TestModule/ActionGroup/BasicActionGroup/ActionGroupWithEntityUsageActionGroup.xml b/dev/tests/verification/TestModule/ActionGroup/BasicActionGroup/ActionGroupWithEntityUsageActionGroup.xml new file mode 100644 index 000000000..d85c5f661 --- /dev/null +++ b/dev/tests/verification/TestModule/ActionGroup/BasicActionGroup/ActionGroupWithEntityUsageActionGroup.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="actionGroupWithEntityUsage"> + <arguments> + <argument name="someArgument" type="entity" defaultValue="stringLiteral"/> + </arguments> + <see selector="{{SampleSection.oneParamElement(someArgument)}}" userInput="{{someArgument}}" stepKey="see1" /> + </actionGroup> +</actionGroups> diff --git a/dev/tests/verification/TestModule/ActionGroup/BasicActionGroup/ActionGroupWithMultipleParameterSelectorsFromArgumentActionGroup.xml b/dev/tests/verification/TestModule/ActionGroup/BasicActionGroup/ActionGroupWithMultipleParameterSelectorsFromArgumentActionGroup.xml new file mode 100644 index 000000000..44561f91d --- /dev/null +++ b/dev/tests/verification/TestModule/ActionGroup/BasicActionGroup/ActionGroupWithMultipleParameterSelectorsFromArgumentActionGroup.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="actionGroupWithMultipleParameterSelectorsFromArgument"> + <arguments> + <argument name="someArgument" defaultValue="ReplacementPerson" /> + </arguments> + + <see selector="{{SampleSection.threeParamElement(someArgument.firstname, someArgument.lastname, 'test')}}" userInput="{{someArgument.lastname}}" stepKey="seeLastName" /> + </actionGroup> +</actionGroups> diff --git a/dev/tests/verification/TestModule/ActionGroup/BasicActionGroup/ActionGroupWithNestedArgumentActionGroup.xml b/dev/tests/verification/TestModule/ActionGroup/BasicActionGroup/ActionGroupWithNestedArgumentActionGroup.xml new file mode 100644 index 000000000..2e767481d --- /dev/null +++ b/dev/tests/verification/TestModule/ActionGroup/BasicActionGroup/ActionGroupWithNestedArgumentActionGroup.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="actionGroupWithNestedArgument"> + <arguments> + <argument name="count" defaultValue="10" type="string"/> + </arguments> + <grabMultiple selector="selector" stepKey="grabProducts"/> + <assertCount stepKey="assertCount"> + <expectedResult type="int">{{count}}</expectedResult> + <actualResult type="variable">grabProducts</actualResult> + </assertCount> + </actionGroup> +</actionGroups> diff --git a/dev/tests/verification/TestModule/ActionGroup/BasicActionGroup/ActionGroupWithSectionAndDataActionGroup.xml b/dev/tests/verification/TestModule/ActionGroup/BasicActionGroup/ActionGroupWithSectionAndDataActionGroup.xml new file mode 100644 index 000000000..4917a4545 --- /dev/null +++ b/dev/tests/verification/TestModule/ActionGroup/BasicActionGroup/ActionGroupWithSectionAndDataActionGroup.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="actionGroupWithSectionAndData"> + <arguments> + <argument name="content" type="string"/> + <argument name="section"/> + </arguments> + <waitForElementVisible selector="{{section.oneParamElement(content)}}" stepKey="arg1"/> + </actionGroup> +</actionGroups> diff --git a/dev/tests/verification/TestModule/ActionGroup/BasicActionGroup/ActionGroupWithSingleParameterSelectorFromArgumentActionGroup.xml b/dev/tests/verification/TestModule/ActionGroup/BasicActionGroup/ActionGroupWithSingleParameterSelectorFromArgumentActionGroup.xml new file mode 100644 index 000000000..1e6c2de78 --- /dev/null +++ b/dev/tests/verification/TestModule/ActionGroup/BasicActionGroup/ActionGroupWithSingleParameterSelectorFromArgumentActionGroup.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="actionGroupWithSingleParameterSelectorFromArgument"> + <arguments> + <argument name="someArgument" defaultValue="ReplacementPerson" /> + </arguments> + + <see selector="{{SampleSection.oneParamElement(someArgument.firstname)}}" userInput="{{someArgument.lastname}}" stepKey="seeLastName" /> + </actionGroup> +</actionGroups> diff --git a/dev/tests/verification/TestModule/ActionGroup/BasicActionGroup/ActionGroupWithStringUsageActionGroup.xml b/dev/tests/verification/TestModule/ActionGroup/BasicActionGroup/ActionGroupWithStringUsageActionGroup.xml new file mode 100644 index 000000000..0ee47f9f2 --- /dev/null +++ b/dev/tests/verification/TestModule/ActionGroup/BasicActionGroup/ActionGroupWithStringUsageActionGroup.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="actionGroupWithStringUsage"> + <arguments> + <argument name="someArgument" type="string" defaultValue="stringLiteral"/> + </arguments> + <see selector="{{SampleSection.oneParamElement(someArgument)}}" userInput="{{someArgument}}" stepKey="see1" /> + </actionGroup> +</actionGroups> diff --git a/dev/tests/verification/TestModule/ActionGroup/BasicActionGroup/ActionGroupWithTwoArgumentsActionGroup.xml b/dev/tests/verification/TestModule/ActionGroup/BasicActionGroup/ActionGroupWithTwoArgumentsActionGroup.xml new file mode 100644 index 000000000..d54143e4c --- /dev/null +++ b/dev/tests/verification/TestModule/ActionGroup/BasicActionGroup/ActionGroupWithTwoArgumentsActionGroup.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="actionGroupWithTwoArguments"> + <arguments> + <argument name="somePerson"/> + <argument name="anotherPerson"/> + </arguments> + + <see selector="{{anotherPerson.firstname}}" userInput="{{somePerson.firstname}}" stepKey="seeFirstName" /> + </actionGroup> +</actionGroups> diff --git a/dev/tests/verification/TestModule/ActionGroup/BasicActionGroup/ActionGroupWithoutArgumentsActionGroup.xml b/dev/tests/verification/TestModule/ActionGroup/BasicActionGroup/ActionGroupWithoutArgumentsActionGroup.xml new file mode 100644 index 000000000..428ca1a40 --- /dev/null +++ b/dev/tests/verification/TestModule/ActionGroup/BasicActionGroup/ActionGroupWithoutArgumentsActionGroup.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="actionGroupWithoutArguments"> + <wait time="1" stepKey="waitForNothing" /> + </actionGroup> +</actionGroups> diff --git a/dev/tests/verification/TestModule/ActionGroup/BasicActionGroup/ExtendBasicActionGroup.xml b/dev/tests/verification/TestModule/ActionGroup/BasicActionGroup/ExtendBasicActionGroup.xml new file mode 100644 index 000000000..bda82ebad --- /dev/null +++ b/dev/tests/verification/TestModule/ActionGroup/BasicActionGroup/ExtendBasicActionGroup.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="extendBasicActionGroup"> + <comment stepKey="removeMe" userInput="This Should Be Removed"/> + </actionGroup> +</actionGroups> diff --git a/dev/tests/verification/TestModule/ActionGroup/BasicActionGroup/ExtendRemoveTestActionGroup.xml b/dev/tests/verification/TestModule/ActionGroup/BasicActionGroup/ExtendRemoveTestActionGroup.xml new file mode 100644 index 000000000..f4f8e5647 --- /dev/null +++ b/dev/tests/verification/TestModule/ActionGroup/BasicActionGroup/ExtendRemoveTestActionGroup.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="extendRemoveTestActionGroup" extends="extendBasicActionGroup"> + <remove keyForRemoval="removeMe"/> + </actionGroup> +</actionGroups> diff --git a/dev/tests/verification/TestModule/ActionGroup/BasicActionGroup/ExtendTestActionGroup.xml b/dev/tests/verification/TestModule/ActionGroup/BasicActionGroup/ExtendTestActionGroup.xml new file mode 100644 index 000000000..0b9bf6041 --- /dev/null +++ b/dev/tests/verification/TestModule/ActionGroup/BasicActionGroup/ExtendTestActionGroup.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="extendTestActionGroup" extends="ActionGroupToExtend"> + <arguments> + <argument name="otherCount" type="string"/> + </arguments> + <grabMultiple selector="notASelector" stepKey="grabProducts"/> + <comment userInput="New Input After" stepKey="afterGrabProducts" after="grabProducts"/> + <comment userInput="New Input Before" stepKey="beforeGrabProducts" before="grabProducts"/> + <assertCount stepKey="assertSecondCount"> + <expectedResult type="int">{{otherCount}}</expectedResult> + <actualResult type="variable">grabProducts</actualResult> + </assertCount> + </actionGroup> +</actionGroups> diff --git a/dev/tests/verification/TestModule/ActionGroup/BasicActionGroup/ExtendedActionGroupReturningValueActionGroup.xml b/dev/tests/verification/TestModule/ActionGroup/BasicActionGroup/ExtendedActionGroupReturningValueActionGroup.xml new file mode 100644 index 000000000..18933a2f2 --- /dev/null +++ b/dev/tests/verification/TestModule/ActionGroup/BasicActionGroup/ExtendedActionGroupReturningValueActionGroup.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="ExtendedActionGroupReturningValueActionGroup" extends="ActionGroupReturningValueActionGroup"> + <arguments> + <argument name="otherCount" type="string"/> + </arguments> + <grabMultiple selector="otherSelector" stepKey="grabProducts2"/> + <assertCount stepKey="assertSecondCount"> + <expectedResult type="int">{{otherCount}}</expectedResult> + <actualResult type="variable">grabProducts2</actualResult> + </assertCount> + <return value="{$grabProducts2}" stepKey="returnProducts2"/> + </actionGroup> +</actionGroups> diff --git a/dev/tests/verification/TestModule/ActionGroup/DeprecatedActionGroup.xml b/dev/tests/verification/TestModule/ActionGroup/DeprecatedActionGroup.xml new file mode 100644 index 000000000..f447ce76e --- /dev/null +++ b/dev/tests/verification/TestModule/ActionGroup/DeprecatedActionGroup.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="DeprecatedActionGroup" deprecated="Deprecated action group"> + <see stepKey="deprecatedSee" userInput="{{DeprecatedData.field}}" selector="{{DeprecatedSection.deprecatedElement}}" /> + </actionGroup> +</actionGroups> diff --git a/dev/tests/verification/TestModule/ActionGroup/FunctionalActionGroup.xml b/dev/tests/verification/TestModule/ActionGroup/FunctionalActionGroup.xml deleted file mode 100644 index 30cba3848..000000000 --- a/dev/tests/verification/TestModule/ActionGroup/FunctionalActionGroup.xml +++ /dev/null @@ -1,106 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ ---> - -<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> - <actionGroup name="FunctionalActionGroup"> - <fillField selector="#foo" userInput="myData1" stepKey="fillField1"/> - <fillField selector="#bar" userInput="myData2" stepKey="fillField2"/> - </actionGroup> - <actionGroup name="FunctionalActionGroupWithData"> - <arguments> - <argument name="person" defaultValue="DefaultPerson"/> - </arguments> - <amOnPage url="{{SamplePage.url(person.firstname,person.lastname)}}" stepKey="amOnPage1"/> - <fillField selector="#foo" userInput="{{person.firstname}}" stepKey="fillField1"/> - <fillField selector="#bar" userInput="{{person.lastname}}" stepKey="fillField2"/> - <searchAndMultiSelectOption selector="#foo" parameterArray="[{{person.firstname}}, {{person.lastname}}]" stepKey="multi1"/> - <see selector="{{SampleSection.oneParamElement(person.firstname)}}" stepKey="see1"/> - </actionGroup> - <actionGroup name="FunctionalActionGroupNoDefault"> - <arguments> - <argument name="person"/> - </arguments> - <fillField selector="#foo" userInput="{{person.firstname}}" stepKey="fillField1"/> - <fillField selector="#bar" userInput="{{person.lastname}}" stepKey="fillField2"/> - <see selector="{{SampleSection.twoParamElement(person.firstname,person.lastname)}}" stepKey="see2"/> - </actionGroup> - <actionGroup name="FunctionalActionGroupForMerge"> - <arguments> - <argument name="myArg"/> - </arguments> - <fillField stepKey="deleteMe" userInput="Please delete me" selector="#delete" /> - <see selector="{{SampleSection.oneParamElement(myArg.firstname)}}" stepKey="see1"/> - <amOnPage url="{{SamplePage.url(myArg.firstname,myArg.lastname)}}" stepKey="amOnPage1"/> - </actionGroup> - <actionGroup name="FunctionalActionGroupWithTrickyArgument"> - <arguments> - <argument name="simple" defaultValue="simpleData"/> - </arguments> - <seeElement stepKey="see1" selector="{{SampleSection.simpleElement}}"/> - <seeElement stepKey="see2" selector="{{SampleSection.simpleElementOneParam(simple.firstname)}}"/> - </actionGroup> - <actionGroup name="FunctionActionGroupWithStepKeyReferences"> - <createData entity="simpleData" stepKey="createSimpleData"/> - <grabTextFrom selector=".class" stepKey="grabTextData"/> - <fillField stepKey="fill1" selector=".{$grabTextData}" userInput="$createSimpleData.field$"/> - <comment userInput="Invocation stepKey will not be appended in non stepKey instances" stepKey="comment1"/> - <click selector="{$action0}" stepKey="action0"/> - <fillField selector="{$action1}" stepKey="action1"/> - <comment userInput="Invocation stepKey will be appended in non stepKey instances" stepKey="comment2"/> - <executeJS function="{$action3}" stepKey="action3"/> - <magentoCLI command="{$action4}" arguments=""stuffHere"" stepKey="action4"/> - <generateDate date="{$action5}" format="H:i:s" stepKey="action5"/> - <formatMoney userInput="{$action6}" stepKey="action6"/> - <deleteData createDataKey="{$action7}" stepKey="action7"/> - <getData entity="{$action8}" stepKey="action8"/> - <updateData entity="{$action9}" stepKey="action9" createDataKey="1"/> - <createData entity="{$action10}" stepKey="action10"/> - <grabAttributeFrom selector="{$action11}" userInput="someInput" stepKey="action11"/> - <grabCookie userInput="{$action12}" parameterArray="['domain' => 'www.google.com']" stepKey="action12"/> - <grabFromCurrentUrl regex="{$action13}" stepKey="action13"/> - <grabMultiple selector="{$action14}" stepKey="action14"/> - <grabTextFrom selector="{$action15}" stepKey="action15"/> - <grabValueFrom selector="{$action16}" stepKey="action16"/> - </actionGroup> - <actionGroup name="FunctionalActionGroupForMassMergeBefore"> - <fillField selector="#foo" userInput="foo" stepKey="fillField1"/> - <fillField selector="#bar" userInput="bar" stepKey="fillField2"/> - <fillField selector="#baz" userInput="baz" stepKey="fillField3"/> - </actionGroup> - <actionGroup name="FunctionalActionGroupForMassMergeAfter"> - <fillField selector="#foo" userInput="foo" stepKey="fillField1"/> - <fillField selector="#bar" userInput="bar" stepKey="fillField2"/> - <fillField selector="#baz" userInput="baz" stepKey="fillField3"/> - </actionGroup> - <actionGroup name="FunctionalActionGroupWithXmlAndPersistedData"> - <arguments> - <argument name="xmlData" defaultValue="uniqueData"/> - <argument name="persistedData"/> - </arguments> - <seeInCurrentUrl url="/{{persistedData.urlKey}}.html?___store={{xmlData.firstname}}" stepKey="checkUrl"/> - </actionGroup> - <actionGroup name="SectionArgumentWithParameterizedSelector"> - <arguments> - <argument name="section" defaultValue="SampleSection"/> - </arguments> - <executeJS function="{{section.oneParamElement('full-width')}}" stepKey="keyone"/> - </actionGroup> - <actionGroup name="actionGroupWithParametrizedSelectors"> - <arguments> - <argument name="param" type="entity"/> - <argument name="param2" type="entity" defaultValue="simpleParamData"/> - </arguments> - <executeJS function="return 1" stepKey="testVariable"/> - <executeJS function="return 'test'" stepKey="testVariable2"/> - <createData entity="simpleData" stepKey="createSimpleData"/> - <click selector="{{SampleSection.twoParamElement({$testVariable2}, param.firstname)}}" stepKey="click1"/> - <click selector="{{SampleSection.threeParamElement(param.lastname, param2.uniqueNamePre, {$testVariable})}}" stepKey="click2"/> - <seeElement selector="{{SampleSection.fourParamElement(param.middlename, {$testVariable}, {$testVariable2}, $$createSimpleData.name$$)}}" stepKey="see1"/> - </actionGroup> -</actionGroups> diff --git a/dev/tests/verification/TestModule/ActionGroup/FunctionalActionGroup/ActionGroupWithParametrizedSelectorsActionGroup.xml b/dev/tests/verification/TestModule/ActionGroup/FunctionalActionGroup/ActionGroupWithParametrizedSelectorsActionGroup.xml new file mode 100644 index 000000000..cfa4e1343 --- /dev/null +++ b/dev/tests/verification/TestModule/ActionGroup/FunctionalActionGroup/ActionGroupWithParametrizedSelectorsActionGroup.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="actionGroupWithParametrizedSelectors"> + <arguments> + <argument name="param" type="entity"/> + <argument name="param2" type="entity" defaultValue="simpleParamData"/> + </arguments> + <executeJS function="return 1" stepKey="testVariable"/> + <executeJS function="return 'test'" stepKey="testVariable2"/> + <createData entity="simpleData" stepKey="createSimpleData"/> + <click selector="{{SampleSection.twoParamElement({$testVariable2}, param.firstname)}}" stepKey="click1"/> + <click selector="{{SampleSection.threeParamElement(param.lastname, param2.uniqueNamePre, {$testVariable})}}" stepKey="click2"/> + <seeElement selector="{{SampleSection.fourParamElement(param.middlename, {$testVariable}, {$testVariable2}, $$createSimpleData.name$$)}}" stepKey="see1"/> + </actionGroup> +</actionGroups> diff --git a/dev/tests/verification/TestModule/ActionGroup/FunctionalActionGroup/FunctionActionGroupWithStepKeyReferencesActionGroup.xml b/dev/tests/verification/TestModule/ActionGroup/FunctionalActionGroup/FunctionActionGroupWithStepKeyReferencesActionGroup.xml new file mode 100644 index 000000000..a9275c23b --- /dev/null +++ b/dev/tests/verification/TestModule/ActionGroup/FunctionalActionGroup/FunctionActionGroupWithStepKeyReferencesActionGroup.xml @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="FunctionActionGroupWithStepKeyReferences"> + <createData entity="TestData" stepKey="createSimpleData"/> + <grabTextFrom selector=".class" stepKey="grabTextData"/> + <fillField stepKey="fill1" selector=".{$grabTextData}" userInput="$createSimpleData.field$"/> + <comment userInput="Invocation stepKey will not be appended in non stepKey instances" stepKey="comment1"/> + <click selector="{$action0}" stepKey="action0"/> + <fillField selector="{$action1}" stepKey="action1"/> + <comment userInput="Invocation stepKey will be appended in non stepKey instances" stepKey="comment2"/> + <executeJS function="{$action3}" stepKey="action3"/> + <magentoCLI command="{$action4}" arguments=""stuffHere"" stepKey="action4"/> + <generateDate date="{$action5}" format="H:i:s" stepKey="action5"/> + <formatCurrency userInput="{$action6}" locale="en_CA" currency="USD" stepKey="action6"/> + <deleteData createDataKey="{$action7}" stepKey="action7"/> + <getData entity="{$action8}" stepKey="action8"/> + <updateData entity="{$action9}" stepKey="action9" createDataKey="1"/> + <grabAttributeFrom selector="{$action11}" userInput="someInput" stepKey="action11"/> + <grabCookie userInput="{$action12}" parameterArray="['domain' => 'www.google.com']" stepKey="action12"/> + <grabFromCurrentUrl regex="{$action13}" stepKey="action13"/> + <grabMultiple selector="{$action14}" stepKey="action14"/> + <grabTextFrom selector="{$action15}" stepKey="action15"/> + <grabValueFrom selector="{$action16}" stepKey="action16"/> + <grabCookieAttributes userInput="{$action17}" parameterArray="['domain' => 'www.google.com']" stepKey="action17"/> + </actionGroup> +</actionGroups> diff --git a/dev/tests/verification/TestModule/ActionGroup/FunctionalActionGroup/FunctionalActionGroup.xml b/dev/tests/verification/TestModule/ActionGroup/FunctionalActionGroup/FunctionalActionGroup.xml new file mode 100644 index 000000000..1da276f10 --- /dev/null +++ b/dev/tests/verification/TestModule/ActionGroup/FunctionalActionGroup/FunctionalActionGroup.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="FunctionalActionGroup"> + <fillField selector="#foo" userInput="myData1" stepKey="fillField1"/> + <fillField selector="#bar" userInput="myData2" stepKey="fillField2"/> + </actionGroup> +</actionGroups> diff --git a/dev/tests/verification/TestModule/ActionGroup/FunctionalActionGroup/FunctionalActionGroupForMassMergeAfterActionGroup.xml b/dev/tests/verification/TestModule/ActionGroup/FunctionalActionGroup/FunctionalActionGroupForMassMergeAfterActionGroup.xml new file mode 100644 index 000000000..0868a28e2 --- /dev/null +++ b/dev/tests/verification/TestModule/ActionGroup/FunctionalActionGroup/FunctionalActionGroupForMassMergeAfterActionGroup.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="FunctionalActionGroupForMassMergeAfter"> + <fillField selector="#foo" userInput="foo" stepKey="fillField1"/> + <fillField selector="#bar" userInput="bar" stepKey="fillField2"/> + <fillField selector="#baz" userInput="baz" stepKey="fillField3"/> + </actionGroup> +</actionGroups> diff --git a/dev/tests/verification/TestModule/ActionGroup/FunctionalActionGroup/FunctionalActionGroupForMassMergeBeforeActionGroup.xml b/dev/tests/verification/TestModule/ActionGroup/FunctionalActionGroup/FunctionalActionGroupForMassMergeBeforeActionGroup.xml new file mode 100644 index 000000000..e9b7f52c2 --- /dev/null +++ b/dev/tests/verification/TestModule/ActionGroup/FunctionalActionGroup/FunctionalActionGroupForMassMergeBeforeActionGroup.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="FunctionalActionGroupForMassMergeBefore"> + <fillField selector="#foo" userInput="foo" stepKey="fillField1"/> + <fillField selector="#bar" userInput="bar" stepKey="fillField2"/> + <fillField selector="#baz" userInput="baz" stepKey="fillField3"/> + </actionGroup> +</actionGroups> diff --git a/dev/tests/verification/TestModule/ActionGroup/FunctionalActionGroup/FunctionalActionGroupForMergeActionGroup.xml b/dev/tests/verification/TestModule/ActionGroup/FunctionalActionGroup/FunctionalActionGroupForMergeActionGroup.xml new file mode 100644 index 000000000..f3fabe074 --- /dev/null +++ b/dev/tests/verification/TestModule/ActionGroup/FunctionalActionGroup/FunctionalActionGroupForMergeActionGroup.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="FunctionalActionGroupForMerge"> + <arguments> + <argument name="myArg"/> + </arguments> + <fillField stepKey="deleteMe" userInput="Please delete me" selector="#delete" /> + <see selector="{{SampleSection.oneParamElement(myArg.firstname)}}" stepKey="see1"/> + <amOnPage url="{{SamplePage.url(myArg.firstname,myArg.lastname)}}" stepKey="amOnPage1"/> + </actionGroup> +</actionGroups> diff --git a/dev/tests/verification/TestModule/ActionGroup/FunctionalActionGroup/FunctionalActionGroupNoDefaultActionGroup.xml b/dev/tests/verification/TestModule/ActionGroup/FunctionalActionGroup/FunctionalActionGroupNoDefaultActionGroup.xml new file mode 100644 index 000000000..5313178f9 --- /dev/null +++ b/dev/tests/verification/TestModule/ActionGroup/FunctionalActionGroup/FunctionalActionGroupNoDefaultActionGroup.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="FunctionalActionGroupNoDefault"> + <arguments> + <argument name="person"/> + </arguments> + <fillField selector="#foo" userInput="{{person.firstname}}" stepKey="fillField1"/> + <fillField selector="#bar" userInput="{{person.lastname}}" stepKey="fillField2"/> + <see selector="{{SampleSection.twoParamElement(person.firstname,person.lastname)}}" stepKey="see2"/> + </actionGroup> +</actionGroups> diff --git a/dev/tests/verification/TestModule/ActionGroup/FunctionalActionGroup/FunctionalActionGroupWithDataActionGroup.xml b/dev/tests/verification/TestModule/ActionGroup/FunctionalActionGroup/FunctionalActionGroupWithDataActionGroup.xml new file mode 100644 index 000000000..6948f8ffd --- /dev/null +++ b/dev/tests/verification/TestModule/ActionGroup/FunctionalActionGroup/FunctionalActionGroupWithDataActionGroup.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="FunctionalActionGroupWithData"> + <arguments> + <argument name="person" defaultValue="DefaultPerson"/> + </arguments> + <amOnPage url="{{SamplePage.url(person.firstname,person.lastname)}}" stepKey="amOnPage1"/> + <fillField selector="#foo" userInput="{{person.firstname}}" stepKey="fillField1"/> + <fillField selector="#bar" userInput="{{person.lastname}}" stepKey="fillField2"/> + <searchAndMultiSelectOption selector="#foo" parameterArray="[{{person.firstname}}, {{person.lastname}}]" stepKey="multi1"/> + <see selector="{{SampleSection.oneParamElement(person.firstname)}}" stepKey="see1"/> + </actionGroup> +</actionGroups> diff --git a/dev/tests/verification/TestModule/ActionGroup/FunctionalActionGroup/FunctionalActionGroupWithReturnValueActionGroup.xml b/dev/tests/verification/TestModule/ActionGroup/FunctionalActionGroup/FunctionalActionGroupWithReturnValueActionGroup.xml new file mode 100644 index 000000000..77900efd3 --- /dev/null +++ b/dev/tests/verification/TestModule/ActionGroup/FunctionalActionGroup/FunctionalActionGroupWithReturnValueActionGroup.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="FunctionalActionGroupWithReturnValueActionGroup"> + <grabTextFrom selector="#foo" stepKey="grabTextFrom1"/> + <return value="{$grabTextFrom1}" stepKey="return"/> + </actionGroup> +</actionGroups> diff --git a/dev/tests/verification/TestModule/ActionGroup/FunctionalActionGroup/FunctionalActionGroupWithTrickyArgumentActionGroup.xml b/dev/tests/verification/TestModule/ActionGroup/FunctionalActionGroup/FunctionalActionGroupWithTrickyArgumentActionGroup.xml new file mode 100644 index 000000000..cad553151 --- /dev/null +++ b/dev/tests/verification/TestModule/ActionGroup/FunctionalActionGroup/FunctionalActionGroupWithTrickyArgumentActionGroup.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="FunctionalActionGroupWithTrickyArgument"> + <arguments> + <argument name="simple" defaultValue="simpleData"/> + </arguments> + <seeElement stepKey="see1" selector="{{SampleSection.simpleElement}}"/> + <seeElement stepKey="see2" selector="{{SampleSection.simpleElementOneParam(simple.firstname)}}"/> + </actionGroup> +</actionGroups> diff --git a/dev/tests/verification/TestModule/ActionGroup/FunctionalActionGroup/FunctionalActionGroupWithXmlAndPersistedDataActionGroup.xml b/dev/tests/verification/TestModule/ActionGroup/FunctionalActionGroup/FunctionalActionGroupWithXmlAndPersistedDataActionGroup.xml new file mode 100644 index 000000000..949d5b95d --- /dev/null +++ b/dev/tests/verification/TestModule/ActionGroup/FunctionalActionGroup/FunctionalActionGroupWithXmlAndPersistedDataActionGroup.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="FunctionalActionGroupWithXmlAndPersistedData"> + <arguments> + <argument name="xmlData" defaultValue="uniqueData"/> + <argument name="persistedData"/> + </arguments> + <seeInCurrentUrl url="/{{persistedData.urlKey}}.html?___store={{xmlData.firstname}}" stepKey="checkUrl"/> + </actionGroup> +</actionGroups> diff --git a/dev/tests/verification/TestModule/ActionGroup/FunctionalActionGroup/MergeActionGroupReturningValueActionGroup.xml b/dev/tests/verification/TestModule/ActionGroup/FunctionalActionGroup/MergeActionGroupReturningValueActionGroup.xml new file mode 100644 index 000000000..83afbce0c --- /dev/null +++ b/dev/tests/verification/TestModule/ActionGroup/FunctionalActionGroup/MergeActionGroupReturningValueActionGroup.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="MergeActionGroupReturningValueActionGroup"> + <arguments> + <argument name="myArg"/> + </arguments> + <fillField userInput="Please delete me" selector="#delete" stepKey="deleteMe"/> + <amOnPage url="{{SamplePage.url(myArg.firstname,myArg.lastname)}}" stepKey="amOnPage1"/> + <grabMultiple selector="#foo" stepKey="grabMultiple1"/> + <return value="{$grabMultiple1}" stepKey="returnValue"/> + </actionGroup> +</actionGroups> diff --git a/dev/tests/verification/TestModule/ActionGroup/FunctionalActionGroup/SectionArgumentWithParameterizedSelectorActionGroup.xml b/dev/tests/verification/TestModule/ActionGroup/FunctionalActionGroup/SectionArgumentWithParameterizedSelectorActionGroup.xml new file mode 100644 index 000000000..d82b6d64a --- /dev/null +++ b/dev/tests/verification/TestModule/ActionGroup/FunctionalActionGroup/SectionArgumentWithParameterizedSelectorActionGroup.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="SectionArgumentWithParameterizedSelector"> + <arguments> + <argument name="section" defaultValue="SampleSection"/> + </arguments> + <executeJS function="{{section.oneParamElement('full-width')}}" stepKey="keyone"/> + </actionGroup> +</actionGroups> diff --git a/dev/tests/verification/TestModule/ActionGroup/MergeFunctionalActionGroup.xml b/dev/tests/verification/TestModule/ActionGroup/MergeFunctionalActionGroup.xml deleted file mode 100644 index 7d8585772..000000000 --- a/dev/tests/verification/TestModule/ActionGroup/MergeFunctionalActionGroup.xml +++ /dev/null @@ -1,28 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ ---> - -<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> - <actionGroup name="FunctionalActionGroupForMerge"> - <see stepKey="myMergedSeeElement" selector=".merge .{{myArg.firstname}}" before="see1"/> - <click stepKey="myMergedClick" selector=".merge .{{myArg.lastname}}" after="amOnPage1"/> - <remove keyForRemoval="deleteMe"/> - </actionGroup> - - <actionGroup name="FunctionalActionGroupForMassMergeBefore" insertBefore="fillField2"> - <click stepKey="mergeBeforeBar" selector="#foo2"/> - <click stepKey="mergeAfterFoo2" selector="#bar2"/> - <click stepKey="mergeAfterBar2" selector="#baz2"/> - </actionGroup> - - <actionGroup name="FunctionalActionGroupForMassMergeAfter" insertAfter="fillField2"> - <click stepKey="mergeAfterBar" selector="#foo2"/> - <click stepKey="mergeAfterFoo2" selector="#bar2"/> - <click stepKey="mergeAfterBar2" selector="#baz2"/> - </actionGroup> -</actionGroups> diff --git a/dev/tests/verification/TestModule/ActionGroup/MergeFunctionalActionGroup/FunctionalActionGroupForMassMergeAfterActionGroup.xml b/dev/tests/verification/TestModule/ActionGroup/MergeFunctionalActionGroup/FunctionalActionGroupForMassMergeAfterActionGroup.xml new file mode 100644 index 000000000..cca3c1b24 --- /dev/null +++ b/dev/tests/verification/TestModule/ActionGroup/MergeFunctionalActionGroup/FunctionalActionGroupForMassMergeAfterActionGroup.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="FunctionalActionGroupForMassMergeAfter" insertAfter="fillField2"> + <click stepKey="mergeAfterBar" selector="#foo2"/> + <click stepKey="mergeAfterFoo2" selector="#bar2"/> + <click stepKey="mergeAfterBar2" selector="#baz2"/> + </actionGroup> +</actionGroups> diff --git a/dev/tests/verification/TestModule/ActionGroup/MergeFunctionalActionGroup/FunctionalActionGroupForMassMergeBeforeActionGroup.xml b/dev/tests/verification/TestModule/ActionGroup/MergeFunctionalActionGroup/FunctionalActionGroupForMassMergeBeforeActionGroup.xml new file mode 100644 index 000000000..d9a5c3f55 --- /dev/null +++ b/dev/tests/verification/TestModule/ActionGroup/MergeFunctionalActionGroup/FunctionalActionGroupForMassMergeBeforeActionGroup.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="FunctionalActionGroupForMassMergeBefore" insertBefore="fillField2"> + <click stepKey="mergeBeforeBar" selector="#foo2"/> + <click stepKey="mergeAfterFoo2" selector="#bar2"/> + <click stepKey="mergeAfterBar2" selector="#baz2"/> + </actionGroup> +</actionGroups> diff --git a/dev/tests/verification/TestModule/ActionGroup/MergeFunctionalActionGroup/FunctionalActionGroupForMergeActionGroup.xml b/dev/tests/verification/TestModule/ActionGroup/MergeFunctionalActionGroup/FunctionalActionGroupForMergeActionGroup.xml new file mode 100644 index 000000000..dd451f511 --- /dev/null +++ b/dev/tests/verification/TestModule/ActionGroup/MergeFunctionalActionGroup/FunctionalActionGroupForMergeActionGroup.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="FunctionalActionGroupForMerge"> + <see stepKey="myMergedSeeElement" selector=".merge .{{myArg.firstname}}" before="see1"/> + <click stepKey="myMergedClick" selector=".merge .{{myArg.lastname}}" after="amOnPage1"/> + <remove keyForRemoval="deleteMe"/> + </actionGroup> +</actionGroups> diff --git a/dev/tests/verification/TestModule/ActionGroup/MergeFunctionalActionGroup/MergeActionGroupReturningValueActionGroup.xml b/dev/tests/verification/TestModule/ActionGroup/MergeFunctionalActionGroup/MergeActionGroupReturningValueActionGroup.xml new file mode 100644 index 000000000..d952fdddf --- /dev/null +++ b/dev/tests/verification/TestModule/ActionGroup/MergeFunctionalActionGroup/MergeActionGroupReturningValueActionGroup.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="MergeActionGroupReturningValueActionGroup"> + <click stepKey="myMergedClick" selector=".merge .{{myArg.firstname}}" after="amOnPage1"/> + <remove keyForRemoval="deleteMe"/> + </actionGroup> +</actionGroups> diff --git a/dev/tests/verification/TestModule/ActionGroup/PersistenceActionGroup.xml b/dev/tests/verification/TestModule/ActionGroup/PersistenceActionGroup.xml deleted file mode 100644 index c4f894cfc..000000000 --- a/dev/tests/verification/TestModule/ActionGroup/PersistenceActionGroup.xml +++ /dev/null @@ -1,33 +0,0 @@ -<?xml version="1.0" encoding="UTF-8" ?> -<!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ ---> -<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> - <actionGroup name="PersistenceActionGroup"> - <arguments> - <argument name="arg1" type="string"/> - <argument name="arg2"/> - <argument name="arg3"/> - </arguments> - <createData entity="simpleData" stepKey="createDataAG1"> - <field key="firstname">{{arg1}}</field> - </createData> - <createData entity="simpleData" stepKey="createDataAG2"> - <field key="firstname">{{arg2}}</field> - </createData> - <createData entity="simpleData" stepKey="createDataAG3"> - <field key="firstname">{{arg3}}</field> - </createData> - </actionGroup> - <actionGroup name="DataPersistenceAppendingActionGroup"> - <createData entity="entity" stepKey="createData"/> - <updateData entity="newEntity" createDataKey="createData" stepKey="updateData"/> - <deleteData createDataKey="createData" stepKey="deleteData"/> - <getData entity="someEneity" stepKey="getData"/> - <comment userInput="$createData.field$" stepKey="comment"/> - </actionGroup> -</actionGroups> \ No newline at end of file diff --git a/dev/tests/verification/TestModule/ActionGroup/PersistenceActionGroup/DataPersistenceAppendingActionGroup.xml b/dev/tests/verification/TestModule/ActionGroup/PersistenceActionGroup/DataPersistenceAppendingActionGroup.xml new file mode 100644 index 000000000..2fa5cb0aa --- /dev/null +++ b/dev/tests/verification/TestModule/ActionGroup/PersistenceActionGroup/DataPersistenceAppendingActionGroup.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="DataPersistenceAppendingActionGroup"> + <createData entity="DefaultPerson" stepKey="createData"/> + <updateData entity="newEntity" createDataKey="createData" stepKey="updateData"/> + <deleteData createDataKey="createData" stepKey="deleteData"/> + <getData entity="someEneity" stepKey="getData"/> + <comment userInput="$createData.field$" stepKey="comment"/> + </actionGroup> +</actionGroups> diff --git a/dev/tests/verification/TestModule/ActionGroup/PersistenceActionGroup/DataPersistenceSelfReferenceActionGroup.xml b/dev/tests/verification/TestModule/ActionGroup/PersistenceActionGroup/DataPersistenceSelfReferenceActionGroup.xml new file mode 100644 index 000000000..091a7a6e3 --- /dev/null +++ b/dev/tests/verification/TestModule/ActionGroup/PersistenceActionGroup/DataPersistenceSelfReferenceActionGroup.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="DataPersistenceSelfReferenceActionGroup"> + <createData entity="entity1" stepKey="createData1"/> + <createData entity="entity2" stepKey="createData2"/> + <createData entity="entity3" stepKey="createData3"> + <field key="key1">$createData1.field$</field> + <field key="key2">$createData2.field$</field> + </createData> + </actionGroup> +</actionGroups> diff --git a/dev/tests/verification/TestModule/ActionGroup/PersistenceActionGroup/PersistenceActionGroup.xml b/dev/tests/verification/TestModule/ActionGroup/PersistenceActionGroup/PersistenceActionGroup.xml new file mode 100644 index 000000000..2a7b4873d --- /dev/null +++ b/dev/tests/verification/TestModule/ActionGroup/PersistenceActionGroup/PersistenceActionGroup.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="PersistenceActionGroup"> + <arguments> + <argument name="arg1" type="string"/> + <argument name="arg2"/> + <argument name="arg3"/> + </arguments> + <createData entity="DefaultPerson" stepKey="createDataAG1"> + <field key="firstname">{{arg1}}</field> + </createData> + <createData entity="DefaultPerson" stepKey="createDataAG2"> + <field key="firstname">{{arg2}}</field> + </createData> + <createData entity="DefaultPerson" stepKey="createDataAG3"> + <field key="firstname">{{arg3}}</field> + </createData> + </actionGroup> +</actionGroups> diff --git a/dev/tests/verification/TestModule/ActionGroup/XmlCommentedActionGroup.xml b/dev/tests/verification/TestModule/ActionGroup/XmlCommentedActionGroup.xml index a46dab939..9f1a429f7 100644 --- a/dev/tests/verification/TestModule/ActionGroup/XmlCommentedActionGroup.xml +++ b/dev/tests/verification/TestModule/ActionGroup/XmlCommentedActionGroup.xml @@ -6,7 +6,7 @@ */ --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <actionGroup name="XmlCommentedActionGroup"> <arguments> <!-- Comments in arguments are not affecting test generation --> diff --git a/dev/tests/verification/TestModule/ActionGroup/XmlDuplicateActionGroup.xml b/dev/tests/verification/TestModule/ActionGroup/XmlDuplicateActionGroup.xml index fcc22acca..14cbd836f 100644 --- a/dev/tests/verification/TestModule/ActionGroup/XmlDuplicateActionGroup.xml +++ b/dev/tests/verification/TestModule/ActionGroup/XmlDuplicateActionGroup.xml @@ -6,7 +6,7 @@ */ --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <actionGroup name="xmlDuplicateActionGroup"> <acceptPopup stepKey="ap1"/> <acceptPopup stepKey="ap2"/> @@ -78,20 +78,20 @@ <doubleClick selector="1" stepKey="dblclick2"/> <dragAndDrop selector1="1" selector2="2" stepKey="dragndrop1"/> <dragAndDrop selector1="1" selector2="2" stepKey="dragndrop2"/> - <executeInSelenium function="1" stepKey="executeSelenium1"/> - <executeInSelenium function="1" stepKey="executeSelenium2"/> <executeJS function="1" stepKey="execJS1"/> <executeJS function="1" stepKey="execJS2"/> <fillField stepKey="fill1"/> <fillField stepKey="fill21"/> - <formatMoney stepKey="frmtmoney1"/> - <formatMoney stepKey="frmtmoney12"/> + <formatCurrency userInput="1234.567890" locale="de_DE" currency="EUR" stepKey="frmtmoney1"/> + <formatCurrency userInput="1234.567890" locale="de_DE" currency="USD" stepKey="frmtmoney12"/> <getData entity="1" stepKey="getdata1"/> <getData entity="1" stepKey="getdata12"/> <grabAttributeFrom selector="1" stepKey="grabattribute1"/> <grabAttributeFrom selector="1" stepKey="grabattribute12"/> <grabCookie stepKey="grabcookie1"/> <grabCookie stepKey="grabcookie12"/> + <grabCookieAttributes stepKey="grabcookieattributes1"/> + <grabCookieAttributes stepKey="grabcookieattributes12"/> <grabFromCurrentUrl stepKey="grabfromcurl1"/> <grabFromCurrentUrl stepKey="grabfromcurl12"/> <grabMultiple selector="1" stepKey="grabmulti1"/> @@ -124,10 +124,8 @@ <openNewTab stepKey="newtab12"/> <parseFloat stepKey="parsefloat1"/> <parseFloat stepKey="parsefloat12"/> - <pauseExecution stepKey="pause1"/> - <pauseExecution stepKey="pause12"/> - <performOn selector="1" function="1" stepKey="performon1"/> - <performOn selector="1" function="1" stepKey="performon12"/> + <pause stepKey="pause1"/> + <pause stepKey="pause12"/> <pressKey selector="1" stepKey="press1"/> <pressKey selector="1" stepKey="press12"/> <reloadPage stepKey="reload1"/> diff --git a/dev/tests/verification/TestModule/Data/DefaultPerson.xml b/dev/tests/verification/TestModule/Data/DefaultPerson.xml new file mode 100644 index 000000000..d513b1fe1 --- /dev/null +++ b/dev/tests/verification/TestModule/Data/DefaultPerson.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="DefaultPerson" deprecated="Default Person"> + <data key="firstname">test</data> + <data key="lastname"> bar</data> + </entity> +</entities> diff --git a/dev/tests/verification/TestModule/Data/DeprecatedData.xml b/dev/tests/verification/TestModule/Data/DeprecatedData.xml new file mode 100644 index 000000000..7d5f1e200 --- /dev/null +++ b/dev/tests/verification/TestModule/Data/DeprecatedData.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="DeprecatedData" deprecated="Data entity deprecated"> + <data key="field">deprecated</data> + </entity> +</entities> diff --git a/dev/tests/verification/TestModule/Data/ExtendedData.xml b/dev/tests/verification/TestModule/Data/ExtendedData.xml index 74fa921d0..c26715eec 100644 --- a/dev/tests/verification/TestModule/Data/ExtendedData.xml +++ b/dev/tests/verification/TestModule/Data/ExtendedData.xml @@ -7,7 +7,7 @@ --> <entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> <entity name="parentData" type="data"> <data key="name">name</data> <data key="uniqueNamePre" unique="prefix">prename</data> diff --git a/dev/tests/verification/TestModule/Data/ParameterArrayData.xml b/dev/tests/verification/TestModule/Data/ParameterArrayData.xml index 3b45aedf7..38f6a2439 100644 --- a/dev/tests/verification/TestModule/Data/ParameterArrayData.xml +++ b/dev/tests/verification/TestModule/Data/ParameterArrayData.xml @@ -7,7 +7,7 @@ --> <entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> <entity name="simpleParamData" type="data"> <data key="name">name</data> <data key="uniqueNamePre" unique="prefix">prename</data> diff --git a/dev/tests/verification/TestModule/Data/PersistedReplacementData.xml b/dev/tests/verification/TestModule/Data/PersistedReplacementData.xml index a8a747857..7213fa6b9 100644 --- a/dev/tests/verification/TestModule/Data/PersistedReplacementData.xml +++ b/dev/tests/verification/TestModule/Data/PersistedReplacementData.xml @@ -8,7 +8,7 @@ --> <entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> <entity name="ReplacementPerson" type="samplePerson"> <data key="firstname">John</data> <data key="lastName">Doe</data> @@ -22,4 +22,8 @@ <data key="lastName">Dane</data> <data key="mergedField">unmerged</data> </entity> + <entity name="SecretData" type="SecretData"> + <data key="key1">some/data</data> + <data key="key2">{{_CREDS.magento/some/secret}}</data> + </entity> </entities> diff --git a/dev/tests/verification/TestModule/Data/ReplacementData.xml b/dev/tests/verification/TestModule/Data/ReplacementData.xml index 4d91fa84e..133fe8f6e 100644 --- a/dev/tests/verification/TestModule/Data/ReplacementData.xml +++ b/dev/tests/verification/TestModule/Data/ReplacementData.xml @@ -8,7 +8,7 @@ --> <entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> <entity name="simpleData" type="simpleData"> <data key="firstname">John</data> <data key="lastname">Doe</data> diff --git a/dev/tests/verification/TestModule/Data/TestData.xml b/dev/tests/verification/TestModule/Data/TestData.xml new file mode 100644 index 000000000..bbd9bdaf6 --- /dev/null +++ b/dev/tests/verification/TestModule/Data/TestData.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="TestData" deprecated="Test Data"> + <data key="field">test</data> + </entity> +</entities> diff --git a/dev/tests/verification/TestModule/Data/UniquePerson.xml b/dev/tests/verification/TestModule/Data/UniquePerson.xml new file mode 100644 index 000000000..5027b1d2a --- /dev/null +++ b/dev/tests/verification/TestModule/Data/UniquePerson.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="UniquePerson" deprecated="Unique Person"> + <requiredEntity type="createdData">Qty_1000</requiredEntity> + <data key="firstname">test</data> + <data key="lastname"> bar</data> + </entity> +</entities> diff --git a/dev/tests/verification/TestModule/Data/entity1.xml b/dev/tests/verification/TestModule/Data/entity1.xml new file mode 100644 index 000000000..c000223c2 --- /dev/null +++ b/dev/tests/verification/TestModule/Data/entity1.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="entity1" deprecated="Entity one"> + </entity> +</entities> diff --git a/dev/tests/verification/TestModule/Data/entity2.xml b/dev/tests/verification/TestModule/Data/entity2.xml new file mode 100644 index 000000000..0e1455f2d --- /dev/null +++ b/dev/tests/verification/TestModule/Data/entity2.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="entity2" > + </entity> +</entities> diff --git a/dev/tests/verification/TestModule/Page/DeprecatedPage.xml b/dev/tests/verification/TestModule/Page/DeprecatedPage.xml new file mode 100644 index 000000000..e758c3f07 --- /dev/null +++ b/dev/tests/verification/TestModule/Page/DeprecatedPage.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> + <page name="DeprecatedPage" url="/test.html" area="storefront" module="UnknownVendor_TestModule" deprecated="Deprecated page"> + <section name="SampleSection"/> + </page> +</pages> diff --git a/dev/tests/verification/TestModule/Page/SamplePage.xml b/dev/tests/verification/TestModule/Page/SamplePage.xml deleted file mode 100644 index 59efded6e..000000000 --- a/dev/tests/verification/TestModule/Page/SamplePage.xml +++ /dev/null @@ -1,32 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ ---> - -<pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../src/Magento/FunctionalTestingFramework/Page/etc/PageObject.xsd"> - <page name="SamplePage" url="/{{var1}}/{{var2}}.html" area="storefront" module="UnknownVendor_TestModule" parameterized="true"> - <section name="SampleSection"/> - </page> - <page name="NoParamPage" url="/page.html" area="storefront" module="UnknownVendor_TestModule"> - <section name="SampleSection"/> - </page> - <page name="OneParamPage" url="/{{var1}}/page.html" area="storefront" module="UnknownVendor_TestModule" parameterized="true"> - <section name="SampleSection"/> - </page> - <page name="TwoParamPage" url="/{{var1}}/{{var2}}.html" area="storefront" module="UnknownVendor_TestModule" parameterized="true"> - <section name="SampleSection"/> - </page> - <page name="AdminPage" url="/backend" area="admin" module="UnknownVendor_TestModule"> - <section name="SampleSection"/> - </page> - <page name="AdminOneParamPage" url="/{{var1}}/page.html" area="admin" module="UnknownVendor_TestModule" parameterized="true"> - <section name="SampleSection"/> - </page> - <page name="ExternalPage" url="http://myFullUrl.com/" area="external" module="UnknownVendor_TestModule"> - <section name="SampleSection"/> - </page> -</pages> diff --git a/dev/tests/verification/TestModule/Page/SamplePage/AdminOneParamPage.xml b/dev/tests/verification/TestModule/Page/SamplePage/AdminOneParamPage.xml new file mode 100644 index 000000000..de51a11fa --- /dev/null +++ b/dev/tests/verification/TestModule/Page/SamplePage/AdminOneParamPage.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> + <page name="AdminOneParamPage" url="/{{var1}}/page.html" area="admin" module="UnknownVendor_TestModule" parameterized="true"> + <section name="SampleSection"/> + </page> +</pages> diff --git a/dev/tests/verification/TestModule/Page/SamplePage/AdminPage.xml b/dev/tests/verification/TestModule/Page/SamplePage/AdminPage.xml new file mode 100644 index 000000000..12f554da6 --- /dev/null +++ b/dev/tests/verification/TestModule/Page/SamplePage/AdminPage.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> + <page name="AdminPage" url="/backend" area="admin" module="UnknownVendor_TestModule"> + <section name="SampleSection"/> + </page> +</pages> diff --git a/dev/tests/verification/TestModule/Page/SamplePage/ExternalPage.xml b/dev/tests/verification/TestModule/Page/SamplePage/ExternalPage.xml new file mode 100644 index 000000000..5c3e131e4 --- /dev/null +++ b/dev/tests/verification/TestModule/Page/SamplePage/ExternalPage.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> + <page name="ExternalPage" url="http://myFullUrl.com/" area="external" module="UnknownVendor_TestModule"> + <section name="SampleSection"/> + </page> +</pages> diff --git a/dev/tests/verification/TestModule/Page/SamplePage/NoParamPage.xml b/dev/tests/verification/TestModule/Page/SamplePage/NoParamPage.xml new file mode 100644 index 000000000..2a9b0efc5 --- /dev/null +++ b/dev/tests/verification/TestModule/Page/SamplePage/NoParamPage.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> + <page name="NoParamPage" url="/page.html" area="storefront" module="UnknownVendor_TestModule"> + <section name="SampleSection"/> + </page> +</pages> diff --git a/dev/tests/verification/TestModule/Page/SamplePage/OneParamPage.xml b/dev/tests/verification/TestModule/Page/SamplePage/OneParamPage.xml new file mode 100644 index 000000000..2986ebed9 --- /dev/null +++ b/dev/tests/verification/TestModule/Page/SamplePage/OneParamPage.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> + <page name="OneParamPage" url="/{{var1}}/page.html" area="storefront" module="UnknownVendor_TestModule" parameterized="true"> + <section name="SampleSection"/> + </page> +</pages> diff --git a/dev/tests/verification/TestModule/Page/SamplePage/SamplePage.xml b/dev/tests/verification/TestModule/Page/SamplePage/SamplePage.xml new file mode 100644 index 000000000..7baa9858e --- /dev/null +++ b/dev/tests/verification/TestModule/Page/SamplePage/SamplePage.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> + <page name="SamplePage" url="/{{var1}}/{{var2}}.html" area="storefront" module="UnknownVendor_TestModule" parameterized="true"> + <section name="SampleSection"/> + </page> +</pages> diff --git a/dev/tests/verification/TestModule/Page/SamplePage/TwoParamPage.xml b/dev/tests/verification/TestModule/Page/SamplePage/TwoParamPage.xml new file mode 100644 index 000000000..4ab1f632a --- /dev/null +++ b/dev/tests/verification/TestModule/Page/SamplePage/TwoParamPage.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> + <page name="TwoParamPage" url="/{{var1}}/{{var2}}.html" area="storefront" module="UnknownVendor_TestModule" parameterized="true"> + <section name="SampleSection"/> + </page> +</pages> diff --git a/dev/tests/verification/TestModule/Section/DeprecatedSection.xml b/dev/tests/verification/TestModule/Section/DeprecatedSection.xml new file mode 100644 index 000000000..954889786 --- /dev/null +++ b/dev/tests/verification/TestModule/Section/DeprecatedSection.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="DeprecatedSection" deprecated="Deprecated section"> + <element name="deprecatedElement" type="button" selector="#element" deprecated="Deprecated element"/> + </section> +</sections> diff --git a/dev/tests/verification/TestModule/Section/LocatorFunctionSection.xml b/dev/tests/verification/TestModule/Section/LocatorFunctionSection.xml index 28612e1b3..cc986d969 100644 --- a/dev/tests/verification/TestModule/Section/LocatorFunctionSection.xml +++ b/dev/tests/verification/TestModule/Section/LocatorFunctionSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="LocatorFunctionSection"> <element name="simpleLocator" type="button" locatorFunction="contains('label', 'Name')"/> <element name="simpleLocatorOneParam" type="button" locatorFunction="contains({{arg1}}, 'Name')" parameterized="true"/> diff --git a/dev/tests/verification/TestModule/Section/SampleSection.xml b/dev/tests/verification/TestModule/Section/SampleSection.xml index 920ee6e1b..088d3e344 100644 --- a/dev/tests/verification/TestModule/Section/SampleSection.xml +++ b/dev/tests/verification/TestModule/Section/SampleSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="SampleSection"> <element name="simpleElement" type="button" selector="#element"/> <element name="underscore_element" type="button" selector="#element"/> diff --git a/dev/tests/verification/TestModule/Suite/ActionsInDifferentModulesSuite.xml b/dev/tests/verification/TestModule/Suite/ActionsInDifferentModulesSuite.xml new file mode 100644 index 000000000..e3553308d --- /dev/null +++ b/dev/tests/verification/TestModule/Suite/ActionsInDifferentModulesSuite.xml @@ -0,0 +1,43 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<suites xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Suite/etc/suiteSchema.xsd"> + <suite name="ActionsInDifferentModulesSuite"> + <include> + <test name="IncludeActionsInDifferentModulesTest"/> + </include> + <before> + <magentoCLI command="{{SecretData.key2}}" stepKey="cli"/> + <createData entity="SecretData" stepKey="create1"> + <field key="someKey">dataHere</field> + </createData> + <createData entity="SecretData" stepKey="create2"/> + <createData entity="SecretData" stepKey="create3"> + <requiredEntity createDataKey="create1"/> + <requiredEntity createDataKey="create2"/> + </createData> + <fillField selector="#fill" userInput="{{SecretData.key2}}+{{SecretData.key2}}" stepKey="fillBefore"/> + <click userInput="$create2.key2$" stepKey="click"/> + <actionGroup ref="ActionGroupReturningValueActionGroup" stepKey="return1"> + <argument name="count" value="1"/> + </actionGroup> + </before> + <after> + <actionGroup ref="ExtendedActionGroupReturningValueActionGroup" stepKey="return2"> + <argument name="count" value="1"/> + <argument name="otherCount" value="2"/> + </actionGroup> + <deleteData createDataKey="create1" stepKey="delete1"/> + <deleteData createDataKey="create2" stepKey="delete2"/> + <deleteData createDataKey="create3" stepKey="delete3"/> + <deleteData url="deleteThis" stepKey="deleteThis"/> + <fillField selector="#fill" userInput="{{SecretData.key2}}" stepKey="fillAfter"/> + <magentoCLI command="{{SecretData.key2}}-{{SecretData.key1}}-{{SecretData.key2}}" stepKey="cli2"/> + </after> + </suite> +</suites> diff --git a/dev/tests/verification/TestModule/Suite/functionalSuite/functionalSuite1.xml b/dev/tests/verification/TestModule/Suite/functionalSuite/functionalSuite1.xml new file mode 100644 index 000000000..5f78e043e --- /dev/null +++ b/dev/tests/verification/TestModule/Suite/functionalSuite/functionalSuite1.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<suites xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Suite/etc/suiteSchema.xsd"> + <suite name="functionalSuite1"> + <include> + <group name = "include" /> + <test name="IncludeTest"/> + <test name="additionalExcludeTest2"/> + <test name="additionalIncludeTest2"/> + </include> + <exclude> + <group name="exclude"/> + <test name="ExcludeTest2"/> + </exclude> + </suite> +</suites> diff --git a/dev/tests/verification/TestModule/Suite/functionalSuite/functionalSuite2.xml b/dev/tests/verification/TestModule/Suite/functionalSuite/functionalSuite2.xml new file mode 100644 index 000000000..c65cf6cfc --- /dev/null +++ b/dev/tests/verification/TestModule/Suite/functionalSuite/functionalSuite2.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<suites xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Suite/etc/suiteSchema.xsd"> + <suite name="functionalSuite2"> + <include> + <group name="include"/> + <test name="IncludeTest"/> + <test name="additionalExcludeTest2"/> + <test name="additionalIncludeTest2"/> + </include> + <exclude> + <group name="exclude"/> + <test name="ExcludeTest2"/> + </exclude> + </suite> +</suites> diff --git a/dev/tests/verification/_suite/functionalSuiteHooks.xml b/dev/tests/verification/TestModule/Suite/functionalSuite/functionalSuiteWithComments.xml similarity index 64% rename from dev/tests/verification/_suite/functionalSuiteHooks.xml rename to dev/tests/verification/TestModule/Suite/functionalSuite/functionalSuiteWithComments.xml index f86f8c43c..ffc0e15db 100644 --- a/dev/tests/verification/_suite/functionalSuiteHooks.xml +++ b/dev/tests/verification/TestModule/Suite/functionalSuite/functionalSuiteWithComments.xml @@ -6,29 +6,29 @@ */ --> -<suites xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../src/Magento/FunctionalTestingFramework/Suite/etc/suiteSchema.xsd"> - <suite name="functionalSuiteHooks"> +<suites xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Suite/etc/suiteSchema.xsd"> + <suite name="functionalSuiteWithComments"> <include> + <!-- Comment Block--> <test name="IncludeTest"/> </include> <before> + <!-- Comment in Before--> <amOnPage url="some.url" stepKey="before"/> <createData entity="createThis" stepKey="create"> + <!--Comment in Nested Element--> <field key="someKey">dataHere</field> </createData> + <!-- <click stepKey="comment with element" userInput="helloworld"/> --> <click stepKey="clickWithData" userInput="$create.data$"/> <actionGroup ref="actionGroupWithTwoArguments" stepKey="AC"> + <!--Comment in AG Args--> <argument name="somePerson" value="simpleData"/> <argument name="anotherPerson" value="uniqueData"/> </actionGroup> </before> <after> - <amOnPage url="some.url" stepKey="after"/> - <deleteData url="deleteThis" stepKey="delete"/> - <actionGroup ref="actionGroupWithTwoArguments" stepKey="AC"> - <argument name="somePerson" value="simpleData"/> - <argument name="anotherPerson" value="uniqueData"/> - </actionGroup> + <comment userInput="afterBlock" stepKey="afterBlock"/> </after> </suite> </suites> diff --git a/dev/tests/verification/_suite/functionalSuiteExtends.xml b/dev/tests/verification/TestModule/Suite/functionalSuiteExtends.xml similarity index 76% rename from dev/tests/verification/_suite/functionalSuiteExtends.xml rename to dev/tests/verification/TestModule/Suite/functionalSuiteExtends.xml index aac3b51e5..44af78d20 100644 --- a/dev/tests/verification/_suite/functionalSuiteExtends.xml +++ b/dev/tests/verification/TestModule/Suite/functionalSuiteExtends.xml @@ -6,7 +6,7 @@ */ --> -<suites xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../src/Magento/FunctionalTestingFramework/Suite/etc/suiteSchema.xsd"> +<suites xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Suite/etc/suiteSchema.xsd"> <suite name="suiteExtends"> <include> <group name="ExtendedTestInSuite"/> diff --git a/dev/tests/verification/TestModule/Suite/functionalSuiteHooks.xml b/dev/tests/verification/TestModule/Suite/functionalSuiteHooks.xml new file mode 100644 index 000000000..b4dca7f8d --- /dev/null +++ b/dev/tests/verification/TestModule/Suite/functionalSuiteHooks.xml @@ -0,0 +1,42 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<suites xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Suite/etc/suiteSchema.xsd"> + <suite name="functionalSuiteHooks"> + <include> + <test name="IncludeTest"/> + </include> + <before> + <amOnPage url="some.url" stepKey="before"/> + <createData entity="createEntityOne" stepKey="createOne"> + <field key="someKey">dataHere</field> + </createData> + <createData entity="createEntityTwo" stepKey="createTwo"> + <requiredEntity createDataKey="createEntityOne"/> + </createData> + <createData entity="createEntityThree" stepKey="createThree"/> + <createData entity="createEntityFour" stepKey="createFour"> + <requiredEntity createDataKey="createEntityTwo"/> + <requiredEntity createDataKey="createEntityThree"/> + </createData> + <click stepKey="clickWithData" userInput="$createTwo.data$"/> + <actionGroup ref="actionGroupWithTwoArguments" stepKey="AC"> + <argument name="somePerson" value="simpleData"/> + <argument name="anotherPerson" value="uniqueData"/> + </actionGroup> + </before> + <after> + <amOnPage url="some.url" stepKey="after"/> + <deleteData url="deleteThis" stepKey="delete"/> + <actionGroup ref="actionGroupWithTwoArguments" stepKey="AC"> + <argument name="somePerson" value="simpleData"/> + <argument name="anotherPerson" value="uniqueData"/> + </actionGroup> + </after> + </suite> +</suites> diff --git a/dev/tests/verification/TestModule/Test/ActionGroupFunctionalTest.xml b/dev/tests/verification/TestModule/Test/ActionGroupFunctionalTest.xml deleted file mode 100644 index 8c24794e1..000000000 --- a/dev/tests/verification/TestModule/Test/ActionGroupFunctionalTest.xml +++ /dev/null @@ -1,187 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ ---> - -<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> - <test name="BasicActionGroupTest"> - <annotations> - <severity value="CRITICAL"/> - <group value="functional"/> - <features value="Action Group Functional Cest"/> - <stories value="MQE-433"/> - </annotations> - <before> - <createData entity="ReplacementPerson" stepKey="createPersonParam"/> - <actionGroup ref="FunctionalActionGroup" stepKey="beforeGroup"/> - </before> - <amOnPage stepKey="step1" url="/someUrl"/> - <actionGroup ref="FunctionalActionGroup" stepKey="actionGroup1"/> - <click stepKey="step6" selector="loginButton"/> - </test> - <test name="ActionGroupWithDataTest"> - <annotations> - <severity value="CRITICAL"/> - <group value="functional"/> - <features value="Action Group Functional Cest"/> - <stories value="MQE-433"/> - </annotations> - <before> - <createData entity="ReplacementPerson" stepKey="createPersonParam"/> - <actionGroup ref="FunctionalActionGroup" stepKey="beforeGroup"/> - </before> - <amOnPage stepKey="step1" url="/someUrl"/> - <actionGroup ref="FunctionalActionGroupWithData" stepKey="actionGroupWithData1"/> - <click stepKey="step6" selector="loginButton"/> - <after> - <actionGroup ref="FunctionalActionGroup" stepKey="afterGroup"/> - </after> - </test> - <test name="ActionGroupWithDataOverrideTest"> - <annotations> - <severity value="CRITICAL"/> - <group value="functional"/> - <features value="Action Group Functional Cest"/> - <stories value="MQE-433"/> - </annotations> - <before> - <createData entity="ReplacementPerson" stepKey="createPersonParam"/> - <actionGroup ref="FunctionalActionGroup" stepKey="beforeGroup"/> - </before> - <amOnPage stepKey="step1" url="/someUrl"/> - <actionGroup ref="FunctionalActionGroupWithData" stepKey="actionGroupWithDataOverride1"> - <argument name="person" value="ReplacementPerson"/> - </actionGroup> - <click stepKey="step6" selector="loginButton"/> - <after> - <actionGroup ref="FunctionalActionGroup" stepKey="afterGroup"/> - </after> - </test> - <test name="ActionGroupWithNoDefaultTest"> - <annotations> - <severity value="CRITICAL"/> - <group value="functional"/> - <features value="Action Group Functional Cest"/> - <stories value="MQE-433"/> - </annotations> - <before> - <createData entity="ReplacementPerson" stepKey="createPersonParam"/> - <actionGroup ref="FunctionalActionGroup" stepKey="beforeGroup"/> - </before> - <amOnPage stepKey="step1" url="/someUrl"/> - <actionGroup ref="FunctionalActionGroupNoDefault" stepKey="actionGroupWithDataOverride1"> - <argument name="person" value="DefaultPerson"/> - </actionGroup> - <click stepKey="step6" selector="loginButton"/> - <after> - <actionGroup ref="FunctionalActionGroup" stepKey="afterGroup"/> - </after> - </test> - <test name="ActionGroupWithPersistedData"> - <annotations> - <severity value="CRITICAL"/> - <group value="functional"/> - <features value="Action Group Functional Cest"/> - <stories value="MQE-433"/> - </annotations> - <before> - <createData entity="ReplacementPerson" stepKey="createPersonParam"/> - <actionGroup ref="FunctionalActionGroup" stepKey="beforeGroup"/> - </before> - <createData entity="DefaultPerson" stepKey="createPerson"/> - <actionGroup ref="FunctionalActionGroupWithData" stepKey="actionGroupWithPersistedData1"> - <argument name="person" value="$createPerson$"/> - </actionGroup> - <after> - <actionGroup ref="FunctionalActionGroup" stepKey="afterGroup"/> - </after> - </test> - <test name="ActionGroupWithTopLevelPersistedData"> - <annotations> - <severity value="CRITICAL"/> - <group value="functional"/> - <features value="Action Group Functional Cest"/> - <stories value="MQE-433"/> - </annotations> - <before> - <createData entity="ReplacementPerson" stepKey="createPersonParam"/> - <actionGroup ref="FunctionalActionGroup" stepKey="beforeGroup"/> - </before> - <actionGroup ref="FunctionalActionGroupWithData" stepKey="actionGroupWithPersistedData1"> - <argument name="person" value="$$createPersonParam$$"/> - </actionGroup> - <after> - <actionGroup ref="FunctionalActionGroup" stepKey="afterGroup"/> - </after> - </test> - <test name="MultipleActionGroupsTest"> - <annotations> - <severity value="CRITICAL"/> - <group value="functional"/> - <features value="Action Group Functional Cest"/> - <stories value="MQE-433"/> - </annotations> - <before> - <createData entity="ReplacementPerson" stepKey="createPersonParam"/> - <actionGroup ref="FunctionalActionGroup" stepKey="beforeGroup"/> - </before> - <amOnPage stepKey="step1" url="/someUrl"/> - <actionGroup ref="FunctionalActionGroupWithData" stepKey="actionGroup1"/> - <click stepKey="step6" selector="loginButton"/> - <actionGroup ref="FunctionalActionGroupWithData" stepKey="actionGroupWithDataOverride2"> - <argument name="person" value="ReplacementPerson"/> - </actionGroup> - <after> - <actionGroup ref="FunctionalActionGroup" stepKey="afterGroup"/> - </after> - </test> - <test name="MergedActionGroupTest"> - <annotations> - <severity value="CRITICAL"/> - <group value="functional"/> - <features value="Action Group Functional Cest"/> - <stories value="MQE-433"/> - </annotations> - <before> - <createData entity="ReplacementPerson" stepKey="createPersonParam"/> - <actionGroup ref="FunctionalActionGroup" stepKey="beforeGroup"/> - </before> - <actionGroup ref="FunctionalActionGroupForMerge" stepKey="actionGroupForMerge"> - <argument name="myArg" value="DefaultPerson"/> - </actionGroup> - <after> - <actionGroup ref="FunctionalActionGroup" stepKey="afterGroup"/> - </after> - </test> - <test name="ArgumentWithSameNameAsElement"> - <annotations> - <severity value="CRITICAL"/> - <group value="functional"/> - <features value="Action Group Functional Cest"/> - <stories value="MQE-433"/> - </annotations> - <before> - <createData entity="ReplacementPerson" stepKey="createPersonParam"/> - <actionGroup ref="FunctionalActionGroup" stepKey="beforeGroup"/> - </before> - <actionGroup ref="FunctionalActionGroupWithTrickyArgument" stepKey="actionGroup1"/> - <after> - <actionGroup ref="FunctionalActionGroup" stepKey="afterGroup"/> - </after> - </test> - <test name="ActionGroupMergedViaInsertBefore"> - <actionGroup ref="FunctionalActionGroupForMassMergeBefore" stepKey="keyone"/> - </test> - <test name="ActionGroupMergedViaInsertAfter"> - <actionGroup ref="FunctionalActionGroupForMassMergeAfter" stepKey="keyone"/> - </test> - <test name="PersistedAndXmlEntityArguments"> - <actionGroup ref="FunctionalActionGroupWithXmlAndPersistedData" stepKey="afterGroup"> - <argument name="persistedData" value="$persistedInTest$"/> - </actionGroup> - </test> -</tests> diff --git a/dev/tests/verification/TestModule/Test/ActionGroupFunctionalTest/ActionGroupMergedViaInsertAfter.xml b/dev/tests/verification/TestModule/Test/ActionGroupFunctionalTest/ActionGroupMergedViaInsertAfter.xml new file mode 100644 index 000000000..0bca36759 --- /dev/null +++ b/dev/tests/verification/TestModule/Test/ActionGroupFunctionalTest/ActionGroupMergedViaInsertAfter.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="ActionGroupMergedViaInsertAfter"> + <actionGroup ref="FunctionalActionGroupForMassMergeAfter" stepKey="keyone"/> + </test> +</tests> diff --git a/dev/tests/verification/TestModule/Test/ActionGroupFunctionalTest/ActionGroupMergedViaInsertBefore.xml b/dev/tests/verification/TestModule/Test/ActionGroupFunctionalTest/ActionGroupMergedViaInsertBefore.xml new file mode 100644 index 000000000..06d9c965d --- /dev/null +++ b/dev/tests/verification/TestModule/Test/ActionGroupFunctionalTest/ActionGroupMergedViaInsertBefore.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="ActionGroupMergedViaInsertBefore"> + <actionGroup ref="FunctionalActionGroupForMassMergeBefore" stepKey="keyone"/> + </test> +</tests> diff --git a/dev/tests/verification/TestModule/Test/ActionGroupFunctionalTest/ActionGroupReturningValueTest.xml b/dev/tests/verification/TestModule/Test/ActionGroupFunctionalTest/ActionGroupReturningValueTest.xml new file mode 100644 index 000000000..ade1f51db --- /dev/null +++ b/dev/tests/verification/TestModule/Test/ActionGroupFunctionalTest/ActionGroupReturningValueTest.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="ActionGroupReturningValueTest"> + <annotations> + <severity value="CRITICAL"/> + <group value="functional"/> + <features value="Action Group Functional Cest"/> + <stories value="MQE-433"/> + </annotations> + <before> + <createData entity="ReplacementPerson" stepKey="createPersonParam"/> + <actionGroup ref="FunctionalActionGroup" stepKey="beforeGroup"/> + </before> + <amOnPage url="/someUrl" stepKey="step1"/> + <actionGroup ref="FunctionalActionGroupWithReturnValueActionGroup" stepKey="actionGroupWithReturnValue1"/> + <actionGroup ref="actionGroupWithStringUsage" stepKey="actionGroupWithStringUsage1"> + <argument name="someArgument" value="{$actionGroupWithReturnValue1}"/> + </actionGroup> + <after> + <actionGroup ref="FunctionalActionGroup" stepKey="afterGroup"/> + </after> + </test> +</tests> diff --git a/dev/tests/verification/TestModule/Test/ActionGroupFunctionalTest/ActionGroupWithDataOverrideTest.xml b/dev/tests/verification/TestModule/Test/ActionGroupFunctionalTest/ActionGroupWithDataOverrideTest.xml new file mode 100644 index 000000000..e1b72784d --- /dev/null +++ b/dev/tests/verification/TestModule/Test/ActionGroupFunctionalTest/ActionGroupWithDataOverrideTest.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="ActionGroupWithDataOverrideTest"> + <annotations> + <severity value="CRITICAL"/> + <group value="functional"/> + <features value="Action Group Functional Cest"/> + <stories value="MQE-433"/> + </annotations> + <before> + <createData entity="ReplacementPerson" stepKey="createPersonParam"/> + <actionGroup ref="FunctionalActionGroup" stepKey="beforeGroup"/> + </before> + <amOnPage stepKey="step1" url="/someUrl"/> + <actionGroup ref="FunctionalActionGroupWithData" stepKey="actionGroupWithDataOverride1"> + <argument name="person" value="ReplacementPerson"/> + </actionGroup> + <click stepKey="step6" selector="loginButton"/> + <after> + <actionGroup ref="FunctionalActionGroup" stepKey="afterGroup"/> + </after> + </test> +</tests> diff --git a/dev/tests/verification/TestModule/Test/ActionGroupFunctionalTest/ActionGroupWithDataTest.xml b/dev/tests/verification/TestModule/Test/ActionGroupFunctionalTest/ActionGroupWithDataTest.xml new file mode 100644 index 000000000..379d79a48 --- /dev/null +++ b/dev/tests/verification/TestModule/Test/ActionGroupFunctionalTest/ActionGroupWithDataTest.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="ActionGroupWithDataTest"> + <annotations> + <severity value="CRITICAL"/> + <group value="functional"/> + <features value="Action Group Functional Cest"/> + <stories value="MQE-433"/> + </annotations> + <before> + <createData entity="ReplacementPerson" stepKey="createPersonParam"/> + <actionGroup ref="FunctionalActionGroup" stepKey="beforeGroup"/> + </before> + <amOnPage stepKey="step1" url="/someUrl"/> + <actionGroup ref="FunctionalActionGroupWithData" stepKey="actionGroupWithData1"/> + <click stepKey="step6" selector="loginButton"/> + <after> + <actionGroup ref="FunctionalActionGroup" stepKey="afterGroup"/> + </after> + </test> +</tests> diff --git a/dev/tests/verification/TestModule/Test/ActionGroupFunctionalTest/ActionGroupWithNoDefaultTest.xml b/dev/tests/verification/TestModule/Test/ActionGroupFunctionalTest/ActionGroupWithNoDefaultTest.xml new file mode 100644 index 000000000..d5e77c7e7 --- /dev/null +++ b/dev/tests/verification/TestModule/Test/ActionGroupFunctionalTest/ActionGroupWithNoDefaultTest.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="ActionGroupWithNoDefaultTest"> + <annotations> + <severity value="CRITICAL"/> + <group value="functional"/> + <features value="Action Group Functional Cest"/> + <stories value="MQE-433"/> + </annotations> + <before> + <createData entity="ReplacementPerson" stepKey="createPersonParam"/> + <actionGroup ref="FunctionalActionGroup" stepKey="beforeGroup"/> + </before> + <amOnPage stepKey="step1" url="/someUrl"/> + <actionGroup ref="FunctionalActionGroupNoDefault" stepKey="actionGroupWithDataOverride1"> + <argument name="person" value="DefaultPerson"/> + </actionGroup> + <click stepKey="step6" selector="loginButton"/> + <after> + <actionGroup ref="FunctionalActionGroup" stepKey="afterGroup"/> + </after> + </test> +</tests> diff --git a/dev/tests/verification/TestModule/Test/ActionGroupFunctionalTest/ActionGroupWithPersistedData.xml b/dev/tests/verification/TestModule/Test/ActionGroupFunctionalTest/ActionGroupWithPersistedData.xml new file mode 100644 index 000000000..bfb8a7cc2 --- /dev/null +++ b/dev/tests/verification/TestModule/Test/ActionGroupFunctionalTest/ActionGroupWithPersistedData.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="ActionGroupWithPersistedData"> + <annotations> + <severity value="CRITICAL"/> + <group value="functional"/> + <features value="Action Group Functional Cest"/> + <stories value="MQE-433"/> + </annotations> + <before> + <createData entity="ReplacementPerson" stepKey="createPersonParam"/> + <actionGroup ref="FunctionalActionGroup" stepKey="beforeGroup"/> + </before> + <createData entity="DefaultPerson" stepKey="createPerson"/> + <actionGroup ref="FunctionalActionGroupWithData" stepKey="actionGroupWithPersistedData1"> + <argument name="person" value="$createPerson$"/> + </actionGroup> + <after> + <actionGroup ref="FunctionalActionGroup" stepKey="afterGroup"/> + </after> + </test> +</tests> diff --git a/dev/tests/verification/TestModule/Test/ActionGroupFunctionalTest/ActionGroupWithTopLevelPersistedData.xml b/dev/tests/verification/TestModule/Test/ActionGroupFunctionalTest/ActionGroupWithTopLevelPersistedData.xml new file mode 100644 index 000000000..a7f2a558b --- /dev/null +++ b/dev/tests/verification/TestModule/Test/ActionGroupFunctionalTest/ActionGroupWithTopLevelPersistedData.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="ActionGroupWithTopLevelPersistedData"> + <annotations> + <severity value="CRITICAL"/> + <group value="functional"/> + <features value="Action Group Functional Cest"/> + <stories value="MQE-433"/> + </annotations> + <before> + <createData entity="ReplacementPerson" stepKey="createPersonParam"/> + <actionGroup ref="FunctionalActionGroup" stepKey="beforeGroup"/> + </before> + <actionGroup ref="FunctionalActionGroupWithData" stepKey="actionGroupWithPersistedData1"> + <argument name="person" value="$$createPersonParam$$"/> + </actionGroup> + <after> + <actionGroup ref="FunctionalActionGroup" stepKey="afterGroup"/> + </after> + </test> +</tests> diff --git a/dev/tests/verification/TestModule/Test/ActionGroupFunctionalTest/ArgumentWithSameNameAsElement.xml b/dev/tests/verification/TestModule/Test/ActionGroupFunctionalTest/ArgumentWithSameNameAsElement.xml new file mode 100644 index 000000000..e8e994d46 --- /dev/null +++ b/dev/tests/verification/TestModule/Test/ActionGroupFunctionalTest/ArgumentWithSameNameAsElement.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="ArgumentWithSameNameAsElement"> + <annotations> + <severity value="CRITICAL"/> + <group value="functional"/> + <features value="Action Group Functional Cest"/> + <stories value="MQE-433"/> + </annotations> + <before> + <createData entity="ReplacementPerson" stepKey="createPersonParam"/> + <actionGroup ref="FunctionalActionGroup" stepKey="beforeGroup"/> + </before> + <actionGroup ref="FunctionalActionGroupWithTrickyArgument" stepKey="actionGroup1"/> + <after> + <actionGroup ref="FunctionalActionGroup" stepKey="afterGroup"/> + </after> + </test> +</tests> diff --git a/dev/tests/verification/TestModule/Test/ActionGroupFunctionalTest/BasicActionGroupTest.xml b/dev/tests/verification/TestModule/Test/ActionGroupFunctionalTest/BasicActionGroupTest.xml new file mode 100644 index 000000000..0c72b0f19 --- /dev/null +++ b/dev/tests/verification/TestModule/Test/ActionGroupFunctionalTest/BasicActionGroupTest.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="BasicActionGroupTest"> + <annotations> + <severity value="CRITICAL"/> + <group value="functional"/> + <features value="Action Group Functional Cest"/> + <stories value="MQE-433"/> + </annotations> + <before> + <createData entity="ReplacementPerson" stepKey="createPersonParam"/> + <actionGroup ref="FunctionalActionGroup" stepKey="beforeGroup"/> + </before> + <amOnPage stepKey="step1" url="/someUrl"/> + <actionGroup ref="FunctionalActionGroup" stepKey="actionGroup1"/> + <click stepKey="step6" selector="loginButton"/> + </test> +</tests> diff --git a/dev/tests/verification/TestModule/Test/ActionGroupFunctionalTest/MergedActionGroupTest.xml b/dev/tests/verification/TestModule/Test/ActionGroupFunctionalTest/MergedActionGroupTest.xml new file mode 100644 index 000000000..a5a3a0685 --- /dev/null +++ b/dev/tests/verification/TestModule/Test/ActionGroupFunctionalTest/MergedActionGroupTest.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="MergedActionGroupTest"> + <annotations> + <severity value="CRITICAL"/> + <group value="functional"/> + <features value="Action Group Functional Cest"/> + <stories value="MQE-433"/> + </annotations> + <before> + <createData entity="ReplacementPerson" stepKey="createPersonParam"/> + <actionGroup ref="FunctionalActionGroup" stepKey="beforeGroup"/> + </before> + <actionGroup ref="FunctionalActionGroupForMerge" stepKey="actionGroupForMerge"> + <argument name="myArg" value="DefaultPerson"/> + </actionGroup> + <after> + <actionGroup ref="FunctionalActionGroup" stepKey="afterGroup"/> + </after> + </test> +</tests> diff --git a/dev/tests/verification/TestModule/Test/ActionGroupFunctionalTest/MultipleActionGroupsTest.xml b/dev/tests/verification/TestModule/Test/ActionGroupFunctionalTest/MultipleActionGroupsTest.xml new file mode 100644 index 000000000..c5473b789 --- /dev/null +++ b/dev/tests/verification/TestModule/Test/ActionGroupFunctionalTest/MultipleActionGroupsTest.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="MultipleActionGroupsTest"> + <annotations> + <severity value="CRITICAL"/> + <group value="functional"/> + <features value="Action Group Functional Cest"/> + <stories value="MQE-433"/> + </annotations> + <before> + <createData entity="ReplacementPerson" stepKey="createPersonParam"/> + <actionGroup ref="FunctionalActionGroup" stepKey="beforeGroup"/> + </before> + <amOnPage stepKey="step1" url="/someUrl"/> + <actionGroup ref="FunctionalActionGroupWithData" stepKey="actionGroup1"/> + <click stepKey="step6" selector="loginButton"/> + <actionGroup ref="FunctionalActionGroupWithData" stepKey="actionGroupWithDataOverride2"> + <argument name="person" value="ReplacementPerson"/> + </actionGroup> + <after> + <actionGroup ref="FunctionalActionGroup" stepKey="afterGroup"/> + </after> + </test> +</tests> diff --git a/dev/tests/verification/TestModule/Test/ActionGroupFunctionalTest/PersistedAndXmlEntityArguments.xml b/dev/tests/verification/TestModule/Test/ActionGroupFunctionalTest/PersistedAndXmlEntityArguments.xml new file mode 100644 index 000000000..defd72174 --- /dev/null +++ b/dev/tests/verification/TestModule/Test/ActionGroupFunctionalTest/PersistedAndXmlEntityArguments.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="PersistedAndXmlEntityArguments"> + <actionGroup ref="FunctionalActionGroupWithXmlAndPersistedData" stepKey="afterGroup"> + <argument name="persistedData" value="$persistedInTest$"/> + </actionGroup> + </test> +</tests> diff --git a/dev/tests/verification/TestModule/Test/ActionGroupTest.xml b/dev/tests/verification/TestModule/Test/ActionGroupTest.xml deleted file mode 100644 index 5dce96934..000000000 --- a/dev/tests/verification/TestModule/Test/ActionGroupTest.xml +++ /dev/null @@ -1,181 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ ---> -<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> - <test name="ActionGroupWithNoArguments"> - <annotations> - <severity value="BLOCKER"/> - <title value="Action Group With No Argument"/> - </annotations> - <actionGroup ref="actionGroupWithoutArguments" stepKey="actionGroup"/> - </test> - - <test name="ActionGroupWithDefaultArgumentAndStringSelectorParam"> - <annotations> - <severity value="BLOCKER"/> - <title value="Action Group With Default Argument Value and Hardcoded Value in Param"/> - </annotations> - - <actionGroup ref="actionGroupWithDefaultArgumentAndStringSelectorParam" stepKey="actionGroup"/> - </test> - - <test name="ActionGroupWithPassedArgumentAndStringSelectorParam"> - <annotations> - <severity value="BLOCKER"/> - <title value="Action Group With Passed Argument Value and Hardcoded Value in Param"/> - </annotations> - - <actionGroup ref="actionGroupWithDefaultArgumentAndStringSelectorParam" stepKey="actionGroup"> - <argument name="someArgument" value="UniquePerson"/> - </actionGroup> - </test> - - <test name="ActionGroupWithSingleParameterSelectorFromDefaultArgument"> - <annotations> - <severity value="BLOCKER"/> - <title value="Action Group With Default Argument Value and Argument Value in Param"/> - </annotations> - - <actionGroup ref="actionGroupWithSingleParameterSelectorFromArgument" stepKey="actionGroup"/> - </test> - - <test name="ActionGroupWithSingleParameterSelectorFromPassedArgument"> - <annotations> - <severity value="BLOCKER"/> - <title value="Action Group With Passed Argument Value and Argument Value in Param"/> - </annotations> - - <actionGroup ref="actionGroupWithSingleParameterSelectorFromArgument" stepKey="actionGroup"> - <argument name="someArgument" value="UniquePerson"/> - </actionGroup> - </test> - - <test name="ActionGroupWithMultipleParameterSelectorsFromDefaultArgument"> - <annotations> - <severity value="BLOCKER"/> - <title value="Action Group With Passed Argument Value and Multiple Argument Values in Param"/> - </annotations> - - <actionGroup ref="actionGroupWithMultipleParameterSelectorsFromArgument" stepKey="actionGroup"/> - </test> - - <test name="ActionGroupWithSimpleDataUsageFromPassedArgument"> - <annotations> - <severity value="CRITICAL"/> - <title value="Action Group With Simple Data Usage From Passed Argument"/> - </annotations> - - <actionGroup ref="actionGroupWithStringUsage" stepKey="actionGroup1"> - <argument name="someArgument" value="overrideString"/> - </actionGroup> - <actionGroup ref="actionGroupWithStringUsage" stepKey="actionGroup11"> - <argument name="someArgument" value="1"/> - </actionGroup> - <actionGroup ref="actionGroupWithStringUsage" stepKey="actionGroup12"> - <argument name="someArgument" value="1.5"/> - </actionGroup> - <actionGroup ref="actionGroupWithStringUsage" stepKey="actionGroup13"> - <argument name="someArgument" value="true"/> - </actionGroup> - <actionGroup ref="actionGroupWithStringUsage" stepKey="actionGroup2"> - <argument name="someArgument" value="simpleData.firstname"/> - </actionGroup> - <actionGroup ref="actionGroupWithStringUsage" stepKey="actionGroup3"> - <argument name="someArgument" value="$persisted.data$"/> - </actionGroup> - - <actionGroup ref="actionGroupWithEntityUsage" stepKey="actionGroup4"> - <argument name="someArgument" value="simpleData.firstname"/> - </actionGroup> - <actionGroup ref="actionGroupWithEntityUsage" stepKey="actionGroup5"> - <argument name="someArgument" value="$simpleData.firstname$"/> - </actionGroup> - <actionGroup ref="actionGroupWithEntityUsage" stepKey="actionGroup6"> - <argument name="someArgument" value="$simpleData.firstname[0]$"/> - </actionGroup> - <actionGroup ref="actionGroupWithEntityUsage" stepKey="actionGroup7"> - <argument name="someArgument" value="$simpleData.firstname[data_index]$"/> - </actionGroup> - </test> - - <test name="ActionGroupWithSimpleDataUsageFromDefaultArgument"> - <annotations> - <severity value="CRITICAL"/> - <title value="Action Group With Simple Data Usage From Default Argument"/> - </annotations> - <actionGroup ref="actionGroupWithStringUsage" stepKey="actionGroup"/> - </test> - - <test name="ActionGroupWithStepKeyReferences"> - <actionGroup ref="FunctionActionGroupWithStepKeyReferences" stepKey="actionGroup"/> - </test> - - <test name="ActionGroupUsingNestedArgument"> - <actionGroup ref="ActionGroupToExtend" stepKey="actionGroup"> - <argument name="count" value="99"/> - </actionGroup> - </test> - - <test name="ActionGroupToExtend"> - <actionGroup ref="ActionGroupToExtend" stepKey="actionGroup"> - <argument name="count" value="99"/> - </actionGroup> - </test> - - <test name="ExtendedActionGroup"> - <actionGroup ref="extendTestActionGroup" stepKey="actionGroup"> - <argument name="count" value="99"/> - <argument name="otherCount" value="8000"/> - </actionGroup> - </test> - - <test name="ExtendedRemoveActionGroup"> - <actionGroup ref="extendRemoveTestActionGroup" stepKey="actionGroup"/> - </test> - - <test name="ActionGroupUsingCreateData"> - <before> - <actionGroup ref="actionGroupWithCreateData" stepKey="Key1"/> - </before> - </test> - - <test name="ActionGroupSkipReadiness"> - <actionGroup ref="actionGroupWithSkipReadinessActions" stepKey="skipReadinessActionGroup"/> - </test> - - <test name="ActionGroupContainsStepKeyInArgText"> - <before> - <actionGroup ref="actionGroupContainsStepKeyInArgValue" stepKey="actionGroup"> - <argument name="sameStepKeyAsArg" value="arg1"/> - </actionGroup> - </before> - <actionGroup ref="actionGroupContainsStepKeyInArgValue" stepKey="actionGroup"> - <argument name="sameStepKeyAsArg" value="arg1"/> - </actionGroup> - </test> - - <test name="ActionGroupWithSectionAndDataAsArguments"> - <actionGroup ref="actionGroupWithSectionAndData" stepKey="actionGroup"> - <argument name="content" value="{{simpleData.firstname}}"/> - <argument name="section" value="SampleSection"/> - </actionGroup> - </test> - - <test name="ActionGroupWithParameterizedElementWithHyphen"> - <actionGroup ref="SectionArgumentWithParameterizedSelector" stepKey="actionGroup"> - <argument name="section" value="SampleSection"/> - </actionGroup> - </test> - - <test name="ActionGroupWithParameterizedElementsWithStepKeyReferences"> - <actionGroup ref="actionGroupWithParametrizedSelectors" stepKey="actionGroup"> - <argument name="param" value="simpleData"/> - <argument name="param2" value="simpleParamData"/> - </actionGroup> - </test> -</tests> diff --git a/dev/tests/verification/TestModule/Test/ActionGroupTest/ActionGroupContainsStepKeyInArgText.xml b/dev/tests/verification/TestModule/Test/ActionGroupTest/ActionGroupContainsStepKeyInArgText.xml new file mode 100644 index 000000000..bdea3c163 --- /dev/null +++ b/dev/tests/verification/TestModule/Test/ActionGroupTest/ActionGroupContainsStepKeyInArgText.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="ActionGroupContainsStepKeyInArgText"> + <before> + <actionGroup ref="actionGroupContainsStepKeyInArgValue" stepKey="actionGroup"> + <argument name="sameStepKeyAsArg" value="arg1"/> + </actionGroup> + </before> + <actionGroup ref="actionGroupContainsStepKeyInArgValue" stepKey="actionGroup"> + <argument name="sameStepKeyAsArg" value="arg1"/> + </actionGroup> + </test> +</tests> diff --git a/dev/tests/verification/TestModule/Test/ActionGroupTest/ActionGroupToExtend.xml b/dev/tests/verification/TestModule/Test/ActionGroupTest/ActionGroupToExtend.xml new file mode 100644 index 000000000..a08122fdb --- /dev/null +++ b/dev/tests/verification/TestModule/Test/ActionGroupTest/ActionGroupToExtend.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="ActionGroupToExtend"> + <actionGroup ref="ActionGroupToExtend" stepKey="actionGroup"> + <argument name="count" value="99"/> + </actionGroup> + </test> +</tests> diff --git a/dev/tests/verification/TestModule/Test/ActionGroupTest/ActionGroupUsingCreateData.xml b/dev/tests/verification/TestModule/Test/ActionGroupTest/ActionGroupUsingCreateData.xml new file mode 100644 index 000000000..92d142700 --- /dev/null +++ b/dev/tests/verification/TestModule/Test/ActionGroupTest/ActionGroupUsingCreateData.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="ActionGroupUsingCreateData"> + <before> + <actionGroup ref="actionGroupWithCreateData" stepKey="Key1"/> + </before> + </test> +</tests> diff --git a/dev/tests/verification/TestModule/Test/ActionGroupTest/ActionGroupUsingNestedArgument.xml b/dev/tests/verification/TestModule/Test/ActionGroupTest/ActionGroupUsingNestedArgument.xml new file mode 100644 index 000000000..100c498b6 --- /dev/null +++ b/dev/tests/verification/TestModule/Test/ActionGroupTest/ActionGroupUsingNestedArgument.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="ActionGroupUsingNestedArgument"> + <actionGroup ref="ActionGroupToExtend" stepKey="actionGroup"> + <argument name="count" value="99"/> + </actionGroup> + </test> +</tests> diff --git a/dev/tests/verification/TestModule/Test/ActionGroupTest/ActionGroupWithDefaultArgumentAndStringSelectorParam.xml b/dev/tests/verification/TestModule/Test/ActionGroupTest/ActionGroupWithDefaultArgumentAndStringSelectorParam.xml new file mode 100644 index 000000000..7d780ef11 --- /dev/null +++ b/dev/tests/verification/TestModule/Test/ActionGroupTest/ActionGroupWithDefaultArgumentAndStringSelectorParam.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="ActionGroupWithDefaultArgumentAndStringSelectorParam"> + <annotations> + <severity value="BLOCKER"/> + <title value="Action Group With Default Argument Value and Hardcoded Value in Param"/> + </annotations> + + <actionGroup ref="actionGroupWithDefaultArgumentAndStringSelectorParam" stepKey="actionGroup"/> + </test> +</tests> diff --git a/dev/tests/verification/TestModule/Test/ActionGroupTest/ActionGroupWithMultipleParameterSelectorsFromDefaultArgument.xml b/dev/tests/verification/TestModule/Test/ActionGroupTest/ActionGroupWithMultipleParameterSelectorsFromDefaultArgument.xml new file mode 100644 index 000000000..2607a9fd0 --- /dev/null +++ b/dev/tests/verification/TestModule/Test/ActionGroupTest/ActionGroupWithMultipleParameterSelectorsFromDefaultArgument.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="ActionGroupWithMultipleParameterSelectorsFromDefaultArgument"> + <annotations> + <severity value="BLOCKER"/> + <title value="Action Group With Passed Argument Value and Multiple Argument Values in Param"/> + </annotations> + + <actionGroup ref="actionGroupWithMultipleParameterSelectorsFromArgument" stepKey="actionGroup"/> + </test> +</tests> diff --git a/dev/tests/verification/TestModule/Test/ActionGroupTest/ActionGroupWithNoArguments.xml b/dev/tests/verification/TestModule/Test/ActionGroupTest/ActionGroupWithNoArguments.xml new file mode 100644 index 000000000..36c8b53b2 --- /dev/null +++ b/dev/tests/verification/TestModule/Test/ActionGroupTest/ActionGroupWithNoArguments.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="ActionGroupWithNoArguments"> + <annotations> + <severity value="BLOCKER"/> + <title value="Action Group With No Argument"/> + </annotations> + <actionGroup ref="actionGroupWithoutArguments" stepKey="actionGroup"/> + </test> +</tests> diff --git a/dev/tests/verification/TestModule/Test/ActionGroupTest/ActionGroupWithParameterizedElementWithHyphen.xml b/dev/tests/verification/TestModule/Test/ActionGroupTest/ActionGroupWithParameterizedElementWithHyphen.xml new file mode 100644 index 000000000..261c493f1 --- /dev/null +++ b/dev/tests/verification/TestModule/Test/ActionGroupTest/ActionGroupWithParameterizedElementWithHyphen.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="ActionGroupWithParameterizedElementWithHyphen"> + <actionGroup ref="SectionArgumentWithParameterizedSelector" stepKey="actionGroup"> + <argument name="section" value="SampleSection"/> + </actionGroup> + </test> +</tests> diff --git a/dev/tests/verification/TestModule/Test/ActionGroupTest/ActionGroupWithParameterizedElementsWithStepKeyReferences.xml b/dev/tests/verification/TestModule/Test/ActionGroupTest/ActionGroupWithParameterizedElementsWithStepKeyReferences.xml new file mode 100644 index 000000000..f99004311 --- /dev/null +++ b/dev/tests/verification/TestModule/Test/ActionGroupTest/ActionGroupWithParameterizedElementsWithStepKeyReferences.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="ActionGroupWithParameterizedElementsWithStepKeyReferences"> + <actionGroup ref="actionGroupWithParametrizedSelectors" stepKey="actionGroup"> + <argument name="param" value="simpleData"/> + <argument name="param2" value="simpleParamData"/> + </actionGroup> + </test> +</tests> diff --git a/dev/tests/verification/TestModule/Test/ActionGroupTest/ActionGroupWithPassedArgumentAndStringSelectorParam.xml b/dev/tests/verification/TestModule/Test/ActionGroupTest/ActionGroupWithPassedArgumentAndStringSelectorParam.xml new file mode 100644 index 000000000..ea8e98b8a --- /dev/null +++ b/dev/tests/verification/TestModule/Test/ActionGroupTest/ActionGroupWithPassedArgumentAndStringSelectorParam.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="ActionGroupWithPassedArgumentAndStringSelectorParam"> + <annotations> + <severity value="BLOCKER"/> + <title value="Action Group With Passed Argument Value and Hardcoded Value in Param"/> + </annotations> + + <actionGroup ref="actionGroupWithDefaultArgumentAndStringSelectorParam" stepKey="actionGroup"> + <argument name="someArgument" value="UniquePerson"/> + </actionGroup> + </test> +</tests> diff --git a/dev/tests/verification/TestModule/Test/ActionGroupTest/ActionGroupWithSectionAndDataAsArguments.xml b/dev/tests/verification/TestModule/Test/ActionGroupTest/ActionGroupWithSectionAndDataAsArguments.xml new file mode 100644 index 000000000..dacb2ed18 --- /dev/null +++ b/dev/tests/verification/TestModule/Test/ActionGroupTest/ActionGroupWithSectionAndDataAsArguments.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="ActionGroupWithSectionAndDataAsArguments"> + <actionGroup ref="actionGroupWithSectionAndData" stepKey="actionGroup"> + <argument name="content" value="{{simpleData.firstname}}"/> + <argument name="section" value="SampleSection"/> + </actionGroup> + </test> +</tests> diff --git a/dev/tests/verification/TestModule/Test/ActionGroupTest/ActionGroupWithSimpleDataUsageFromDefaultArgument.xml b/dev/tests/verification/TestModule/Test/ActionGroupTest/ActionGroupWithSimpleDataUsageFromDefaultArgument.xml new file mode 100644 index 000000000..a33603c07 --- /dev/null +++ b/dev/tests/verification/TestModule/Test/ActionGroupTest/ActionGroupWithSimpleDataUsageFromDefaultArgument.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="ActionGroupWithSimpleDataUsageFromDefaultArgument"> + <annotations> + <severity value="CRITICAL"/> + <title value="Action Group With Simple Data Usage From Default Argument"/> + </annotations> + <actionGroup ref="actionGroupWithStringUsage" stepKey="actionGroup"/> + </test> +</tests> diff --git a/dev/tests/verification/TestModule/Test/ActionGroupTest/ActionGroupWithSimpleDataUsageFromPassedArgument.xml b/dev/tests/verification/TestModule/Test/ActionGroupTest/ActionGroupWithSimpleDataUsageFromPassedArgument.xml new file mode 100644 index 000000000..61aa66f9b --- /dev/null +++ b/dev/tests/verification/TestModule/Test/ActionGroupTest/ActionGroupWithSimpleDataUsageFromPassedArgument.xml @@ -0,0 +1,48 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="ActionGroupWithSimpleDataUsageFromPassedArgument"> + <annotations> + <severity value="CRITICAL"/> + <title value="Action Group With Simple Data Usage From Passed Argument"/> + </annotations> + + <actionGroup ref="actionGroupWithStringUsage" stepKey="actionGroup1"> + <argument name="someArgument" value="overrideString"/> + </actionGroup> + <actionGroup ref="actionGroupWithStringUsage" stepKey="actionGroup11"> + <argument name="someArgument" value="1"/> + </actionGroup> + <actionGroup ref="actionGroupWithStringUsage" stepKey="actionGroup12"> + <argument name="someArgument" value="1.5"/> + </actionGroup> + <actionGroup ref="actionGroupWithStringUsage" stepKey="actionGroup13"> + <argument name="someArgument" value="true"/> + </actionGroup> + <actionGroup ref="actionGroupWithStringUsage" stepKey="actionGroup2"> + <argument name="someArgument" value="simpleData.firstname"/> + </actionGroup> + <actionGroup ref="actionGroupWithStringUsage" stepKey="actionGroup3"> + <argument name="someArgument" value="$persisted.data$"/> + </actionGroup> + + <actionGroup ref="actionGroupWithEntityUsage" stepKey="actionGroup4"> + <argument name="someArgument" value="simpleData.firstname"/> + </actionGroup> + <actionGroup ref="actionGroupWithEntityUsage" stepKey="actionGroup5"> + <argument name="someArgument" value="$simpleData.firstname$"/> + </actionGroup> + <actionGroup ref="actionGroupWithEntityUsage" stepKey="actionGroup6"> + <argument name="someArgument" value="$simpleData.firstname[0]$"/> + </actionGroup> + <actionGroup ref="actionGroupWithEntityUsage" stepKey="actionGroup7"> + <argument name="someArgument" value="$simpleData.firstname[data_index]$"/> + </actionGroup> + </test> +</tests> diff --git a/dev/tests/verification/TestModule/Test/ActionGroupTest/ActionGroupWithSingleParameterSelectorFromDefaultArgument.xml b/dev/tests/verification/TestModule/Test/ActionGroupTest/ActionGroupWithSingleParameterSelectorFromDefaultArgument.xml new file mode 100644 index 000000000..f76e45acf --- /dev/null +++ b/dev/tests/verification/TestModule/Test/ActionGroupTest/ActionGroupWithSingleParameterSelectorFromDefaultArgument.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="ActionGroupWithSingleParameterSelectorFromDefaultArgument"> + <annotations> + <severity value="BLOCKER"/> + <title value="Action Group With Default Argument Value and Argument Value in Param"/> + </annotations> + + <actionGroup ref="actionGroupWithSingleParameterSelectorFromArgument" stepKey="actionGroup"/> + </test> +</tests> diff --git a/dev/tests/verification/TestModule/Test/ActionGroupTest/ActionGroupWithSingleParameterSelectorFromPassedArgument.xml b/dev/tests/verification/TestModule/Test/ActionGroupTest/ActionGroupWithSingleParameterSelectorFromPassedArgument.xml new file mode 100644 index 000000000..d9bfbe656 --- /dev/null +++ b/dev/tests/verification/TestModule/Test/ActionGroupTest/ActionGroupWithSingleParameterSelectorFromPassedArgument.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="ActionGroupWithSingleParameterSelectorFromPassedArgument"> + <annotations> + <severity value="BLOCKER"/> + <title value="Action Group With Passed Argument Value and Argument Value in Param"/> + </annotations> + + <actionGroup ref="actionGroupWithSingleParameterSelectorFromArgument" stepKey="actionGroup"> + <argument name="someArgument" value="UniquePerson"/> + </actionGroup> + </test> +</tests> diff --git a/dev/tests/verification/TestModule/Test/ActionGroupTest/ActionGroupWithStepKeyReferences.xml b/dev/tests/verification/TestModule/Test/ActionGroupTest/ActionGroupWithStepKeyReferences.xml new file mode 100644 index 000000000..d68190f83 --- /dev/null +++ b/dev/tests/verification/TestModule/Test/ActionGroupTest/ActionGroupWithStepKeyReferences.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="ActionGroupWithStepKeyReferences"> + <actionGroup ref="FunctionActionGroupWithStepKeyReferences" stepKey="actionGroup"/> + </test> +</tests> diff --git a/dev/tests/verification/TestModule/Test/ActionGroupTest/ExtendedActionGroup.xml b/dev/tests/verification/TestModule/Test/ActionGroupTest/ExtendedActionGroup.xml new file mode 100644 index 000000000..c7bb24c4f --- /dev/null +++ b/dev/tests/verification/TestModule/Test/ActionGroupTest/ExtendedActionGroup.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="ExtendedActionGroup"> + <actionGroup ref="extendTestActionGroup" stepKey="actionGroup"> + <argument name="count" value="99"/> + <argument name="otherCount" value="8000"/> + </actionGroup> + </test> +</tests> diff --git a/dev/tests/verification/TestModule/Test/ActionGroupTest/ExtendedActionGroupReturningValueTest.xml b/dev/tests/verification/TestModule/Test/ActionGroupTest/ExtendedActionGroupReturningValueTest.xml new file mode 100644 index 000000000..68042344c --- /dev/null +++ b/dev/tests/verification/TestModule/Test/ActionGroupTest/ExtendedActionGroupReturningValueTest.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="ExtendedActionGroupReturningValueTest"> + <annotations> + <severity value="CRITICAL"/> + <title value="Extended ActionGroup Returning Value Test"/> + </annotations> + <actionGroup ref="ActionGroupReturningValueActionGroup" stepKey="actionGroupReturningValue"> + <argument name="count" value="99"/> + </actionGroup> + <actionGroup ref="actionGroupWithStringUsage" stepKey="actionGroupWithStringUsage1"> + <argument name="someArgument" value="{$actionGroupReturningValue}"/> + </actionGroup> + </test> +</tests> diff --git a/dev/tests/verification/TestModule/Test/ActionGroupTest/ExtendedChildActionGroupReturningValueTest.xml b/dev/tests/verification/TestModule/Test/ActionGroupTest/ExtendedChildActionGroupReturningValueTest.xml new file mode 100644 index 000000000..ecd46c3c4 --- /dev/null +++ b/dev/tests/verification/TestModule/Test/ActionGroupTest/ExtendedChildActionGroupReturningValueTest.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="ExtendedChildActionGroupReturningValueTest"> + <annotations> + <severity value="CRITICAL"/> + <title value="Extended Child ActionGroup Returning Value Test"/> + </annotations> + <actionGroup ref="ExtendedActionGroupReturningValueActionGroup" stepKey="extendedActionGroupReturningValue"> + <argument name="count" value="99"/> + <argument name="otherCount" value="8000"/> + </actionGroup> + <actionGroup ref="actionGroupWithStringUsage" stepKey="actionGroupWithStringUsage1"> + <argument name="someArgument" value="{$extendedActionGroupReturningValue}"/> + </actionGroup> + </test> +</tests> diff --git a/dev/tests/verification/TestModule/Test/ActionGroupTest/ExtendedRemoveActionGroup.xml b/dev/tests/verification/TestModule/Test/ActionGroupTest/ExtendedRemoveActionGroup.xml new file mode 100644 index 000000000..bcbed8f23 --- /dev/null +++ b/dev/tests/verification/TestModule/Test/ActionGroupTest/ExtendedRemoveActionGroup.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="ExtendedRemoveActionGroup"> + <actionGroup ref="extendRemoveTestActionGroup" stepKey="actionGroup"/> + </test> +</tests> diff --git a/dev/tests/verification/TestModule/Test/AssertTest.xml b/dev/tests/verification/TestModule/Test/AssertTest.xml index 82f69b9b0..03d9ca0c7 100644 --- a/dev/tests/verification/TestModule/Test/AssertTest.xml +++ b/dev/tests/verification/TestModule/Test/AssertTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AssertTest"> <before> <createData entity="ReplacementPerson" stepKey="createData1"/> @@ -31,14 +31,18 @@ <expectedResult type="string">kiwi</expectedResult> <actualResult type="const">['orange' => 2, 'apple' => 1]</actualResult> </assertArrayNotHasKey> - <assertArraySubset stepKey="assertArraySubset" message="pass"> - <expectedResult type="const">[1, 2]</expectedResult> - <actualResult type="const">[1, 2, 3, 5]</actualResult> - </assertArraySubset> <assertContains stepKey="assertContains" message="pass"> <expectedResult type="string">ab</expectedResult> <actualResult type="const">['item1' => 'a', 'item2' => 'ab']</actualResult> </assertContains> + <assertStringContainsString stepKey="assertStringContainsString" message="pass"> + <expectedResult type="string">apple</expectedResult> + <actualResult type="string">apple</actualResult> + </assertStringContainsString> + <assertStringContainsStringIgnoringCase stepKey="assertStringContainsStringIgnoringCase" message="pass"> + <expectedResult type="string">Banana</expectedResult> + <actualResult type="string">banana</actualResult> + </assertStringContainsStringIgnoringCase> <assertCount stepKey="assertCount" message="pass"> <expectedResult type="int">2</expectedResult> <actualResult type="const">['a', 'b']</actualResult> @@ -54,6 +58,10 @@ <expectedResult type="string">Copyright © 2013-2017 Magento, Inc. All rights reserved.</expectedResult> <actualResult type="variable">text</actualResult> </assertEquals> + <assertEquals stepKey="assertFloatTypeIsCorrect" message="pass"> + <expectedResult type="float">1.5</expectedResult> + <actualResult type="variable">text</actualResult> + </assertEquals> <assertFalse stepKey="assertFalse1" message="pass"> <actualResult type="bool">0</actualResult> </assertFalse> @@ -75,18 +83,6 @@ <expectedResult type="int">2</expectedResult> <actualResult type="int">5</actualResult> </assertGreaterThanOrEqual> - <assertInternalType stepKey="assertInternalType1" message="pass"> - <expectedResult type="string">string</expectedResult> - <actualResult type="string">xyz</actualResult> - </assertInternalType> - <assertInternalType stepKey="assertInternalType2" message="pass"> - <expectedResult type="string">int</expectedResult> - <actualResult type="int">21</actualResult> - </assertInternalType> - <assertInternalType stepKey="assertInternalType3" message="pass"> - <expectedResult type="string">string</expectedResult> - <actualResult type="variable">text</actualResult> - </assertInternalType> <assertLessOrEquals stepKey="assertLessOrEquals" message="pass"> <expectedResult type="int">5</expectedResult> <actualResult type="int">2</actualResult> @@ -99,21 +95,25 @@ <expectedResult type="int">5</expectedResult> <actualResult type="int">2</actualResult> </assertLessThanOrEqual> - <assertNotContains stepKey="assertNotContains1" message="pass"> + <assertNotContains stepKey="assertNotContains" message="pass"> <expectedResult type="string">bc</expectedResult> <actualResult type="const">['item1' => 'a', 'item2' => 'ab']</actualResult> </assertNotContains> - <assertNotContains stepKey="assertNotContains2" message="pass"> - <expectedResult type="string">bc</expectedResult> - <actualResult type="variable">text</actualResult> - </assertNotContains> + <assertStringNotContainsString stepKey="assertStringNotContainsString" message="pass"> + <expectedResult type="string">apple</expectedResult> + <actualResult type="string">banana</actualResult> + </assertStringNotContainsString> + <assertStringNotContainsStringIgnoringCase stepKey="assertStringNotContainsStringIgnoringCase" message="pass"> + <expectedResult type="string">apple</expectedResult> + <actualResult type="string">banana</actualResult> + </assertStringNotContainsStringIgnoringCase> <assertNotEmpty stepKey="assertNotEmpty1" message="pass"> <actualResult type="const">[1, 2]</actualResult> </assertNotEmpty> <assertNotEmpty stepKey="assertNotEmpty2" message="pass"> <actualResult type="variable">text</actualResult> </assertNotEmpty> - <assertNotEquals stepKey="assertNotEquals" message="pass" delta=""> + <assertNotEquals stepKey="assertNotEquals" message="pass"> <expectedResult type="int">2</expectedResult> <actualResult type="int">5</actualResult> </assertNotEquals> @@ -153,47 +153,141 @@ <!-- asserts backward compatible --> <comment stepKey="commentBackwardCompatible" userInput="asserts backward compatible"/> - <assertArrayHasKey stepKey="assertArrayHasKeyBackwardCompatible" expected="apple" expectedType="string" actual="['orange' => 2, 'apple' => 1]" actualType="const" message="pass"/> - <assertArrayNotHasKey stepKey="assertArrayNotHasKeyBackwardCompatible" expected="kiwi" expectedType="string" actual="['orange' => 2, 'apple' => 1]" message="pass"/> - <assertArraySubset stepKey="assertArraySubsetBackwardCompatible" expected="[1, 2]" actual="[1, 2, 3, 5]" message="pass"/> - <assertContains stepKey="assertContainsBackwardCompatible" expected="ab" expectedType="string" actual="['item1' => 'a', 'item2' => 'ab']" message="pass"/> - <assertCount stepKey="assertCountBackwardCompatible" expected="2" expectedType="int" actual="['a', 'b']" message="pass"/> - <assertEmpty stepKey="assertEmptyBackwardCompatible" actual="[]" message="pass"/> - <assertEquals stepKey="assertEquals1BackwardCompatible" expected="text" expectedType="variable" actual="Copyright © 2013-2017 Magento, Inc. All rights reserved." actualType="string" message="pass"/> - <assertEquals stepKey="assertEquals2BackwardCompatible" expected="Copyright © 2013-2017 Magento, Inc. All rights reserved." expectedType="string" actual="text" actualType="variable" message="pass"/> - <assertFalse stepKey="assertFalse1BackwardCompatible" actual="0" actualType="bool" message="pass"/> - <assertFileNotExists stepKey="assertFileNotExists1BackwardCompatible" actual="/out.txt" actualType="string" message="pass"/> - <assertFileNotExists stepKey="assertFileNotExists2BackwardCompatible" actual="$text" actualType="variable" message="pass"/> - <assertGreaterOrEquals stepKey="assertGreaterOrEqualsBackwardCompatible" expected="2" expectedType="int" actual="5" actualType="int" message="pass"/> - <assertGreaterThan stepKey="assertGreaterThanBackwardCompatible" expected="2" expectedType="int" actual="5" actualType="int" message="pass"/> - <assertGreaterThanOrEqual stepKey="assertGreaterThanOrEqualBackwardCompatible" expected="2" expectedType="int" actual="5" actualType="int" message="pass"/> - <assertInternalType stepKey="assertInternalType1BackwardCompatible" expected="string" expectedType="string" actual="xyz" actualType="string" message="pass"/> - <assertInternalType stepKey="assertInternalType2BackwardCompatible" expected="int" expectedType="string" actual="21" actualType="int" message="pass"/> - <assertInternalType stepKey="assertInternalType3BackwardCompatible" expected="string" expectedType="string" actual="$text" actualType="variable" message="pass"/> - <assertLessOrEquals stepKey="assertLessOrEqualBackwardCompatibles" expected="5" expectedType="int" actual="2" actualType="int" message="pass"/> - <assertLessThan stepKey="assertLessThanBackwardCompatible" expected="5" expectedType="int" actual="2" actualType="int" message="pass"/> - <assertLessThanOrEqual stepKey="assertLessThanOrEqualBackwardCompatible" expected="5" expectedType="int" actual="2" actualType="int" message="pass"/> - <assertNotContains stepKey="assertNotContains1BackwardCompatible" expected="bc" expectedType="string" actual="['item1' => 'a', 'item2' => 'ab']" message="pass"/> - <assertNotContains stepKey="assertNotContains2BackwardCompatible" expected="bc" expectedType="string" actual="text" actualType="variable" message="pass"/> - <assertNotEmpty stepKey="assertNotEmpty1BackwardCompatible" actual="[1, 2]" message="pass"/> - <assertNotEmpty stepKey="assertNotEmpty2BackwardCompatible" actual="text" actualType="variable" message="pass"/> - <assertNotEquals stepKey="assertNotEqualsBackwardCompatible" expected="2" expectedType="int" actual="5" actualType="int" message="pass" delta=""/> - <assertNotNull stepKey="assertNotNull1BackwardCompatible" actual="abc" actualType="string" message="pass"/> - <assertNotNull stepKey="assertNotNull2BackwardCompatible" actual="text" actualType="variable" message="pass"/> - <assertNotRegExp stepKey="assertNotRegExpBackwardCompatible" expected="/foo/" expectedType="string" actual="bar" actualType="string" message="pass"/> - <assertNotSame stepKey="assertNotSameBackwardCompatible" expected="log" expectedType="string" actual="tag" actualType="string" message="pass"/> - <assertRegExp stepKey="assertRegExpBackwardCompatible" expected="/foo/" expectedType="string" actual="foo" actualType="string" message="pass"/> - <assertSame stepKey="assertSameBackwardCompatible" expected="bar" expectedType="string" actual="bar" actualType="string" message="pass"/> - <assertStringStartsNotWith stepKey="assertStringStartsNotWithBackwardCompatible" expected="a" expectedType="string" actual="banana" actualType="string" message="pass"/> - <assertStringStartsWith stepKey="assertStringStartsWithBackwardCompatible" expected="a" expectedType="string" actual="apple" actualType="string" message="pass"/> - <assertTrue stepKey="assertTrueBackwardCompatible" actual="1" actualType="bool" message="pass"/> - <assertElementContainsAttribute selector="#username" attribute="class" expectedValue="admin__control-text" stepKey="assertElementContainsAttributeBackwardCompatible"/> - <assertInstanceOf stepKey="assertInstanceOfBackwardCompatible" expected="User::class" actual="text" actualType="variable" message="pass"/> - <assertNotInstanceOf stepKey="assertNotInstanceOfBackwardCompatible" expected="User::class" actual="21" actualType="int" message="pass"/> - <assertFileExists stepKey="assertFileExistsBackwardCompatible" actual="text" actualType="variable" message="pass"/> - <assertIsEmpty stepKey="assertIsEmptyBackwardCompatible" actual="text" actualType="variable" message="pass"/> - <assertNull stepKey="assertNullBackwardCompatible" actual="text" actualType="variable" message="pass"/> - <expectException stepKey="expectExceptionBackwardCompatible" expected="new MyException('exception msg')" actual="function() {$this->doSomethingBad();}"/> + <assertArrayHasKey stepKey="assertArrayHasKeyBackwardCompatible" message="pass"> + <expectedResult type="string">apple</expectedResult> + <actualResult type="const">['orange' => 2, 'apple' => 1]</actualResult> + </assertArrayHasKey> + <assertArrayNotHasKey stepKey="assertArrayNotHasKeyBackwardCompatible" message="pass"> + <expectedResult type="string">kiwi</expectedResult> + <actualResult type="const">['orange' => 2, 'apple' => 1]</actualResult> + </assertArrayNotHasKey> + <assertContains stepKey="assertContainsBackwardCompatible" message="pass"> + <expectedResult type="string">ab</expectedResult> + <actualResult type="const">['item1' => 'a', 'item2' => 'ab']</actualResult> + </assertContains> + <assertCount stepKey="assertCountBackwardCompatible" message="pass"> + <actualResult type="const">['a', 'b']</actualResult> + <expectedResult type="int">2</expectedResult> + </assertCount> + <assertEmpty stepKey="assertEmptyBackwardCompatible" message="pass"> + <actualResult type="const">[]</actualResult> + </assertEmpty> + <assertEquals stepKey="assertEquals1BackwardCompatible" message="pass"> + <actualResult type="string">Copyright © 2013-2017 Magento, Inc. All rights reserved.</actualResult> + <expectedResult type="variable">text</expectedResult> + </assertEquals> + <assertEquals stepKey="assertEquals2BackwardCompatible" message="pass"> + <actualResult type="variable">text</actualResult> + <expectedResult type="string">Copyright © 2013-2017 Magento, Inc. All rights reserved.</expectedResult> + </assertEquals> + <assertFalse stepKey="assertFalse1BackwardCompatible" message="pass"> + <actualResult type="bool">0</actualResult> + </assertFalse> + <assertFileNotExists stepKey="assertFileNotExists1BackwardCompatible" message="pass"> + <actualResult type="string">/out.txt</actualResult> + </assertFileNotExists> + <assertFileNotExists stepKey="assertFileNotExists2BackwardCompatible" message="pass"> + <actualResult type="variable">$text</actualResult> + </assertFileNotExists> + <assertGreaterOrEquals stepKey="assertGreaterOrEqualsBackwardCompatible" message="pass"> + <actualResult type="int">5</actualResult> + <expectedResult type="int">2</expectedResult> + </assertGreaterOrEquals> + <assertGreaterThan stepKey="assertGreaterThanBackwardCompatible" message="pass"> + <actualResult type="int">5</actualResult> + <expectedResult type="int">2</expectedResult> + </assertGreaterThan> + <assertGreaterThanOrEqual stepKey="assertGreaterThanOrEqualBackwardCompatible" message="pass"> + <actualResult type="int">5</actualResult> + <expectedResult type="int">2</expectedResult> + </assertGreaterThanOrEqual> + <assertLessOrEquals stepKey="assertLessOrEqualBackwardCompatibles" message="pass"> + <actualResult type="int">2</actualResult> + <expectedResult type="int">5</expectedResult> + </assertLessOrEquals> + <assertLessThan stepKey="assertLessThanBackwardCompatible" message="pass"> + <actualResult type="int">2</actualResult> + <expectedResult type="int">5</expectedResult> + </assertLessThan> + <assertLessThanOrEqual stepKey="assertLessThanOrEqualBackwardCompatible" message="pass"> + <actualResult type="int">2</actualResult> + <expectedResult type="int">5</expectedResult> + </assertLessThanOrEqual> + <assertNotContains stepKey="assertNotContains1BackwardCompatible" message="pass"> + <expectedResult type="string">bc</expectedResult> + <actualResult type="const">['item1' => 'a', 'item2' => 'ab']</actualResult> + </assertNotContains> + <assertNotContains stepKey="assertNotContains2BackwardCompatible" message="pass"> + <actualResult type="variable">text</actualResult> + <expectedResult type="string">bc</expectedResult> + </assertNotContains> + <assertNotEmpty stepKey="assertNotEmpty1BackwardCompatible" message="pass"> + <actualResult type="const">[1, 2]</actualResult> + </assertNotEmpty> + <assertNotEmpty stepKey="assertNotEmpty2BackwardCompatible" message="pass"> + <actualResult type="variable">text</actualResult> + </assertNotEmpty> + <assertNotEquals stepKey="assertNotEqualsBackwardCompatible" message="pass"> + <actualResult type="int">5</actualResult> + <expectedResult type="int">2</expectedResult> + </assertNotEquals> + <assertNotNull stepKey="assertNotNull1BackwardCompatible" message="pass"> + <actualResult type="string">abc</actualResult> + </assertNotNull> + <assertNotNull stepKey="assertNotNull2BackwardCompatible" message="pass"> + <actualResult type="variable">text</actualResult> + </assertNotNull> + <assertNotRegExp stepKey="assertNotRegExpBackwardCompatible" message="pass"> + <actualResult type="string">bar</actualResult> + <expectedResult type="string">/foo/</expectedResult> + </assertNotRegExp> + <assertNotSame stepKey="assertNotSameBackwardCompatible" message="pass"> + <actualResult type="string">tag</actualResult> + <expectedResult type="string">log</expectedResult> + </assertNotSame> + <assertRegExp stepKey="assertRegExpBackwardCompatible" message="pass"> + <actualResult type="string">foo</actualResult> + <expectedResult type="string">/foo/</expectedResult> + </assertRegExp> + <assertSame stepKey="assertSameBackwardCompatible" message="pass"> + <actualResult type="string">bar</actualResult> + <expectedResult type="string">bar</expectedResult> + </assertSame> + <assertStringStartsNotWith stepKey="assertStringStartsNotWithBackwardCompatible" message="pass"> + <actualResult type="string">banana</actualResult> + <expectedResult type="string">a</expectedResult> + </assertStringStartsNotWith> + <assertStringStartsWith stepKey="assertStringStartsWithBackwardCompatible" message="pass"> + <actualResult type="string">apple</actualResult> + <expectedResult type="string">a</expectedResult> + </assertStringStartsWith> + <assertTrue stepKey="assertTrueBackwardCompatible" message="pass"> + <actualResult type="bool">1</actualResult> + </assertTrue> + <assertElementContainsAttribute stepKey="assertElementContainsAttributeBackwardCompatible"> + <expectedResult selector="#username" attribute="class" type="string">admin__control-text</expectedResult> + </assertElementContainsAttribute> + <assertInstanceOf stepKey="assertInstanceOfBackwardCompatible" message="pass"> + <actualResult type="variable">text</actualResult> + <expectedResult type="const">User::class</expectedResult> + </assertInstanceOf> + <assertNotInstanceOf stepKey="assertNotInstanceOfBackwardCompatible" message="pass"> + <actualResult type="int">21</actualResult> + <expectedResult type="const">User::class</expectedResult> + </assertNotInstanceOf> + <assertFileExists stepKey="assertFileExistsBackwardCompatible" message="pass"> + <actualResult type="variable">text</actualResult> + </assertFileExists> + <assertIsEmpty stepKey="assertIsEmptyBackwardCompatible" message="pass"> + <actualResult type="variable">text</actualResult> + </assertIsEmpty> + <assertNull stepKey="assertNullBackwardCompatible" message="pass"> + <actualResult type="variable">text</actualResult> + </assertNull> + <expectException stepKey="expectExceptionBackwardCompatible"> + <expectedResult type="const">new MyException('exception msg')</expectedResult> + <actualResult type="const">function() {$this->doSomethingBad();}</actualResult> + </expectException> <!-- string type that use created data --> <comment stepKey="c2" userInput="string type that use created data"/> @@ -212,14 +306,6 @@ <!-- array type that use created data --> <comment stepKey="c3" userInput="array type that use created data"/> - <assertArraySubset stepKey="assert9" message="pass"> - <expectedResult type="array">[$$createData1.lastname$$, $$createData1.firstname$$]</expectedResult> - <actualResult type="array">[$$createData1.lastname$$, $$createData1.firstname$$, 1]</actualResult> - </assertArraySubset> - <assertArraySubset stepKey="assert10" message="pass"> - <expectedResult type="array">[$createData2.firstname$, $createData2.lastname$]</expectedResult> - <actualResult type="array">[$createData2.firstname$, $createData2.lastname$, 1]</actualResult> - </assertArraySubset> <assertArrayHasKey stepKey="assert3" message="pass"> <expectedResult type="string">lastname</expectedResult> <actualResult type="array">['lastname' => $$createData1.lastname$$, 'firstname' => $$createData1.firstname$$]</actualResult> @@ -259,29 +345,29 @@ <fail stepKey="assert8" message="$$createData1.firstname$$ $$createData1.lastname$$"/> <!-- assertElementContainsAttribute examples --> - <assertElementContainsAttribute stepKey="assertElementContainsAttribute1" selector="#username" attribute="class"> - <expectedResult type="string">admin__control-text</expectedResult> + <assertElementContainsAttribute stepKey="assertElementContainsAttribute1"> + <expectedResult selector="#username" attribute="class" type="string">admin__control-text</expectedResult> </assertElementContainsAttribute> - <assertElementContainsAttribute stepKey="assertElementContainsAttribute2" selector="#username" attribute="name"> - <expectedResult type="string">login[username]</expectedResult> + <assertElementContainsAttribute stepKey="assertElementContainsAttribute2"> + <expectedResult selector="#username" attribute="name" type="string">login[username]</expectedResult> </assertElementContainsAttribute> - <assertElementContainsAttribute stepKey="assertElementContainsAttribute3" selector="#username" attribute="autofocus"> - <expectedResult type="string">true</expectedResult> + <assertElementContainsAttribute stepKey="assertElementContainsAttribute3"> + <expectedResult selector="#username" attribute="autofocus" type="string">true</expectedResult> </assertElementContainsAttribute> - <assertElementContainsAttribute stepKey="assertElementContainsAttribute4" selector="#username" attribute="data-validate"> - <expectedResult type="string">{required:true}</expectedResult> + <assertElementContainsAttribute stepKey="assertElementContainsAttribute4"> + <expectedResult selector="#username" attribute="data-validate" type="string">{required:true}</expectedResult> </assertElementContainsAttribute> - <assertElementContainsAttribute stepKey="assertElementContainsAttribute5" selector=".admin__menu-overlay" attribute="style"> - <expectedResult type="string">display: none;</expectedResult> + <assertElementContainsAttribute stepKey="assertElementContainsAttribute5"> + <expectedResult selector=".admin__menu-overlay" attribute="style" type="string">display: none;</expectedResult> </assertElementContainsAttribute> - <assertElementContainsAttribute stepKey="assertElementContainsAttribute6" selector=".admin__menu-overlay" attribute="border"> - <expectedResult type="string">0</expectedResult> + <assertElementContainsAttribute stepKey="assertElementContainsAttribute6"> + <expectedResult selector=".admin__menu-overlay" attribute="border" type="string">0</expectedResult> </assertElementContainsAttribute> - <assertElementContainsAttribute stepKey="assertElementContainsAttribute7" selector="#username" attribute="value"> - <expectedResult type="const">$createData2.firstname$</expectedResult> + <assertElementContainsAttribute stepKey="assertElementContainsAttribute7"> + <expectedResult selector="#username" attribute="value" type="const">$createData2.firstname$</expectedResult> </assertElementContainsAttribute> - <assertElementContainsAttribute stepKey="assertElementContainsAttribute8" selector="#username" attribute="value"> - <expectedResult type="const">$$createData1.firstname$$</expectedResult> + <assertElementContainsAttribute stepKey="assertElementContainsAttribute8"> + <expectedResult selector="#username" attribute="value" type="const">$$createData1.firstname$$</expectedResult> </assertElementContainsAttribute> <!-- assert entity resolution --> @@ -289,5 +375,58 @@ <expectedResult type="string">{{simpleData.firstname}}</expectedResult> <actualResult type="string">{{simpleData.lastname}}</actualResult> </assertEquals> + + <assertEqualsWithDelta stepKey="a1" message="pass" delta="1"> + <expectedResult type="const">10.0000</expectedResult> + <actualResult type="const">10.0000</actualResult> + </assertEqualsWithDelta> + <assertNotEqualsWithDelta stepKey="a2" message="pass" delta="1"> + <expectedResult type="const">10.0000</expectedResult> + <actualResult type="const">12.0000</actualResult> + </assertNotEqualsWithDelta> + <assertEqualsCanonicalizing stepKey="a3" message="pass"> + <expectedResult type="array">[4, 2, 1, 3]</expectedResult> + <actualResult type="array">[1, 2, 3, 4]</actualResult> + </assertEqualsCanonicalizing> + <assertNotEqualsCanonicalizing stepKey="a4" message="pass"> + <expectedResult type="array">[5, 8, 7, 9]</expectedResult> + <actualResult type="array">[1, 2, 3, 4]</actualResult> + </assertNotEqualsCanonicalizing> + <assertEqualsIgnoringCase stepKey="a5" message="pass"> + <expectedResult type="string">Cat</expectedResult> + <actualResult type="string">cat</actualResult> + </assertEqualsIgnoringCase> + <assertNotEqualsIgnoringCase stepKey="a6" message="pass"> + <expectedResult type="string">Cat</expectedResult> + <actualResult type="string">Dog</actualResult> + </assertNotEqualsIgnoringCase> + + <!-- assertions.md examples --> + <assertElementContainsAttribute stepKey="assertElementContainsAttribute"> + <expectedResult selector=".admin__menu-overlay" attribute="style" type="string">color: #333;</expectedResult> + </assertElementContainsAttribute> + <assertStringContainsString stepKey="assertDropDownTierPriceTextProduct1"> + <expectedResult type="string">Buy 5 for $5.00 each and save 50%</expectedResult> + <actualResult type="variable">DropDownTierPriceTextProduct1</actualResult> + </assertStringContainsString> + <assertEmpty stepKey="assertSearchButtonEnabled"> + <actualResult type="string">$grabSearchButtonAttribute</actualResult> + </assertEmpty> + <assertGreaterThanOrEqual stepKey="checkStatusSortOrderAsc"> + <actualResult type="const">$getOrderStatusSecondRow</actualResult> + <expectedResult type="const">$getOrderStatusFirstRow</expectedResult> + </assertGreaterThanOrEqual> + <assertNotEquals stepKey="assertNotEqualsExample"> + <actualResult type="string">{$grabTotalAfter}</actualResult> + <expectedResult type="string">{$grabTotalBefore}</expectedResult> + </assertNotEquals> + <assertNotRegExp stepKey="simpleThumbnailIsNotDefault"> + <actualResult type="const">$getSimpleProductThumbnail</actualResult> + <expectedResult type="const">'/placeholder\/thumbnail\.jpg/'</expectedResult> + </assertNotRegExp> + <assertRegExp message="adminAnalyticsMetadata object is invalid" stepKey="validateadminAnalyticsMetadata"> + <expectedResult type="string">#var\s+adminAnalyticsMetadata\s+=\s+{\s+("[\w_]+":\s+"[^"]*?",\s+)*?("[\w_]+":\s+"[^"]*?"\s+)};#s</expectedResult> + <actualResult type="variable">$pageSource</actualResult> + </assertRegExp> </test> </tests> diff --git a/dev/tests/verification/TestModule/Test/BasicFunctionalTest.xml b/dev/tests/verification/TestModule/Test/BasicFunctionalTest/BasicFunctionalTest.xml similarity index 92% rename from dev/tests/verification/TestModule/Test/BasicFunctionalTest.xml rename to dev/tests/verification/TestModule/Test/BasicFunctionalTest/BasicFunctionalTest.xml index ff0708554..fa2b5e247 100644 --- a/dev/tests/verification/TestModule/Test/BasicFunctionalTest.xml +++ b/dev/tests/verification/TestModule/Test/BasicFunctionalTest/BasicFunctionalTest.xml @@ -5,9 +5,8 @@ * See COPYING.txt for license details. */ --> - <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="BasicFunctionalTest"> <annotations> <severity value="CRITICAL"/> @@ -69,21 +68,29 @@ <fillField selector=".functionalTestSelector" userInput="0" stepKey="fillFieldKey2" /> <generateDate date="Now" format="H:i:s" stepKey="generateDateKey"/> <generateDate date="Now" format="H:i:s" stepKey="generateDateKey2" timezone="UTC"/> + <getOTP stepKey="getOtp"/> + <getOTP stepKey="getOtpWithInput" userInput="someInput"/> <grabAttributeFrom selector=".functionalTestSelector" userInput="someInput" stepKey="grabAttributeFromKey1" /> <grabCookie userInput="grabCookieInput" parameterArray="['domain' => 'www.google.com']" stepKey="grabCookieKey1" /> + <grabCookieAttributes userInput="grabCookieAttributesInput" parameterArray="['domain' => 'www.google.com']" stepKey="grabCookieAttributesKey1" /> <grabFromCurrentUrl regex="/grabCurrentUrl" stepKey="grabFromCurrentUrlKey1" /> <grabMultiple selector=".functionalTestSelector" stepKey="grabMultipleKey1" /> <grabTextFrom selector=".functionalTestSelector" stepKey="grabTextFromKey1" /> <grabValueFrom selector=".functionalTestSelector" stepKey="grabValueFromKey1" /> <magentoCLI command="maintenance:enable" arguments=""stuffHere"" stepKey="magentoCli1"/> + <magentoCLI command="maintenance:enable" arguments=""stuffHere"" timeout="120" stepKey="magentoCli2"/> + <magentoCLI command="config:set somePath {{_CREDS.someKey}}" stepKey="magentoCli3"/> + <magentoCLI command="config:set somePath {{_CREDS.someKey}}" timeout="120" stepKey="magentoCli4"/> + <magentoCron stepKey="cronAllGroups"/> + <magentoCron groups="index" stepKey="cronSingleGroup"/> + <magentoCron groups="a b c" stepKey="cronMultipleGroups"/> <makeScreenshot userInput="screenShotInput" stepKey="makeScreenshotKey1"/> <maximizeWindow stepKey="maximizeWindowKey1"/> <moveBack stepKey="moveBackKey1"/> <moveForward stepKey="moveForwardKey1"/> <moveMouseOver selector=".functionalTestSelector" stepKey="moveMouseOverKey1"/> <openNewTab stepKey="openNewTabKey1"/> - <pauseExecution stepKey="pauseExecutionKey1"/> - <performOn selector="#selector" function="function(\WebDriverElement $el) {return $el->isDisplayed();}" stepKey="performOnKey1"/> + <pause stepKey="pauseKey1"/> <pressKey selector="#page" userInput="a" stepKey="pressKey1"/> <pressKey selector="#page" parameterArray="[['ctrl','a'],'new']" stepKey="pressKey2"/> <pressKey selector="#page" parameterArray="[['shift','111'],'1','x']" stepKey="pressKey3"/> @@ -130,14 +137,4 @@ <waitForJS function="someJsFunction" time="30" stepKey="waitForJSKey1" /> <waitForText selector=".functionalTestSelector" userInput="someInput" time="30" stepKey="waitForText1"/> </test> - <test name="MergeMassViaInsertBefore"> - <fillField selector="#foo" userInput="foo" stepKey="fillField1"/> - <fillField selector="#bar" userInput="bar" stepKey="fillField2"/> - <fillField selector="#baz" userInput="baz" stepKey="fillField3"/> - </test> - <test name="MergeMassViaInsertAfter"> - <fillField selector="#foo" userInput="foo" stepKey="fillField1"/> - <fillField selector="#bar" userInput="bar" stepKey="fillField2"/> - <fillField selector="#baz" userInput="baz" stepKey="fillField3"/> - </test> -</tests> \ No newline at end of file +</tests> diff --git a/dev/tests/verification/TestModule/Test/BasicFunctionalTest/MergeMassViaInsertAfter.xml b/dev/tests/verification/TestModule/Test/BasicFunctionalTest/MergeMassViaInsertAfter.xml new file mode 100644 index 000000000..6efe33e49 --- /dev/null +++ b/dev/tests/verification/TestModule/Test/BasicFunctionalTest/MergeMassViaInsertAfter.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="MergeMassViaInsertAfter"> + <fillField selector="#foo" userInput="foo" stepKey="fillField1"/> + <fillField selector="#bar" userInput="bar" stepKey="fillField2"/> + <fillField selector="#baz" userInput="baz" stepKey="fillField3"/> + </test> +</tests> diff --git a/dev/tests/verification/TestModule/Test/BasicFunctionalTest/MergeMassViaInsertBefore.xml b/dev/tests/verification/TestModule/Test/BasicFunctionalTest/MergeMassViaInsertBefore.xml new file mode 100644 index 000000000..897fb51cc --- /dev/null +++ b/dev/tests/verification/TestModule/Test/BasicFunctionalTest/MergeMassViaInsertBefore.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="MergeMassViaInsertBefore"> + <fillField selector="#foo" userInput="foo" stepKey="fillField1"/> + <fillField selector="#bar" userInput="bar" stepKey="fillField2"/> + <fillField selector="#baz" userInput="baz" stepKey="fillField3"/> + </test> +</tests> diff --git a/dev/tests/verification/TestModule/Test/CharacterReplacementTest.xml b/dev/tests/verification/TestModule/Test/CharacterReplacementTest.xml index 9a8c0f17d..85b3fb34e 100644 --- a/dev/tests/verification/TestModule/Test/CharacterReplacementTest.xml +++ b/dev/tests/verification/TestModule/Test/CharacterReplacementTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="CharacterReplacementTest"> <click stepKey="charsInSectionElement" selector="{{SampleSection.underscore_element}}"/> <fillField stepKey="charsInDataRef" selector="{{SampleSection.underscore_element}}" userInput="{{simpleData.street[0]}}"/> diff --git a/dev/tests/verification/TestModule/Test/DataActionsTest.xml b/dev/tests/verification/TestModule/Test/DataActionsTest.xml index 03d7caa35..6f36803ae 100644 --- a/dev/tests/verification/TestModule/Test/DataActionsTest.xml +++ b/dev/tests/verification/TestModule/Test/DataActionsTest.xml @@ -7,19 +7,17 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="DataActionsTest"> <before> - <createData entity="entity" stepKey="createdInBefore"/> <updateData entity="entity" createDataKey="createdInBefore" stepKey="updateInBefore"/> <deleteData createDataKey="createdInBefore" stepKey="deleteInBefore"/> </before> - - <createData entity="entity" stepKey="createdInTest"/> + <waitForElementClickable selector=".functionalTestSelector" time="30" stepKey="waitForElementClickable" /> <updateData entity="entity" createDataKey="createdInTest" stepKey="updateInTest"/> <deleteData createDataKey="createdInTest" stepKey="deleteInTest"/> - <updateData entity="entity" createDataKey="createdInBefore" stepKey="updatedDataOutOfScope"/> <deleteData createDataKey="createdInBefore" stepKey="deleteDataOutOfScope"/> + <rapidClick selector="#functionalTestSelector" count="50" stepKey="rapidClickTest"/> </test> </tests> diff --git a/dev/tests/verification/TestModule/Test/DataReplacementTest.xml b/dev/tests/verification/TestModule/Test/DataReplacementTest.xml index ae11b6fdc..f33a95b50 100644 --- a/dev/tests/verification/TestModule/Test/DataReplacementTest.xml +++ b/dev/tests/verification/TestModule/Test/DataReplacementTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="DataReplacementTest"> <fillField stepKey="inputReplace" selector="#selector" userInput="StringBefore {{simpleData.firstname}} StringAfter"/> <seeCurrentUrlMatches stepKey="seeInRegex" regex="~\/{{simpleData.firstname}}~i"/> diff --git a/dev/tests/verification/TestModule/Test/DeprecatedEntitiesTest.xml b/dev/tests/verification/TestModule/Test/DeprecatedEntitiesTest.xml new file mode 100644 index 000000000..20c7ccd0b --- /dev/null +++ b/dev/tests/verification/TestModule/Test/DeprecatedEntitiesTest.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="DeprecatedEntitiesTest"> + <actionGroup ref="DeprecatedActionGroup" stepKey="deprecatedActionGroup" /> + <amOnPage url="{{DeprecatedPage.url}}" stepKey="amOnPage" /> + </test> +</tests> diff --git a/dev/tests/verification/TestModule/Test/DeprecatedTest.xml b/dev/tests/verification/TestModule/Test/DeprecatedTest.xml new file mode 100644 index 000000000..075e8add3 --- /dev/null +++ b/dev/tests/verification/TestModule/Test/DeprecatedTest.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="DeprecatedTest" deprecated="Test is deprecated"> + <actionGroup ref="DeprecatedActionGroup" stepKey="deprecatedActionGroup" /> + <amOnPage url="{{DeprecatedPage.url}}" stepKey="amOnPage" /> + </test> +</tests> diff --git a/dev/tests/verification/TestModule/Test/ExecuteInSeleniumTest.xml b/dev/tests/verification/TestModule/Test/ExecuteInSeleniumTest.xml deleted file mode 100644 index c25164220..000000000 --- a/dev/tests/verification/TestModule/Test/ExecuteInSeleniumTest.xml +++ /dev/null @@ -1,14 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ ---> - -<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> - <test name="ExecuteInSeleniumTest"> - <executeInSelenium function="function ($webdriver) { return "Hello, World!"}" stepKey="executeInSeleniumStep"/> - </test> -</tests> diff --git a/dev/tests/verification/TestModule/Test/ExecuteJsTest.xml b/dev/tests/verification/TestModule/Test/ExecuteJsTest.xml index b361cdd28..ea80275e1 100644 --- a/dev/tests/verification/TestModule/Test/ExecuteJsTest.xml +++ b/dev/tests/verification/TestModule/Test/ExecuteJsTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="ExecuteJsEscapingTest"> <executeJS function="return $javascriptVariable" stepKey="javaVariableEscape"/> <executeJS function="return {$doNotEscape}" stepKey="mftfVariableNotEscaped"/> diff --git a/dev/tests/verification/TestModule/Test/ExtendedDataTest.xml b/dev/tests/verification/TestModule/Test/ExtendedDataTest.xml index 21f06eb3a..053b26c38 100644 --- a/dev/tests/verification/TestModule/Test/ExtendedDataTest.xml +++ b/dev/tests/verification/TestModule/Test/ExtendedDataTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="ExtendParentDataTest"> <createData entity="extendParentData" stepKey="simpleDataKey"/> <searchAndMultiSelectOption selector="#selector" parameterArray="[{{extendParentData.name}}]" stepKey="getName"/> diff --git a/dev/tests/verification/TestModule/Test/ExtendedFunctionalTest.xml b/dev/tests/verification/TestModule/Test/ExtendedFunctionalTest.xml deleted file mode 100644 index 486a6036f..000000000 --- a/dev/tests/verification/TestModule/Test/ExtendedFunctionalTest.xml +++ /dev/null @@ -1,201 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ ---> - -<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> - <test name="ParentExtendedTest"> - <annotations> - <severity value="AVERAGE"/> - <title value="ParentExtendedTest"/> - <group value="Parent"/> - <features value="Parent"/> - <stories value="Parent"/> - </annotations> - <before> - <amOnPage url="/beforeUrl" stepKey="beforeAmOnPageKey"/> - </before> - <after> - <amOnPage url="/afterUrl" stepKey="afterAmOnPageKey"/> - </after> - <comment stepKey="basicCommentWithNoData" userInput="Parent Comment"/> - </test> - - <test name="ChildExtendedTestReplace" extends="ParentExtendedTest"> - <annotations> - <severity value="MINOR"/> - <title value="ChildExtendedTestReplace"/> - <group value="Child"/> - <features value="Child"/> - <stories value="Child"/> - </annotations> - <comment stepKey="basicCommentWithNoData" userInput="Different Input"/> - </test> - - <test name="ChildExtendedTestReplaceHook" extends="ParentExtendedTest"> - <annotations> - <severity value="MINOR"/> - <title value="ChildExtendedTestReplaceHook"/> - <group value="Child"/> - <features value="Child"/> - <stories value="Child"/> - </annotations> - <before> - <amOnPage url="/slightlyDifferentBeforeUrl" stepKey="beforeAmOnPageKey"/> - </before> - </test> - - <test name="ChildExtendedTestMerging" extends="ParentExtendedTest"> - <annotations> - <severity value="MINOR"/> - <title value="ChildExtendedTestMerging"/> - <group value="Child"/> - <features value="Child"/> - <stories value="Child"/> - </annotations> - <before> - <amOnPage url="/firstUrl" stepKey="firstBeforeAmOnPageKey" before="beforeAmOnPageKey"/> - <amOnPage url="/lastUrl" stepKey="lastBefore" after="beforeAmOnPageKey"/> - </before> - <comment stepKey="lastStepKey" userInput="Last Comment"/> - <comment stepKey="beforeBasicCommentWithNoData" userInput="Before Comment" before="basicCommentWithNoData"/> - <comment stepKey="afterBasicCommentWithNoData" userInput="After Comment" after="basicCommentWithNoData"/> - </test> - - <test name="ChildExtendedTestRemoveAction" extends="ParentExtendedTest"> - <annotations> - <severity value="CRITICAL"/> - <title value="ChildExtendedTestRemoveAction"/> - <group value="Child"/> - <features value="Child"/> - <stories value="Child"/> - </annotations> - <remove keyForRemoval="basicCommentWithNoData"/> - </test> - - <test name="ParentExtendedTestNoHooks"> - <annotations> - <severity value="AVERAGE"/> - <title value="ParentExtendedTestNoHooks"/> - <group value="Parent"/> - <features value="Parent"/> - <stories value="Parent"/> - </annotations> - <comment stepKey="basicCommentWithNoData" userInput="Parent Comment"/> - </test> - - <test name="ChildExtendedTestAddHooks"> - <annotations> - <severity value="AVERAGE"/> - <title value="ChildExtendedTestAddHooks"/> - <group value="Parent"/> - <features value="Parent"/> - <stories value="Parent"/> - </annotations> - <before> - <amOnPage url="/beforeUrl" stepKey="beforeAmOnPageKey"/> - </before> - <after> - <amOnPage url="/afterUrl" stepKey="afterAmOnPageKey"/> - </after> - </test> - - <test name="ChildExtendedTestRemoveHookAction" extends="ParentExtendedTest"> - <annotations> - <severity value="CRITICAL"/> - <title value="ChildExtendedTestRemoveHookAction"/> - <group value="Child"/> - <features value="Child"/> - <stories value="Child"/> - </annotations> - <before> - <remove keyForRemoval="beforeAmOnPageKey"/> - </before> - </test> - <test name="ChildExtendedTestNoParent" extends="ThisTestDoesNotExist"> - <annotations> - <severity value="CRITICAL"/> - <title value="ChildExtendedTestNoParent"/> - <group value="Child"/> - <features value="Child"/> - <stories value="Child"/> - </annotations> - <before> - <remove keyForRemoval="beforeAmOnPageKey"/> - </before> - </test> - <test name="SkippedParent"> - <annotations> - <severity value="CRITICAL"/> - <title value="PARENTSKIPPED"/> - <group value="Parent"/> - <features value="Parent"/> - <stories value="Parent"/> - <skip> - <issueId value="NONE"/> - </skip> - </annotations> - <before> - <amOnPage url="/beforeUrl" stepKey="beforeAmOnPageKey"/> - </before> - <after> - <amOnPage url="/afterUrl" stepKey="afterAmOnPageKey"/> - </after> - <comment userInput="text" stepKey="keepMe"/> - <comment userInput="text" stepKey="replaceMe"/> - </test> - <test name="ExtendingSkippedTest" extends="SkippedParent"> - <annotations> - <severity value="CRITICAL"/> - <title value="ChildExtendedTestSkippedParent"/> - <group value="Child"/> - <features value="Child"/> - <stories value="Child"/> - </annotations> - <comment userInput="child" stepKey="replaceMe"/> - </test> - - <test name="ExtendedTestRelatedToSuiteParentTest"> - <annotations> - <severity value="AVERAGE"/> - <title value="ExtendedTestRelatedToSuiteParentTest"/> - <group value="ExtendedTestRelatedToSuite"/> - <features value="ExtendedTestRelatedToSuiteParentTest"/> - <stories value="ExtendedTestRelatedToSuiteParentTest"/> - </annotations> - <before> - <amOnPage url="/beforeUrl" stepKey="beforeAmOnPageKey"/> - </before> - <after> - <amOnPage url="/afterUrl" stepKey="afterAmOnPageKey"/> - </after> - <comment stepKey="basicCommentWithNoData" userInput="Parent Comment"/> - <amOnPage url="/url/in/parent" stepKey="amOnPageInParent"/> - </test> - - <test name="ExtendedChildTestInSuite" extends="ExtendedTestRelatedToSuiteParentTest"> - <annotations> - <severity value="MINOR"/> - <title value="ExtendedChildTestInSuite"/> - <group value="ExtendedTestInSuite"/> - <features value="ExtendedChildTestInSuite"/> - <stories value="ExtendedChildTestInSuite"/> - </annotations> - <comment stepKey="basicCommentWithNoData" userInput="Different Input"/> - <remove keyForRemoval="amOnPageInParent"/> - </test> - <test name="ExtendedChildTestNotInSuite" extends="ExtendedTestRelatedToSuiteParentTest"> - <annotations> - <severity value="MINOR"/> - <title value="ExtendedChildTestNotInSuite"/> - <features value="ExtendedChildTestNotInSuite"/> - <stories value="ExtendedChildTestNotInSuite"/> - </annotations> - <comment stepKey="basicCommentWithNoData" userInput="Different Input"/> - <remove keyForRemoval="amOnPageInParent"/> - </test> -</tests> \ No newline at end of file diff --git a/dev/tests/verification/TestModule/Test/ExtendedFunctionalTest/ChildExtendedTestAddHooks.xml b/dev/tests/verification/TestModule/Test/ExtendedFunctionalTest/ChildExtendedTestAddHooks.xml new file mode 100644 index 000000000..70cb3c285 --- /dev/null +++ b/dev/tests/verification/TestModule/Test/ExtendedFunctionalTest/ChildExtendedTestAddHooks.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="ChildExtendedTestAddHooks"> + <annotations> + <severity value="AVERAGE"/> + <title value="ChildExtendedTestAddHooks"/> + <group value="Parent"/> + <features value="Parent"/> + <stories value="Parent"/> + </annotations> + <before> + <amOnPage url="/beforeUrl" stepKey="beforeAmOnPageKey"/> + </before> + <after> + <amOnPage url="/afterUrl" stepKey="afterAmOnPageKey"/> + </after> + </test> +</tests> diff --git a/dev/tests/verification/TestModule/Test/ExtendedFunctionalTest/ChildExtendedTestMerging.xml b/dev/tests/verification/TestModule/Test/ExtendedFunctionalTest/ChildExtendedTestMerging.xml new file mode 100644 index 000000000..dfef954e1 --- /dev/null +++ b/dev/tests/verification/TestModule/Test/ExtendedFunctionalTest/ChildExtendedTestMerging.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="ChildExtendedTestMerging" extends="ParentExtendedTest"> + <annotations> + <severity value="MINOR"/> + <title value="ChildExtendedTestMerging"/> + <group value="Child"/> + <features value="Child"/> + <stories value="Child"/> + </annotations> + <before> + <amOnPage url="/firstUrl" stepKey="firstBeforeAmOnPageKey" before="beforeAmOnPageKey"/> + <amOnPage url="/lastUrl" stepKey="lastBefore" after="beforeAmOnPageKey"/> + </before> + <comment stepKey="lastStepKey" userInput="Last Comment"/> + <comment stepKey="beforeBasicCommentWithNoData" userInput="Before Comment" before="basicCommentWithNoData"/> + <comment stepKey="afterBasicCommentWithNoData" userInput="After Comment" after="basicCommentWithNoData"/> + </test> +</tests> diff --git a/dev/tests/verification/TestModule/Test/ExtendedFunctionalTest/ChildExtendedTestNoParent.xml b/dev/tests/verification/TestModule/Test/ExtendedFunctionalTest/ChildExtendedTestNoParent.xml new file mode 100644 index 000000000..81add5d0e --- /dev/null +++ b/dev/tests/verification/TestModule/Test/ExtendedFunctionalTest/ChildExtendedTestNoParent.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="ChildExtendedTestNoParent" extends="ThisTestDoesNotExist"> + <annotations> + <severity value="CRITICAL"/> + <title value="ChildExtendedTestNoParent"/> + <group value="Child"/> + <features value="Child"/> + <stories value="Child"/> + </annotations> + <before> + <remove keyForRemoval="beforeAmOnPageKey"/> + </before> + </test> +</tests> diff --git a/dev/tests/verification/TestModule/Test/ExtendedFunctionalTest/ChildExtendedTestRemoveAction.xml b/dev/tests/verification/TestModule/Test/ExtendedFunctionalTest/ChildExtendedTestRemoveAction.xml new file mode 100644 index 000000000..aa82ced68 --- /dev/null +++ b/dev/tests/verification/TestModule/Test/ExtendedFunctionalTest/ChildExtendedTestRemoveAction.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="ChildExtendedTestRemoveAction" extends="ParentExtendedTest"> + <annotations> + <severity value="CRITICAL"/> + <title value="ChildExtendedTestRemoveAction"/> + <group value="Child"/> + <features value="Child"/> + <stories value="Child"/> + </annotations> + <remove keyForRemoval="basicCommentWithNoData"/> + </test> +</tests> diff --git a/dev/tests/verification/TestModule/Test/ExtendedFunctionalTest/ChildExtendedTestRemoveHookAction.xml b/dev/tests/verification/TestModule/Test/ExtendedFunctionalTest/ChildExtendedTestRemoveHookAction.xml new file mode 100644 index 000000000..7388a293e --- /dev/null +++ b/dev/tests/verification/TestModule/Test/ExtendedFunctionalTest/ChildExtendedTestRemoveHookAction.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="ChildExtendedTestRemoveHookAction" extends="ParentExtendedTest"> + <annotations> + <severity value="CRITICAL"/> + <title value="ChildExtendedTestRemoveHookAction"/> + <group value="Child"/> + <features value="Child"/> + <stories value="Child"/> + </annotations> + <before> + <remove keyForRemoval="beforeAmOnPageKey"/> + </before> + </test> +</tests> diff --git a/dev/tests/verification/TestModule/Test/ExtendedFunctionalTest/ChildExtendedTestReplace.xml b/dev/tests/verification/TestModule/Test/ExtendedFunctionalTest/ChildExtendedTestReplace.xml new file mode 100644 index 000000000..1872fc645 --- /dev/null +++ b/dev/tests/verification/TestModule/Test/ExtendedFunctionalTest/ChildExtendedTestReplace.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="ChildExtendedTestReplace" extends="ParentExtendedTest"> + <annotations> + <severity value="MINOR"/> + <title value="ChildExtendedTestReplace"/> + <group value="Child"/> + <features value="Child"/> + <stories value="Child"/> + </annotations> + <comment stepKey="basicCommentWithNoData" userInput="Different Input"/> + </test> +</tests> diff --git a/dev/tests/verification/TestModule/Test/ExtendedFunctionalTest/ChildExtendedTestReplaceHook.xml b/dev/tests/verification/TestModule/Test/ExtendedFunctionalTest/ChildExtendedTestReplaceHook.xml new file mode 100644 index 000000000..d12bb6d09 --- /dev/null +++ b/dev/tests/verification/TestModule/Test/ExtendedFunctionalTest/ChildExtendedTestReplaceHook.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="ChildExtendedTestReplaceHook" extends="ParentExtendedTest"> + <annotations> + <severity value="MINOR"/> + <title value="ChildExtendedTestReplaceHook"/> + <group value="Child"/> + <features value="Child"/> + <stories value="Child"/> + </annotations> + <before> + <amOnPage url="/slightlyDifferentBeforeUrl" stepKey="beforeAmOnPageKey"/> + </before> + </test> +</tests> diff --git a/dev/tests/verification/TestModule/Test/ExtendedFunctionalTest/ExtendedChildTestInSuite.xml b/dev/tests/verification/TestModule/Test/ExtendedFunctionalTest/ExtendedChildTestInSuite.xml new file mode 100644 index 000000000..76e1e10e3 --- /dev/null +++ b/dev/tests/verification/TestModule/Test/ExtendedFunctionalTest/ExtendedChildTestInSuite.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="ExtendedChildTestInSuite" extends="ExtendedTestRelatedToSuiteParentTest"> + <annotations> + <severity value="MINOR"/> + <title value="ExtendedChildTestInSuite"/> + <group value="ExtendedTestInSuite"/> + <features value="ExtendedChildTestInSuite"/> + <stories value="ExtendedChildTestInSuite"/> + </annotations> + <comment stepKey="basicCommentWithNoData" userInput="Different Input"/> + <remove keyForRemoval="amOnPageInParent"/> + </test> +</tests> diff --git a/dev/tests/verification/TestModule/Test/ExtendedFunctionalTest/ExtendedChildTestNotInSuite.xml b/dev/tests/verification/TestModule/Test/ExtendedFunctionalTest/ExtendedChildTestNotInSuite.xml new file mode 100644 index 000000000..54d0f5cbf --- /dev/null +++ b/dev/tests/verification/TestModule/Test/ExtendedFunctionalTest/ExtendedChildTestNotInSuite.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="ExtendedChildTestNotInSuite" extends="ExtendedTestRelatedToSuiteParentTest"> + <annotations> + <severity value="MINOR"/> + <title value="ExtendedChildTestNotInSuite"/> + <features value="ExtendedChildTestNotInSuite"/> + <stories value="ExtendedChildTestNotInSuite"/> + </annotations> + <comment stepKey="basicCommentWithNoData" userInput="Different Input"/> + <remove keyForRemoval="amOnPageInParent"/> + </test> +</tests> diff --git a/dev/tests/verification/TestModule/Test/ExtendedFunctionalTest/ExtendedTestRelatedToSuiteParentTest.xml b/dev/tests/verification/TestModule/Test/ExtendedFunctionalTest/ExtendedTestRelatedToSuiteParentTest.xml new file mode 100644 index 000000000..1bc656149 --- /dev/null +++ b/dev/tests/verification/TestModule/Test/ExtendedFunctionalTest/ExtendedTestRelatedToSuiteParentTest.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="ExtendedTestRelatedToSuiteParentTest"> + <annotations> + <severity value="AVERAGE"/> + <title value="ExtendedTestRelatedToSuiteParentTest"/> + <group value="ExtendedTestRelatedToSuite"/> + <features value="ExtendedTestRelatedToSuiteParentTest"/> + <stories value="ExtendedTestRelatedToSuiteParentTest"/> + </annotations> + <before> + <amOnPage url="/beforeUrl" stepKey="beforeAmOnPageKey"/> + </before> + <after> + <amOnPage url="/afterUrl" stepKey="afterAmOnPageKey"/> + </after> + <comment stepKey="basicCommentWithNoData" userInput="Parent Comment"/> + <amOnPage url="/url/in/parent" stepKey="amOnPageInParent"/> + </test> +</tests> diff --git a/dev/tests/verification/TestModule/Test/ExtendedFunctionalTest/ExtendingSkippedTest.xml b/dev/tests/verification/TestModule/Test/ExtendedFunctionalTest/ExtendingSkippedTest.xml new file mode 100644 index 000000000..f451c143a --- /dev/null +++ b/dev/tests/verification/TestModule/Test/ExtendedFunctionalTest/ExtendingSkippedTest.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="ExtendingSkippedTest" extends="SkippedParent"> + <annotations> + <severity value="CRITICAL"/> + <title value="ChildExtendedTestSkippedParent"/> + <group value="Child"/> + <features value="Child"/> + <stories value="Child"/> + </annotations> + <comment userInput="child" stepKey="replaceMe"/> + </test> +</tests> diff --git a/dev/tests/verification/TestModule/Test/ExtendedFunctionalTest/ParentExtendedTest.xml b/dev/tests/verification/TestModule/Test/ExtendedFunctionalTest/ParentExtendedTest.xml new file mode 100644 index 000000000..ed5aa6c16 --- /dev/null +++ b/dev/tests/verification/TestModule/Test/ExtendedFunctionalTest/ParentExtendedTest.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="ParentExtendedTest"> + <annotations> + <severity value="AVERAGE"/> + <title value="ParentExtendedTest"/> + <group value="Parent"/> + <features value="Parent"/> + <stories value="Parent"/> + </annotations> + <before> + <amOnPage url="/beforeUrl" stepKey="beforeAmOnPageKey"/> + </before> + <after> + <amOnPage url="/afterUrl" stepKey="afterAmOnPageKey"/> + </after> + <comment stepKey="basicCommentWithNoData" userInput="Parent Comment"/> + </test> +</tests> diff --git a/dev/tests/verification/TestModule/Test/ExtendedFunctionalTest/ParentExtendedTestNoHooks.xml b/dev/tests/verification/TestModule/Test/ExtendedFunctionalTest/ParentExtendedTestNoHooks.xml new file mode 100644 index 000000000..c7d17857e --- /dev/null +++ b/dev/tests/verification/TestModule/Test/ExtendedFunctionalTest/ParentExtendedTestNoHooks.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="ParentExtendedTestNoHooks"> + <annotations> + <severity value="AVERAGE"/> + <title value="ParentExtendedTestNoHooks"/> + <group value="Parent"/> + <features value="Parent"/> + <stories value="Parent"/> + </annotations> + <comment stepKey="basicCommentWithNoData" userInput="Parent Comment"/> + </test> +</tests> diff --git a/dev/tests/verification/TestModule/Test/ExtendedFunctionalTest/SkippedParent.xml b/dev/tests/verification/TestModule/Test/ExtendedFunctionalTest/SkippedParent.xml new file mode 100644 index 000000000..ff36905e7 --- /dev/null +++ b/dev/tests/verification/TestModule/Test/ExtendedFunctionalTest/SkippedParent.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="SkippedParent"> + <annotations> + <severity value="CRITICAL"/> + <title value="PARENTSKIPPED"/> + <group value="Parent"/> + <features value="Parent"/> + <stories value="Parent"/> + <skip> + <issueId value="NONE"/> + </skip> + </annotations> + <before> + <amOnPage url="/beforeUrl" stepKey="beforeAmOnPageKey"/> + </before> + <after> + <amOnPage url="/afterUrl" stepKey="afterAmOnPageKey"/> + </after> + <comment userInput="text" stepKey="keepMe"/> + <comment userInput="text" stepKey="replaceMe"/> + </test> +</tests> diff --git a/dev/tests/verification/TestModule/Test/GroupSkipGenerationTest.xml b/dev/tests/verification/TestModule/Test/GroupSkipGenerationTest.xml new file mode 100644 index 000000000..35cf74d33 --- /dev/null +++ b/dev/tests/verification/TestModule/Test/GroupSkipGenerationTest.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="GroupSkipGenerationTest"> + <annotations> + <stories value="GroupSkipGenerationTestStory"/> + <title value="GroupSkipGenerationTestTitle"/> + <description value="GroupSkipGenerationTestDescription"/> + <severity value="AVERAGE"/> + <group value="skip"/> + </annotations> + </test> +</tests> diff --git a/dev/tests/verification/TestModule/Test/HookActionsTest.xml b/dev/tests/verification/TestModule/Test/HookActionsTest.xml index 7e3521e4f..b1350a0a7 100644 --- a/dev/tests/verification/TestModule/Test/HookActionsTest.xml +++ b/dev/tests/verification/TestModule/Test/HookActionsTest.xml @@ -7,15 +7,11 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="HookActionsTest"> <before> - <createData entity="sampleCreatedEntity" stepKey="sampleCreateBefore"/> - <deleteData createDataKey="sampleCreateBefore" stepKey="sampleDeleteBefore"/> - <createData entity="sampleCreatedEntity" stepKey="sampleCreateForAfter"/> </before> <after> - <createData entity="sampleCreatedEntity" stepKey="sampleCreateAfter"/> <deleteData createDataKey="sampleCreateForAfter" stepKey="sampleDeleteAfter"/> </after> </test> diff --git a/dev/tests/verification/TestModule/Test/LocatorFunctionTest.xml b/dev/tests/verification/TestModule/Test/LocatorFunctionTest.xml index f9caa59fc..eefd75e1f 100644 --- a/dev/tests/verification/TestModule/Test/LocatorFunctionTest.xml +++ b/dev/tests/verification/TestModule/Test/LocatorFunctionTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="LocatorFunctionTest"> <createData entity="ReplacementPerson" stepKey="data"/> diff --git a/dev/tests/verification/TestModule/Test/MergeFunctionalTest.xml b/dev/tests/verification/TestModule/Test/MergeFunctionalTest.xml deleted file mode 100644 index 5189b8cf4..000000000 --- a/dev/tests/verification/TestModule/Test/MergeFunctionalTest.xml +++ /dev/null @@ -1,61 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ ---> - -<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> - <test name="BasicMergeTest"> - <annotations> - <severity value="CRITICAL"/> - <title value="BasicMergeTest"/> - <group value="functional"/> - <features value="Merge Functional Cest"/> - <stories value="MQE-433"/> - </annotations> - <before> - <amOnPage url="/beforeUrl" stepKey="before1"/> - </before> - <after> - <amOnPage url="/afterUrl" stepKey="after1"/> - </after> - <amOnPage stepKey="step1" url="/step1"/> - <fillField stepKey="step3" selector="#username" userInput="step3"/> - <fillField stepKey="step5" selector="#password" userInput="step5"/> - <click stepKey="step6" selector=".step6"/> - <click stepKey="step10" selector="#step10ShouldNotInResult"/> - </test> - <test name="MergedReferencesTest"> - <annotations> - <severity value="CRITICAL"/> - <title value="MergedReferencesTest"/> - <group value="functional"/> - <features value="Merge Functional Cest"/> - <stories value="MQE-433"/> - </annotations> - <before> - <amOnPage url="/beforeUrl" stepKey="before1"/> - </before> - <after> - <amOnPage url="/afterUrl" stepKey="after1"/> - </after> - <fillField stepKey="fillField1" selector="{{SampleSection.mergeElement}}" userInput="{{DefaultPerson.mergedField}}"/> - <fillField stepKey="fillField2" selector="{{SampleSection.newElement}}" userInput="{{DefaultPerson.newField}}" /> - </test> - <test name="MergeMassViaInsertBefore" insertBefore="fillField2"> - <click stepKey="clickOne" selector="#mergeOne"/> - <click stepKey="clickTwo" selector="#mergeTwo"/> - <click stepKey="clickThree" selector="#mergeThree"/> - </test> - <test name="MergeMassViaInsertAfter" insertAfter="fillField2"> - <click stepKey="clickOne" selector="#mergeOne"/> - <click stepKey="clickTwo" selector="#mergeTwo"/> - <click stepKey="clickThree" selector="#mergeThree"/> - </test> - <test name="MergeSkip"> - <comment userInput="ThisTestShouldBeSkipped" stepKey="skipComment"/> - </test> -</tests> diff --git a/dev/tests/verification/TestModule/Test/MergeFunctionalTest/BasicMergeTest.xml b/dev/tests/verification/TestModule/Test/MergeFunctionalTest/BasicMergeTest.xml new file mode 100644 index 000000000..3237ba7bb --- /dev/null +++ b/dev/tests/verification/TestModule/Test/MergeFunctionalTest/BasicMergeTest.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="BasicMergeTest"> + <annotations> + <severity value="CRITICAL"/> + <title value="BasicMergeTest"/> + <group value="functional"/> + <features value="Merge Functional Cest"/> + <stories value="MQE-433"/> + </annotations> + <before> + <amOnPage url="/beforeUrl" stepKey="before1"/> + </before> + <after> + <amOnPage url="/afterUrl" stepKey="after1"/> + </after> + <amOnPage stepKey="step1" url="/step1"/> + <fillField stepKey="step3" selector="#username" userInput="step3"/> + <fillField stepKey="step5" selector="#password" userInput="step5"/> + <click stepKey="step6" selector=".step6"/> + <click stepKey="step10" selector="#step10ShouldNotInResult"/> + </test> +</tests> diff --git a/dev/tests/verification/TestModule/Test/MergeFunctionalTest/MergeMassViaInsertAfter.xml b/dev/tests/verification/TestModule/Test/MergeFunctionalTest/MergeMassViaInsertAfter.xml new file mode 100644 index 000000000..2f35be2cb --- /dev/null +++ b/dev/tests/verification/TestModule/Test/MergeFunctionalTest/MergeMassViaInsertAfter.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="MergeMassViaInsertAfter" insertAfter="fillField2"> + <click stepKey="clickOne" selector="#mergeOne"/> + <click stepKey="clickTwo" selector="#mergeTwo"/> + <click stepKey="clickThree" selector="#mergeThree"/> + </test> +</tests> diff --git a/dev/tests/verification/TestModule/Test/MergeFunctionalTest/MergeMassViaInsertBefore.xml b/dev/tests/verification/TestModule/Test/MergeFunctionalTest/MergeMassViaInsertBefore.xml new file mode 100644 index 000000000..913644f51 --- /dev/null +++ b/dev/tests/verification/TestModule/Test/MergeFunctionalTest/MergeMassViaInsertBefore.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="MergeMassViaInsertBefore" insertBefore="fillField2"> + <click stepKey="clickOne" selector="#mergeOne"/> + <click stepKey="clickTwo" selector="#mergeTwo"/> + <click stepKey="clickThree" selector="#mergeThree"/> + </test> +</tests> diff --git a/dev/tests/verification/TestModule/Test/MergeFunctionalTest/MergeSkip.xml b/dev/tests/verification/TestModule/Test/MergeFunctionalTest/MergeSkip.xml new file mode 100644 index 000000000..1d8b82200 --- /dev/null +++ b/dev/tests/verification/TestModule/Test/MergeFunctionalTest/MergeSkip.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="MergeSkip"> + <comment userInput="ThisTestShouldBeSkipped" stepKey="skipComment"/> + </test> +</tests> diff --git a/dev/tests/verification/TestModule/Test/MergeFunctionalTest/MergedActionGroupReturningValueTest.xml b/dev/tests/verification/TestModule/Test/MergeFunctionalTest/MergedActionGroupReturningValueTest.xml new file mode 100644 index 000000000..baf7c0ded --- /dev/null +++ b/dev/tests/verification/TestModule/Test/MergeFunctionalTest/MergedActionGroupReturningValueTest.xml @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="MergedActionGroupReturningValueTest"> + <annotations> + <severity value="CRITICAL"/> + <group value="functional"/> + <features value="Action Group Functional Cest"/> + <stories value="MQE-433"/> + </annotations> + <before> + <createData entity="ReplacementPerson" stepKey="createPersonParam"/> + <actionGroup ref="FunctionalActionGroup" stepKey="beforeGroup"/> + </before> + <amOnPage url="/someUrl" stepKey="step1"/> + <actionGroup ref="MergeActionGroupReturningValueActionGroup" stepKey="actionGroupWithReturnValue1"> + <argument name="myArg" value="DefaultPerson"/> + </actionGroup> + <actionGroup ref="actionGroupWithStringUsage" stepKey="actionGroupWithStringUsage1"> + <argument name="someArgument" value="{$actionGroupWithReturnValue1}"/> + </actionGroup> + <after> + <actionGroup ref="FunctionalActionGroup" stepKey="afterGroup"/> + </after> + </test> +</tests> diff --git a/dev/tests/verification/TestModule/Test/MergeFunctionalTest/MergedReferencesTest.xml b/dev/tests/verification/TestModule/Test/MergeFunctionalTest/MergedReferencesTest.xml new file mode 100644 index 000000000..d48de5d3a --- /dev/null +++ b/dev/tests/verification/TestModule/Test/MergeFunctionalTest/MergedReferencesTest.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="MergedReferencesTest"> + <annotations> + <severity value="CRITICAL"/> + <title value="MergedReferencesTest"/> + <group value="functional"/> + <features value="Merge Functional Cest"/> + <stories value="MQE-433"/> + </annotations> + <before> + <amOnPage url="/beforeUrl" stepKey="before1"/> + </before> + <after> + <amOnPage url="/afterUrl" stepKey="after1"/> + </after> + <fillField stepKey="fillField1" selector="{{SampleSection.mergeElement}}" userInput="{{DefaultPerson.mergedField}}"/> + <fillField stepKey="fillField2" selector="{{SampleSection.newElement}}" userInput="{{DefaultPerson.newField}}" /> + </test> +</tests> diff --git a/dev/tests/verification/TestModule/Test/PageReplacementTest.xml b/dev/tests/verification/TestModule/Test/PageReplacementTest.xml index b6adee5d7..8cd6fe572 100644 --- a/dev/tests/verification/TestModule/Test/PageReplacementTest.xml +++ b/dev/tests/verification/TestModule/Test/PageReplacementTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="PageReplacementTest"> <createData entity="simpleData" stepKey="datakey"/> <amOnPage stepKey="noParamPage" url="{{NoParamPage.url}}"/> diff --git a/dev/tests/verification/TestModule/Test/ParameterArrayTest.xml b/dev/tests/verification/TestModule/Test/ParameterArrayTest.xml index e9bef4244..8bb57ae74 100644 --- a/dev/tests/verification/TestModule/Test/ParameterArrayTest.xml +++ b/dev/tests/verification/TestModule/Test/ParameterArrayTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="ParameterArrayTest"> <createData entity="simpleParamData" stepKey="simpleDataKey"/> <searchAndMultiSelectOption selector="#selector" parameterArray="[{{simpleParamData.name}}]" stepKey="xmlSimpleReplace"/> diff --git a/dev/tests/verification/TestModule/Test/PersistedReplacementTest.xml b/dev/tests/verification/TestModule/Test/PersistedReplacementTest.xml index 4b0f3c902..ba86a1a5d 100644 --- a/dev/tests/verification/TestModule/Test/PersistedReplacementTest.xml +++ b/dev/tests/verification/TestModule/Test/PersistedReplacementTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="PersistedReplacementTest"> <before> <createData entity="ReplacementPerson" stepKey="createData1"/> diff --git a/dev/tests/verification/TestModule/Test/PersistenceActionGroupAppendingTest.xml b/dev/tests/verification/TestModule/Test/PersistenceActionGroupAppendingTest.xml index 20a9b6a28..c76409f26 100644 --- a/dev/tests/verification/TestModule/Test/PersistenceActionGroupAppendingTest.xml +++ b/dev/tests/verification/TestModule/Test/PersistenceActionGroupAppendingTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="PersistenceActionGroupAppendingTest"> <before> <actionGroup ref="DataPersistenceAppendingActionGroup" stepKey="ACTIONGROUPBEFORE"/> diff --git a/dev/tests/verification/TestModule/Test/PersistenceCustomFieldsTest.xml b/dev/tests/verification/TestModule/Test/PersistenceCustomFieldsTest.xml index 747c2c132..867dd35ff 100644 --- a/dev/tests/verification/TestModule/Test/PersistenceCustomFieldsTest.xml +++ b/dev/tests/verification/TestModule/Test/PersistenceCustomFieldsTest.xml @@ -7,31 +7,18 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="PersistenceCustomFieldsTest"> <before> <createData entity="DefaultPerson" stepKey="createData1"> <field key="firstname">Mac</field> - <field key="lastname">{{simpleData.lastname}}</field> - </createData> - <createData entity="uniqueData" stepKey="createData2"> - <requiredEntity createDataKey="createData1"/> - <field key="firstname">$$createData1.firstname$$</field> + <field key="lastname">Bar</field> </createData> </before> - <createData entity="simpleData" stepKey="createdData"> - <field key="favoriteIndex">1</field> - <field key="middlename">Kovacs</field> - </createData> <createData entity="UniquePerson" stepKey="createdData3"> <requiredEntity createDataKey="createdData"/> <field key="firstname">Takeshi</field> <field key="lastname">Kovacs</field> </createData> - <actionGroup ref="PersistenceActionGroup" stepKey="createdAG"> - <argument name="arg1" value="string1"/> - <argument name="arg2" value="DefaultPerson.firstname"/> - <argument name="arg3" value="$createdData3.firstname$"/> - </actionGroup> </test> </tests> diff --git a/dev/tests/verification/TestModule/Test/SampleSuite2Test.xml b/dev/tests/verification/TestModule/Test/SampleSuite2Test/AdditionalExcludeTest2.xml similarity index 59% rename from dev/tests/verification/TestModule/Test/SampleSuite2Test.xml rename to dev/tests/verification/TestModule/Test/SampleSuite2Test/AdditionalExcludeTest2.xml index 92f10ddd0..c38915bf4 100644 --- a/dev/tests/verification/TestModule/Test/SampleSuite2Test.xml +++ b/dev/tests/verification/TestModule/Test/SampleSuite2Test/AdditionalExcludeTest2.xml @@ -5,9 +5,8 @@ * See COPYING.txt for license details. */ --> - <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="additionalExcludeTest2"> <annotations> <group value="exclude"/> @@ -17,10 +16,4 @@ <click stepKey="clickOnSomething" selector=".clickable"/> <fillField stepKey="fillAField" selector=".fillable"/> </test> - <test name="additionalIncludeTest2"> - <amOnPage stepKey="testOnPage" url="/someUrl"/> - <see stepKey="seeThePage" selector=".someSelector"/> - <click stepKey="clickOnSomething" selector=".clickable"/> - <fillField stepKey="fillAField" selector=".fillable"/> - </test> </tests> diff --git a/dev/tests/verification/TestModule/Test/SampleSuite2Test/AdditionalIncludeTest2.xml b/dev/tests/verification/TestModule/Test/SampleSuite2Test/AdditionalIncludeTest2.xml new file mode 100644 index 000000000..81f6d7c1c --- /dev/null +++ b/dev/tests/verification/TestModule/Test/SampleSuite2Test/AdditionalIncludeTest2.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="additionalIncludeTest2"> + <amOnPage stepKey="testOnPage" url="/someUrl"/> + <see stepKey="seeThePage" selector=".someSelector"/> + <click stepKey="clickOnSomething" selector=".clickable"/> + <fillField stepKey="fillAField" selector=".fillable"/> + </test> +</tests> diff --git a/dev/tests/verification/TestModule/Test/SampleSuiteTest.xml b/dev/tests/verification/TestModule/Test/SampleSuiteTest.xml deleted file mode 100644 index 921071cfc..000000000 --- a/dev/tests/verification/TestModule/Test/SampleSuiteTest.xml +++ /dev/null @@ -1,50 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ ---> - -<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> - <test name="IncludeTest"> - <amOnPage stepKey="testOnPage" url="/someUrl"/> - <see stepKey="seeThePage" selector=".someSelector"/> - <click stepKey="clickOnSomething" selector=".clickable"/> - <fillField stepKey="fillAField" selector=".fillable"/> - </test> - <test name="ExcludeTest"> - <amOnPage stepKey="testOnPage" url="/someUrl"/> - <see stepKey="seeThePage" selector=".someSelector"/> - <click stepKey="clickOnSomething" selector=".clickable"/> - <fillField stepKey="fillAField" selector=".fillable"/> - </test> - <test name="IncludeTest2"> - <annotations> - <group value="include"/> - </annotations> - <amOnPage stepKey="testOnPage" url="/someUrl"/> - <see stepKey="seeThePage" selector=".someSelector"/> - <click stepKey="clickOnSomething" selector=".clickable"/> - <fillField stepKey="fillAField" selector=".fillable"/> - </test> - <test name="additionalTest"> - <annotations> - <group value="include"/> - </annotations> - <amOnPage stepKey="testOnPage" url="/someUrl"/> - <see stepKey="seeThePage" selector=".someSelector"/> - <click stepKey="clickOnSomething" selector=".clickable"/> - <fillField stepKey="fillAField" selector=".fillable"/> - </test> - <test name="ExcludeTest2"> - <annotations> - <group value="include"/> - </annotations> - <amOnPage stepKey="testOnPage" url="/someUrl"/> - <see stepKey="seeThePage" selector=".someSelector"/> - <click stepKey="clickOnSomething" selector=".clickable"/> - <fillField stepKey="fillAField" selector=".fillable"/> - </test> -</tests> diff --git a/dev/tests/verification/TestModule/Test/SampleSuiteTest/AdditionalTest.xml b/dev/tests/verification/TestModule/Test/SampleSuiteTest/AdditionalTest.xml new file mode 100644 index 000000000..848d45612 --- /dev/null +++ b/dev/tests/verification/TestModule/Test/SampleSuiteTest/AdditionalTest.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="additionalTest"> + <annotations> + <group value="include"/> + </annotations> + <amOnPage stepKey="testOnPage" url="/someUrl"/> + <see stepKey="seeThePage" selector=".someSelector"/> + <click stepKey="clickOnSomething" selector=".clickable"/> + <fillField stepKey="fillAField" selector=".fillable"/> + </test> +</tests> diff --git a/dev/tests/verification/TestModule/Test/SampleSuiteTest/ExcludeTest.xml b/dev/tests/verification/TestModule/Test/SampleSuiteTest/ExcludeTest.xml new file mode 100644 index 000000000..0996e4a59 --- /dev/null +++ b/dev/tests/verification/TestModule/Test/SampleSuiteTest/ExcludeTest.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="ExcludeTest"> + <amOnPage stepKey="testOnPage" url="/someUrl"/> + <see stepKey="seeThePage" selector=".someSelector"/> + <click stepKey="clickOnSomething" selector=".clickable"/> + <fillField stepKey="fillAField" selector=".fillable"/> + </test> +</tests> diff --git a/dev/tests/verification/TestModule/Test/SampleSuiteTest/ExcludeTest2.xml b/dev/tests/verification/TestModule/Test/SampleSuiteTest/ExcludeTest2.xml new file mode 100644 index 000000000..d03b847da --- /dev/null +++ b/dev/tests/verification/TestModule/Test/SampleSuiteTest/ExcludeTest2.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="ExcludeTest2"> + <annotations> + <group value="include"/> + </annotations> + <amOnPage stepKey="testOnPage" url="/someUrl"/> + <see stepKey="seeThePage" selector=".someSelector"/> + <click stepKey="clickOnSomething" selector=".clickable"/> + <fillField stepKey="fillAField" selector=".fillable"/> + </test> +</tests> diff --git a/dev/tests/verification/TestModule/Test/SampleSuiteTest/IncludeActionsInDifferentModulesTest.xml b/dev/tests/verification/TestModule/Test/SampleSuiteTest/IncludeActionsInDifferentModulesTest.xml new file mode 100644 index 000000000..1be662bbe --- /dev/null +++ b/dev/tests/verification/TestModule/Test/SampleSuiteTest/IncludeActionsInDifferentModulesTest.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="IncludeActionsInDifferentModulesTest"> + <createData entity="SecretData" stepKey="create"> + <field key="someKey">dataHere</field> + </createData> + <fillField selector="#fill" userInput="{{SecretData.key2}}+$create.key2$" stepKey="fill"/> + <magentoCLI command="create.key2$" stepKey="cli"/> + <actionGroup ref="ActionGroupReturningValueActionGroup" stepKey="return"> + <argument name="count" value="3"/> + </actionGroup> + </test> +</tests> diff --git a/dev/tests/verification/TestModule/Test/SampleSuiteTest/IncludeTest.xml b/dev/tests/verification/TestModule/Test/SampleSuiteTest/IncludeTest.xml new file mode 100644 index 000000000..b293742c7 --- /dev/null +++ b/dev/tests/verification/TestModule/Test/SampleSuiteTest/IncludeTest.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="IncludeTest"> + <amOnPage stepKey="testOnPage" url="/someUrl"/> + <see stepKey="seeThePage" selector=".someSelector"/> + <click stepKey="clickOnSomething" selector=".clickable"/> + <fillField stepKey="fillAField" selector=".fillable"/> + </test> +</tests> diff --git a/dev/tests/verification/TestModule/Test/SampleSuiteTest/IncludeTest2.xml b/dev/tests/verification/TestModule/Test/SampleSuiteTest/IncludeTest2.xml new file mode 100644 index 000000000..d6c40fe2d --- /dev/null +++ b/dev/tests/verification/TestModule/Test/SampleSuiteTest/IncludeTest2.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="IncludeTest2"> + <annotations> + <group value="include"/> + </annotations> + <amOnPage stepKey="testOnPage" url="/someUrl"/> + <see stepKey="seeThePage" selector=".someSelector"/> + <click stepKey="clickOnSomething" selector=".clickable"/> + <fillField stepKey="fillAField" selector=".fillable"/> + </test> +</tests> diff --git a/dev/tests/verification/TestModule/Test/SecretCredentialDataTest.xml b/dev/tests/verification/TestModule/Test/SecretCredentialDataTest.xml index 4044a86e3..d87be5d3b 100644 --- a/dev/tests/verification/TestModule/Test/SecretCredentialDataTest.xml +++ b/dev/tests/verification/TestModule/Test/SecretCredentialDataTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="secretCredentialDataTest"> <createData entity="_defaultProduct" stepKey="createProductWithFieldOverridesUsingHardcodedData1"> <field key="qty">123</field> diff --git a/dev/tests/verification/TestModule/Test/SectionReplacementTest.xml b/dev/tests/verification/TestModule/Test/SectionReplacementTest.xml index 17aeb4d69..3b066fcdf 100644 --- a/dev/tests/verification/TestModule/Test/SectionReplacementTest.xml +++ b/dev/tests/verification/TestModule/Test/SectionReplacementTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="SectionReplacementTest"> <click stepKey="selectorReplace" selector="{{SampleSection.simpleElement}}"/> <click stepKey="selectorReplaceTimeout" selector="{{SampleSection.timeoutElement}}"/> diff --git a/dev/tests/verification/TestModule/Test/SkippedTest.xml b/dev/tests/verification/TestModule/Test/SkippedTest.xml deleted file mode 100644 index 7641e270e..000000000 --- a/dev/tests/verification/TestModule/Test/SkippedTest.xml +++ /dev/null @@ -1,60 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ ---> - -<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> - <test name="SkippedTest"> - <annotations> - <stories value="skipped"/> - <title value="skippedTest"/> - <description value=""/> - <severity value="AVERAGE"/> - <skip> - <issueId value="SkippedValue"/> - </skip> - </annotations> - </test> - <test name="SkippedTestWithHooks"> - <annotations> - <stories value="skippedWithHooks"/> - <title value="skippedTestWithHooks"/> - <description value=""/> - <severity value="AVERAGE"/> - <skip> - <issueId value="SkippedValue"/> - </skip> - </annotations> - <before> - <comment userInput="skippedComment" stepKey="beforeComment"/> - </before> - <after> - <comment userInput="skippedComment" stepKey="afterComment"/> - </after> - </test> - <test name="SkippedTestTwoIssues"> - <annotations> - <stories value="skippedMultiple"/> - <title value="skippedMultipleIssuesTest"/> - <description value=""/> - <severity value="AVERAGE"/> - <skip> - <issueId value="SkippedValue"/> - <issueId value="SecondSkippedValue"/> - </skip> - </annotations> - </test> - <test name="SkippedTestNoIssues"> - <annotations> - <stories value="skippedNo"/> - <title value="skippedNoIssuesTest"/> - <description value=""/> - <severity value="AVERAGE"/> - <group value="skip"/> - </annotations> - </test> -</tests> diff --git a/dev/tests/verification/TestModule/Test/SkippedTest/SkippedTest.xml b/dev/tests/verification/TestModule/Test/SkippedTest/SkippedTest.xml new file mode 100644 index 000000000..f0162a562 --- /dev/null +++ b/dev/tests/verification/TestModule/Test/SkippedTest/SkippedTest.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="SkippedTest"> + <annotations> + <stories value="skipped"/> + <title value="skippedTest"/> + <description value=""/> + <severity value="AVERAGE"/> + <skip> + <issueId value="SkippedValue"/> + </skip> + </annotations> + </test> +</tests> diff --git a/dev/tests/verification/TestModule/Test/SkippedTest/SkippedTestTwoIssues.xml b/dev/tests/verification/TestModule/Test/SkippedTest/SkippedTestTwoIssues.xml new file mode 100644 index 000000000..66ad090b4 --- /dev/null +++ b/dev/tests/verification/TestModule/Test/SkippedTest/SkippedTestTwoIssues.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="SkippedTestTwoIssues"> + <annotations> + <stories value="skippedMultiple"/> + <title value="skippedMultipleIssuesTest"/> + <description value=""/> + <severity value="AVERAGE"/> + <skip> + <issueId value="SkippedValue"/> + <issueId value="SecondSkippedValue"/> + </skip> + </annotations> + </test> +</tests> diff --git a/dev/tests/verification/TestModule/Test/SkippedTest/SkippedTestWithHooks.xml b/dev/tests/verification/TestModule/Test/SkippedTest/SkippedTestWithHooks.xml new file mode 100644 index 000000000..790a44a40 --- /dev/null +++ b/dev/tests/verification/TestModule/Test/SkippedTest/SkippedTestWithHooks.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="SkippedTestWithHooks"> + <annotations> + <stories value="skippedWithHooks"/> + <title value="skippedTestWithHooks"/> + <description value=""/> + <severity value="AVERAGE"/> + <skip> + <issueId value="SkippedValue"/> + </skip> + </annotations> + <before> + <comment userInput="skippedComment" stepKey="beforeComment"/> + </before> + <after> + <comment userInput="skippedComment" stepKey="afterComment"/> + </after> + </test> +</tests> diff --git a/dev/tests/verification/TestModule/Test/SkippedTest/SkippedTestWithIssueMustGetSkippedWithoutErrorExitCode.xml b/dev/tests/verification/TestModule/Test/SkippedTest/SkippedTestWithIssueMustGetSkippedWithoutErrorExitCode.xml new file mode 100644 index 000000000..b44cd6d81 --- /dev/null +++ b/dev/tests/verification/TestModule/Test/SkippedTest/SkippedTestWithIssueMustGetSkippedWithoutErrorExitCode.xml @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="SkippedTestWithIssueMustGetSkippedWithoutErrorExitCode"> + <annotations> + <stories value="skipped"/> + <title value="skippedTest"/> + <description value=""/> + <severity value="AVERAGE"/> + <skip> + <issueId value="SkippedValue"/> + </skip> + </annotations> + <before> + <createData entity="ReplacementPerson" stepKey="createPersonParam"/> + <actionGroup ref="FunctionalActionGroup" stepKey="beforeGroup"/> + </before> + <createData entity="DefaultPerson" stepKey="createPerson"/> + <actionGroup ref="FunctionalActionGroupWithData" stepKey="actionGroupWithPersistedData1"> + <argument name="person" value="$createPerson$"/> + </actionGroup> + <after> + <actionGroup ref="FunctionalActionGroup" stepKey="afterGroup" after ="notFound"/> + </after> + </test> +</tests> diff --git a/dev/tests/verification/TestModule/Test/XmlCommentedActionGroupTest.xml b/dev/tests/verification/TestModule/Test/XmlCommentedActionGroupTest.xml index f7fbd8dd6..b464dfc4c 100644 --- a/dev/tests/verification/TestModule/Test/XmlCommentedActionGroupTest.xml +++ b/dev/tests/verification/TestModule/Test/XmlCommentedActionGroupTest.xml @@ -6,7 +6,7 @@ */ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="XmlCommentedActionGroupTest"> <annotations> <severity value="CRITICAL"/> diff --git a/dev/tests/verification/TestModule/Test/XmlCommentedTest.xml b/dev/tests/verification/TestModule/Test/XmlCommentedTest.xml index 133588872..1da6a1fc0 100644 --- a/dev/tests/verification/TestModule/Test/XmlCommentedTest.xml +++ b/dev/tests/verification/TestModule/Test/XmlCommentedTest.xml @@ -6,7 +6,7 @@ */ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <!--< > & $abc " abc ' <click stepKey="click" userInput="$$createDataHook.firstname$$" selector="#id">/--> <test name="XmlCommentedTest"> <annotations> diff --git a/dev/tests/verification/TestModule/Test/XmlDuplicateTest/BasicDupedActionTest.xml b/dev/tests/verification/TestModule/Test/XmlDuplicateTest/BasicDupedActionTest.xml new file mode 100644 index 000000000..19104dbee --- /dev/null +++ b/dev/tests/verification/TestModule/Test/XmlDuplicateTest/BasicDupedActionTest.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="BasicDupedActionTest"> + <before> + <createData entity="simpleData" stepKey="cb1"> + <requiredEntity createDataKey="simpleData2"/> + </createData> + <amOnPage stepKey="aopb1" url="1"/> + <amOnPage stepKey="aopb2" url="2"/> + </before> + <after> + <createData entity="simpleData" stepKey="ca1"> + <requiredEntity createDataKey="simpleData2"/> + </createData> + <amOnPage stepKey="aopf1" url="1"/> + <amOnPage stepKey="aopf2" url="2"/> + </after> + <createData entity="simpleData" stepKey="c1"> + <requiredEntity createDataKey="simpleData2"/> + </createData> + <amOnPage stepKey="aop1" url="1"/> + <amOnPage stepKey="aop2" url="2"/> + </test> +</tests> diff --git a/dev/tests/verification/TestModule/Test/XmlDuplicateTest.xml b/dev/tests/verification/TestModule/Test/XmlDuplicateTest/XmlDuplicateTest.xml similarity index 93% rename from dev/tests/verification/TestModule/Test/XmlDuplicateTest.xml rename to dev/tests/verification/TestModule/Test/XmlDuplicateTest/XmlDuplicateTest.xml index 937c6edba..01d7bc88c 100644 --- a/dev/tests/verification/TestModule/Test/XmlDuplicateTest.xml +++ b/dev/tests/verification/TestModule/Test/XmlDuplicateTest/XmlDuplicateTest.xml @@ -5,9 +5,8 @@ * See COPYING.txt for license details. */ --> - <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="XmlDuplicateTest"> <before> <acceptPopup stepKey="ap1"/> @@ -82,20 +81,20 @@ <doubleClick selector="1" stepKey="dblclick2"/> <dragAndDrop selector1="1" selector2="2" stepKey="dragndrop1"/> <dragAndDrop selector1="1" selector2="2" stepKey="dragndrop2"/> - <executeInSelenium function="1" stepKey="executeSelenium1"/> - <executeInSelenium function="1" stepKey="executeSelenium2"/> <executeJS function="1" stepKey="execJS1"/> <executeJS function="1" stepKey="execJS2"/> <fillField stepKey="fill1"/> <fillField stepKey="fill21"/> - <formatMoney stepKey="frmtmoney1"/> - <formatMoney stepKey="frmtmoney12"/> + <formatCurrency userInput="1234.567890" locale="de_DE" currency="EUR" stepKey="frmtmoney1"/> + <formatCurrency userInput="1234.567890" locale="en_US" currency="EUR" stepKey="frmtmoney12"/> <getData entity="1" stepKey="getdata1"/> <getData entity="1" stepKey="getdata12"/> <grabAttributeFrom selector="1" stepKey="grabattribute1"/> <grabAttributeFrom selector="1" stepKey="grabattribute12"/> <grabCookie stepKey="grabcookie1"/> <grabCookie stepKey="grabcookie12"/> + <grabCookieAttributes stepKey="grabcookieattributes1"/> + <grabCookieAttributes stepKey="grabcookieattributes12"/> <grabFromCurrentUrl stepKey="grabfromcurl1"/> <grabFromCurrentUrl stepKey="grabfromcurl12"/> <grabMultiple selector="1" stepKey="grabmulti1"/> @@ -128,10 +127,8 @@ <openNewTab stepKey="newtab12"/> <parseFloat stepKey="parsefloat1"/> <parseFloat stepKey="parsefloat12"/> - <pauseExecution stepKey="pause1"/> - <pauseExecution stepKey="pause12"/> - <performOn selector="1" function="1" stepKey="performon1"/> - <performOn selector="1" function="1" stepKey="performon12"/> + <pause stepKey="pause1"/> + <pause stepKey="pause12"/> <pressKey selector="1" stepKey="press1"/> <pressKey selector="1" stepKey="press12"/> <reloadPage stepKey="reload1"/> @@ -298,20 +295,20 @@ <doubleClick selector="1" stepKey="dblclick2"/> <dragAndDrop selector1="1" selector2="2" stepKey="dragndrop1"/> <dragAndDrop selector1="1" selector2="2" stepKey="dragndrop2"/> - <executeInSelenium function="1" stepKey="executeSelenium1"/> - <executeInSelenium function="1" stepKey="executeSelenium2"/> <executeJS function="1" stepKey="execJS1"/> <executeJS function="1" stepKey="execJS2"/> <fillField stepKey="fill1"/> <fillField stepKey="fill21"/> - <formatMoney stepKey="frmtmoney1"/> - <formatMoney stepKey="frmtmoney12"/> + <formatCurrency userInput="1234.567890" locale="de_DE" currency="EUR" stepKey="frmtmoney1"/> + <formatCurrency userInput="1234.567890" locale="en_US" currency="EUR" stepKey="frmtmoney12"/> <getData entity="1" stepKey="getdata1"/> <getData entity="1" stepKey="getdata12"/> <grabAttributeFrom selector="1" stepKey="grabattribute1"/> <grabAttributeFrom selector="1" stepKey="grabattribute12"/> <grabCookie stepKey="grabcookie1"/> <grabCookie stepKey="grabcookie12"/> + <grabCookieAttributes stepKey="grabcookieattributes1"/> + <grabCookieAttributes stepKey="grabcookieattributes12"/> <grabFromCurrentUrl stepKey="grabfromcurl1"/> <grabFromCurrentUrl stepKey="grabfromcurl12"/> <grabMultiple selector="1" stepKey="grabmulti1"/> @@ -344,10 +341,8 @@ <openNewTab stepKey="newtab12"/> <parseFloat stepKey="parsefloat1"/> <parseFloat stepKey="parsefloat12"/> - <pauseExecution stepKey="pause1"/> - <pauseExecution stepKey="pause12"/> - <performOn selector="1" function="1" stepKey="performon1"/> - <performOn selector="1" function="1" stepKey="performon12"/> + <pause stepKey="pause1"/> + <pause stepKey="pause12"/> <pressKey selector="1" stepKey="press1"/> <pressKey selector="1" stepKey="press12"/> <reloadPage stepKey="reload1"/> @@ -513,20 +508,20 @@ <doubleClick selector="1" stepKey="dblclick2"/> <dragAndDrop selector1="1" selector2="2" stepKey="dragndrop1"/> <dragAndDrop selector1="1" selector2="2" stepKey="dragndrop2"/> - <executeInSelenium function="1" stepKey="executeSelenium1"/> - <executeInSelenium function="1" stepKey="executeSelenium2"/> <executeJS function="1" stepKey="execJS1"/> <executeJS function="1" stepKey="execJS2"/> <fillField stepKey="fill1"/> <fillField stepKey="fill21"/> - <formatMoney stepKey="frmtmoney1"/> - <formatMoney stepKey="frmtmoney12"/> + <formatCurrency userInput="1234.567890" locale="de_DE" currency="EUR" stepKey="frmtmoney1"/> + <formatCurrency userInput="1234.567890" locale="en_US" currency="EUR" stepKey="frmtmoney12"/> <getData entity="1" stepKey="getdata1"/> <getData entity="1" stepKey="getdata12"/> <grabAttributeFrom selector="1" stepKey="grabattribute1"/> <grabAttributeFrom selector="1" stepKey="grabattribute12"/> <grabCookie stepKey="grabcookie1"/> <grabCookie stepKey="grabcookie12"/> + <grabCookieAttributes stepKey="grabcookieattributes1"/> + <grabCookieAttributes stepKey="grabcookieattributes12"/> <grabFromCurrentUrl stepKey="grabfromcurl1"/> <grabFromCurrentUrl stepKey="grabfromcurl12"/> <grabMultiple selector="1" stepKey="grabmulti1"/> @@ -559,10 +554,8 @@ <openNewTab stepKey="newtab12"/> <parseFloat stepKey="parsefloat1"/> <parseFloat stepKey="parsefloat12"/> - <pauseExecution stepKey="pause1"/> - <pauseExecution stepKey="pause12"/> - <performOn selector="1" function="1" stepKey="performon1"/> - <performOn selector="1" function="1" stepKey="performon12"/> + <pause stepKey="pause1"/> + <pause stepKey="pause12"/> <pressKey selector="1" stepKey="press1"/> <pressKey selector="1" stepKey="press12"/> <reloadPage stepKey="reload1"/> @@ -656,26 +649,4 @@ <waitForText stepKey="waittext1"/> <waitForText stepKey="waittext12"/> </test> - - <test name="BasicDupedActionTest"> - <before> - <createData entity="simpleData" stepKey="cb1"> - <requiredEntity createDataKey="simpleData2"/> - </createData> - <amOnPage stepKey="aopb1" url="1"/> - <amOnPage stepKey="aopb2" url="2"/> - </before> - <after> - <createData entity="simpleData" stepKey="ca1"> - <requiredEntity createDataKey="simpleData2"/> - </createData> - <amOnPage stepKey="aopf1" url="1"/> - <amOnPage stepKey="aopf2" url="2"/> - </after> - <createData entity="simpleData" stepKey="c1"> - <requiredEntity createDataKey="simpleData2"/> - </createData> - <amOnPage stepKey="aop1" url="1"/> - <amOnPage stepKey="aop2" url="2"/> - </test> -</tests> \ No newline at end of file +</tests> diff --git a/dev/tests/verification/TestModuleMerged/Data/MergeData.xml b/dev/tests/verification/TestModuleMerged/Data/MergeData.xml index 72d0dca61..531922aed 100644 --- a/dev/tests/verification/TestModuleMerged/Data/MergeData.xml +++ b/dev/tests/verification/TestModuleMerged/Data/MergeData.xml @@ -8,7 +8,7 @@ --> <entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> <entity name="DefaultPerson" type="samplePerson"> <data key="mergedField">merged</data> <data key="newField">newField</data> diff --git a/dev/tests/verification/TestModuleMerged/Section/MergeSection.xml b/dev/tests/verification/TestModuleMerged/Section/MergeSection.xml index fb661e906..6bbd862ae 100644 --- a/dev/tests/verification/TestModuleMerged/Section/MergeSection.xml +++ b/dev/tests/verification/TestModuleMerged/Section/MergeSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="SampleSection"> <element name="oneParamElement" type="button" selector="#element .{{var1}}" parameterized="true"/> <element name="twoParamElement" type="button" selector="#{{var1}} .{{var2}}" parameterized="true"/> diff --git a/dev/tests/verification/TestModuleMerged/Test/MergeFunctionalTest.xml b/dev/tests/verification/TestModuleMerged/Test/MergeFunctionalTest/BasicMergeTest.xml similarity index 79% rename from dev/tests/verification/TestModuleMerged/Test/MergeFunctionalTest.xml rename to dev/tests/verification/TestModuleMerged/Test/MergeFunctionalTest/BasicMergeTest.xml index 912854cc4..807258b0f 100644 --- a/dev/tests/verification/TestModuleMerged/Test/MergeFunctionalTest.xml +++ b/dev/tests/verification/TestModuleMerged/Test/MergeFunctionalTest/BasicMergeTest.xml @@ -5,9 +5,8 @@ * See COPYING.txt for license details. */ --> - <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="BasicMergeTest"> <annotations> <group value="mergeTest"/> @@ -26,11 +25,4 @@ <click stepKey="step10" selector="#step10MergedInResult"/> <actionGroup ref="FunctionalActionGroupWithData" stepKey="step8Merge" after="step7Merge"/> </test> - <test name="MergeSkip"> - <annotations> - <skip> - <issueId value="Issue5"/> - </skip> - </annotations> - </test> </tests> diff --git a/dev/tests/verification/TestModuleMerged/Test/MergeFunctionalTest/MergeSkip.xml b/dev/tests/verification/TestModuleMerged/Test/MergeFunctionalTest/MergeSkip.xml new file mode 100644 index 000000000..5c9e189c3 --- /dev/null +++ b/dev/tests/verification/TestModuleMerged/Test/MergeFunctionalTest/MergeSkip.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="MergeSkip"> + <annotations> + <skip> + <issueId value="Issue5"/> + </skip> + </annotations> + </test> +</tests> diff --git a/dev/tests/verification/TestModuleMerged/Test/MergeXmlDuplicateTest.xml b/dev/tests/verification/TestModuleMerged/Test/MergeXmlDuplicateTest.xml index 2d99d5cbc..c9c731134 100644 --- a/dev/tests/verification/TestModuleMerged/Test/MergeXmlDuplicateTest.xml +++ b/dev/tests/verification/TestModuleMerged/Test/MergeXmlDuplicateTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="BasicDupedActionTest"> <before> <createData entity="simpleData" stepKey="cb3"> diff --git a/dev/tests/verification/Tests/ActionGroupWithReturnGenerationTest.php b/dev/tests/verification/Tests/ActionGroupWithReturnGenerationTest.php new file mode 100644 index 000000000..e369a797d --- /dev/null +++ b/dev/tests/verification/Tests/ActionGroupWithReturnGenerationTest.php @@ -0,0 +1,52 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace tests\verification\Tests; + +use tests\util\MftfTestCase; + +class ActionGroupWithReturnGenerationTest extends MftfTestCase +{ + /** + * Test generation of a test referencing an action group that returns a value. + * + * @throws \Exception + * @throws \Magento\FunctionalTestingFramework\Exceptions\TestReferenceException + */ + public function testActionGroupReturningValue() + { + $this->generateAndCompareTest('ActionGroupReturningValueTest'); + } + /** + * Test generation of a test referencing a merged action group that returns a value. + * + * @throws \Exception + * @throws \Magento\FunctionalTestingFramework\Exceptions\TestReferenceException + */ + public function testMergedActionGroupReturningValue() + { + $this->generateAndCompareTest('MergedActionGroupReturningValueTest'); + } + /** + * Test generation of a test referencing an extended action group that returns a value. + * + * @throws \Exception + * @throws \Magento\FunctionalTestingFramework\Exceptions\TestReferenceException + */ + public function testExtendedActionGroupReturningValue() + { + $this->generateAndCompareTest('ExtendedActionGroupReturningValueTest'); + } + /** + * Test generation of a test referencing an extending child action group that returns a value. + * + * @throws \Exception + * @throws \Magento\FunctionalTestingFramework\Exceptions\TestReferenceException + */ + public function testExtendedChildActionGroupReturningValue() + { + $this->generateAndCompareTest('ExtendedChildActionGroupReturningValueTest'); + } +} diff --git a/dev/tests/verification/Tests/BasicCestGenerationTest.php b/dev/tests/verification/Tests/BasicCestGenerationTest.php index 793b2a502..39802c50d 100644 --- a/dev/tests/verification/Tests/BasicCestGenerationTest.php +++ b/dev/tests/verification/Tests/BasicCestGenerationTest.php @@ -10,6 +10,7 @@ class BasicCestGenerationTest extends MftfTestCase { /** + * BasicFunctionalTest: * Tests flat generation of a hardcoded test file with no external references. * * @throws \Exception @@ -20,6 +21,30 @@ public function testBasicGeneration() $this->generateAndCompareTest('BasicFunctionalTest'); } + /** + * MergeMassViaInsertAfter: + * Tests flat generation of a hardcoded test file with no external references. + * + * @throws \Exception + * @throws \Magento\FunctionalTestingFramework\Exceptions\TestReferenceException + */ + public function testMergeMassViaInsertAfter() + { + $this->generateAndCompareTest('MergeMassViaInsertAfter'); + } + + /** + * MergeMassViaInsertBefore: + * Tests flat generation of a hardcoded test file with no external references. + * + * @throws \Exception + * @throws \Magento\FunctionalTestingFramework\Exceptions\TestReferenceException + */ + public function testMergeMassViaInsertBefore() + { + $this->generateAndCompareTest('MergeMassViaInsertBefore'); + } + /** * Tests flat generation of a hardcoded test file with no external references and with XML comments in: * - root `tests` element diff --git a/dev/tests/verification/Tests/DeprecatedTest.php b/dev/tests/verification/Tests/DeprecatedTest.php new file mode 100644 index 000000000..78e3326b9 --- /dev/null +++ b/dev/tests/verification/Tests/DeprecatedTest.php @@ -0,0 +1,33 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace tests\verification\Tests; + +use tests\util\MftfTestCase; + +class DeprecatedTest extends MftfTestCase +{ + /** + * Tests flat generation of a deprecated test which uses deprecated entities. + * + * @throws \Exception + * @throws \Magento\FunctionalTestingFramework\Exceptions\TestReferenceException + */ + public function testDeprecatedTestEntitiesGeneration() + { + $this->generateAndCompareTest('DeprecatedTest'); + } + + /** + * Tests flat generation of a test which uses deprecated entities. + * + * @throws \Exception + * @throws \Magento\FunctionalTestingFramework\Exceptions\TestReferenceException + */ + public function testDeprecatedEntitiesOnlyGeneration() + { + $this->generateAndCompareTest('DeprecatedEntitiesTest'); + } +} diff --git a/dev/tests/verification/Tests/ExecuteInSeleniumTest.php b/dev/tests/verification/Tests/GroupSkipGenerationTest.php similarity index 60% rename from dev/tests/verification/Tests/ExecuteInSeleniumTest.php rename to dev/tests/verification/Tests/GroupSkipGenerationTest.php index 36532b362..ad0ba6ec0 100644 --- a/dev/tests/verification/Tests/ExecuteInSeleniumTest.php +++ b/dev/tests/verification/Tests/GroupSkipGenerationTest.php @@ -7,16 +7,16 @@ use tests\util\MftfTestCase; -class ExecuteInSeleniumTest extends MftfTestCase +class GroupSkipGenerationTest extends MftfTestCase { /** - * Tests generation of executeInSelenium action. + * Tests group skip test generation * * @throws \Exception * @throws \Magento\FunctionalTestingFramework\Exceptions\TestReferenceException */ - public function testExecuteInSeleniumTest() + public function testGroupSkipGenerationTest() { - $this->generateAndCompareTest('ExecuteInSeleniumTest'); + $this->generateAndCompareTest('GroupSkipGenerationTest'); } } diff --git a/dev/tests/verification/Tests/ResilientGenerationTest.php b/dev/tests/verification/Tests/ResilientGenerationTest.php new file mode 100644 index 000000000..cbd032ed2 --- /dev/null +++ b/dev/tests/verification/Tests/ResilientGenerationTest.php @@ -0,0 +1,286 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace tests\verification\Tests; + +use Magento\FunctionalTestingFramework\Suite\Handlers\SuiteObjectHandler; +use Magento\FunctionalTestingFramework\Suite\SuiteGenerator; +use Magento\FunctionalTestingFramework\Test\Handlers\TestObjectHandler; +use Magento\FunctionalTestingFramework\Util\Filesystem\DirSetupUtil; +use Magento\FunctionalTestingFramework\Util\Manifest\TestManifestFactory; +use Magento\FunctionalTestingFramework\Util\TestGenerator; +use Symfony\Component\Yaml\Yaml; +use tests\unit\Util\TestLoggingUtil; +use tests\util\MftfTestCase; + +class ResilientGenerationTest extends MftfTestCase +{ + const RESOURCES_DIR = TESTS_BP . DIRECTORY_SEPARATOR . 'verification' . DIRECTORY_SEPARATOR . 'Resources'; + const CONFIG_YML_FILE = TESTS_BP . DIRECTORY_SEPARATOR . SuiteGenerator::YAML_CODECEPTION_CONFIG_FILENAME; + const GENERATE_RESULT_DIR = TESTS_BP . + DIRECTORY_SEPARATOR . + "verification" . + DIRECTORY_SEPARATOR . + "_generated" . + DIRECTORY_SEPARATOR; + + /** + * Exception group names and log messages + * + * @var string[][] + */ + private static $exceptionGrpLogs = [ + 'NotGenerateHookBeforeSuite' => [ + '/Suite NotGenerateHookBeforeSuite is not defined in xml or is invalid./' + ], + 'NotGenerateHookAfterSuite' => [ + '/Suite NotGenerateHookAfterSuite is not defined in xml or is invalid./' + ], + 'NotGenerateEmptySuite' => [ + '/Suite NotGenerateEmptySuite is not defined in xml or is invalid./' + ], + ]; + + /** + * Set up for testing + */ + public static function setUpBeforeClass(): void + { + // destroy _generated if it exists + if (file_exists(self::GENERATE_RESULT_DIR)) { + DirSetupUtil::rmdirRecursive(self::GENERATE_RESULT_DIR); + } + } + + public function setUp(): void + { + // copy config yml file to test dir + $fileSystem = new \Symfony\Component\Filesystem\Filesystem(); + $fileSystem->copy( + realpath( + FW_BP + . DIRECTORY_SEPARATOR + . 'etc' + . DIRECTORY_SEPARATOR + . 'config' + . DIRECTORY_SEPARATOR + . 'codeception.dist.yml' + ), + self::CONFIG_YML_FILE + ); + + TestLoggingUtil::getInstance()->setMockLoggingUtil(); + + $property = new \ReflectionProperty(SuiteGenerator::class, "instance"); + $property->setAccessible(true); + $property->setValue(null); + + $property = new \ReflectionProperty(DirSetupUtil::class, "DIR_CONTEXT"); + $property->setAccessible(true); + $property->setValue([]); + + $property = new \ReflectionProperty(SuiteObjectHandler::class, "instance"); + $property->setAccessible(true); + $property->setValue(null); + + $property = new \ReflectionProperty(TestObjectHandler::class, "testObjectHandler"); + $property->setAccessible(true); + $property->setValue(null); + } + + /** + * Test resilient generate all tests + */ + public function testGenerateAllTests() + { + $testManifest = TestManifestFactory::makeManifest('default', []); + TestGenerator::getInstance(null, [])->createAllTestFiles($testManifest); + + // Validate tests have been generated + $dirContents = array_diff( + scandir(self::GENERATE_RESULT_DIR . DIRECTORY_SEPARATOR . 'default'), + ['..', '.'] + ); + + foreach ($dirContents as $dirContent) { + $this->assertStringStartsNotWith( + 'NotGenerate', + $dirContent, + "string {$dirContent} should not contains \"NotGenerate\"" + ); + } + } + + /** + * Test resilient generate all suites + */ + public function testGenerateAllSuites() + { + $testManifest = TestManifestFactory::makeManifest('', []); + SuiteGenerator::getInstance()->generateAllSuites($testManifest); + + foreach (SuiteTestReferences::$data as $groupName => $expectedContents) { + if (substr($groupName, 0, 11) !== 'NotGenerate') { + // Validate Yaml file updated + $yml = Yaml::parse(file_get_contents(self::CONFIG_YML_FILE)); + $this->assertArrayHasKey($groupName, $yml['groups']); + + $suiteResultBaseDir = self::GENERATE_RESULT_DIR . + DIRECTORY_SEPARATOR . + $groupName . + DIRECTORY_SEPARATOR; + + // Validate suite and tests have been generated + $dirContents = array_diff(scandir($suiteResultBaseDir), ['..', '.']); + + foreach ($expectedContents as $expectedFile) { + $this->assertTrue(in_array($expectedFile, $dirContents)); + } + } else { + $dirContents = array_diff(scandir(self::GENERATE_RESULT_DIR), ['..', '.']); + + // Validate suite is not generated + $this->assertFalse(in_array($groupName, $dirContents)); + } + } + } + + /** + * Test resilient generate some suites + */ + public function testGenerateSomeSuites() + { + $suites = [ + 'PartialGenerateForIncludeSuite', + 'PartialGenerateNoExcludeSuite', + 'NotGenerateHookBeforeSuite', + 'NotGenerateHookAfterSuite', + ]; + + foreach ($suites as $groupName) { + $expectedContents = SuiteTestReferences::$data[$groupName]; + if (!in_array($groupName, array_keys(self::$exceptionGrpLogs))) { + SuiteGenerator::getInstance()->generateSuite($groupName); + } else { + // Validate exception is thrown + $this->assertExceptionRegex( + \Exception::class, + self::$exceptionGrpLogs[$groupName], + function () use ($groupName) { + SuiteGenerator::getInstance()->generateSuite($groupName); + } + ); + } + + if (substr($groupName, 0, 11) !== 'NotGenerate') { + // Validate Yaml file updated + $yml = Yaml::parse(file_get_contents(self::CONFIG_YML_FILE)); + $this->assertArrayHasKey($groupName, $yml['groups']); + + $suiteResultBaseDir = self::GENERATE_RESULT_DIR . + DIRECTORY_SEPARATOR . + $groupName . + DIRECTORY_SEPARATOR; + + // Validate tests have been generated + $dirContents = array_diff(scandir($suiteResultBaseDir), ['..', '.']); + + foreach ($expectedContents as $expectedFile) { + $this->assertTrue(in_array($expectedFile, $dirContents)); + } + } else { + $dirContents = array_diff(scandir(self::GENERATE_RESULT_DIR), ['..', '.']); + + // Validate suite is not generated + $this->assertFalse(in_array($groupName, $dirContents)); + } + + // Validate log message + if (substr($groupName, 0, 11) !== 'NotGenerate' + && !in_array($groupName, array_keys(self::$exceptionGrpLogs))) { + $type = 'info'; + $message = '/suite generated/'; + $context = [ + 'suite' => $groupName, + 'relative_path' => "_generated" . DIRECTORY_SEPARATOR . $groupName + ]; + } else { + $type = 'error'; + $message = self::$exceptionGrpLogs[$groupName][0]; + $context = []; + } + TestLoggingUtil::getInstance()->validateMockLogStatmentRegex($type, $message, $context); + } + } + + /** + * Test generate an empty suite + */ + public function testGenerateEmptySuites() + { + $groupName = 'NotGenerateEmptySuite'; + + // Validate exception is thrown + $this->assertExceptionRegex( + \Exception::class, + self::$exceptionGrpLogs[$groupName], + function () use ($groupName) { + SuiteGenerator::getInstance()->generateSuite($groupName); + } + ); + + // Validate suite is not generated + $dirContents = array_diff(scandir(self::GENERATE_RESULT_DIR), ['..', '.']); + $this->assertFalse(in_array($groupName, $dirContents)); + + // Validate log message + $type = 'error'; + $message = self::$exceptionGrpLogs[$groupName][0]; + $context = []; + + TestLoggingUtil::getInstance()->validateMockLogStatmentRegex($type, $message, $context); + } + + /** + * + * revert any changes made to config.yml + * remove _generated directory + */ + public function tearDown(): void + { + DirSetupUtil::rmdirRecursive(self::GENERATE_RESULT_DIR); + + // delete config yml file from test dir + $fileSystem = new \Symfony\Component\Filesystem\Filesystem(); + $fileSystem->remove( + self::CONFIG_YML_FILE + ); + + $property = new \ReflectionProperty(SuiteGenerator::class, "instance"); + $property->setAccessible(true); + $property->setValue(null); + + $property = new \ReflectionProperty(DirSetupUtil::class, "DIR_CONTEXT"); + $property->setAccessible(true); + $property->setValue([]); + + $property = new \ReflectionProperty(SuiteObjectHandler::class, "instance"); + $property->setAccessible(true); + $property->setValue(null); + + $property = new \ReflectionProperty(TestObjectHandler::class, "testObjectHandler"); + $property->setAccessible(true); + $property->setValue(null); + } + + /** + * Remove yml if created during tests and did not exist before + */ + public static function tearDownAfterClass(): void + { + TestLoggingUtil::getInstance()->clearMockLoggingUtil(); + } +} diff --git a/dev/tests/verification/Tests/SchemaValidationTest.php b/dev/tests/verification/Tests/SchemaValidationTest.php index 92442bd37..e4e5aea40 100644 --- a/dev/tests/verification/Tests/SchemaValidationTest.php +++ b/dev/tests/verification/Tests/SchemaValidationTest.php @@ -6,8 +6,8 @@ namespace tests\verification\Tests; use Magento\FunctionalTestingFramework\Config\MftfApplicationConfig; +use ReflectionProperty; use tests\util\MftfTestCase; -use AspectMock\Test as AspectMock; class SchemaValidationTest extends MftfTestCase { @@ -19,7 +19,11 @@ class SchemaValidationTest extends MftfTestCase */ public function testInvalidTestSchema() { - AspectMock::double(MftfApplicationConfig::class, ['getDebugLevel' => MftfApplicationConfig::LEVEL_DEVELOPER]); + $config = MftfApplicationConfig::getConfig(); + $property = new ReflectionProperty(MftfApplicationConfig::class, 'debugLevel'); + $property->setAccessible(true); + $property->setValue($config, MftfApplicationConfig::LEVEL_DEVELOPER); + $testFile = ['testFile.xml' => "<tests><test name='testName'><annotations>a</annotations></test></tests>"]; $expectedError = TESTS_MODULE_PATH . DIRECTORY_SEPARATOR . @@ -32,11 +36,13 @@ public function testInvalidTestSchema() } /** - * After method functionality - * @return void + * @inheritdoc */ - protected function tearDown() + protected function tearDown(): void { - AspectMock::clean(); + $config = MftfApplicationConfig::getConfig(); + $property = new ReflectionProperty(MftfApplicationConfig::class, 'debugLevel'); + $property->setAccessible(true); + $property->setValue($config, MftfApplicationConfig::LEVEL_DEFAULT); } } diff --git a/dev/tests/verification/Tests/SecretCredentialDataTest.php b/dev/tests/verification/Tests/SecretCredentialDataTest.php index 46391feee..351a3d800 100644 --- a/dev/tests/verification/Tests/SecretCredentialDataTest.php +++ b/dev/tests/verification/Tests/SecretCredentialDataTest.php @@ -25,7 +25,6 @@ class SecretCredentialDataTestCest { /** * @Features({"AdminNotification"}) - * @Parameter(name = "AcceptanceTester", value="$I") * @param AcceptanceTester $I * @return void * @throws \Exception @@ -37,7 +36,7 @@ public function secretCredentialDataTest(AcceptanceTester $I) $createProductWithFieldOverridesUsingHardcodedData1Fields['price'] = "12.34"; $I->comment("[createProductWithFieldOverridesUsingHardcodedData1] create '_defaultProduct' entity"); - PersistedObjectHandler::getInstance()->createEntity( + $I->createEntity( "createProductWithFieldOverridesUsingHardcodedData1", "test", "_defaultProduct", @@ -46,13 +45,13 @@ public function secretCredentialDataTest(AcceptanceTester $I) ); $createProductWithFieldOverridesUsingSecretCredData1Fields['qty'] = - CredentialStore::getInstance()->getSecret("payment_authorizenet_trans_key"); + $I->getSecret("payment_authorizenet_trans_key"); $createProductWithFieldOverridesUsingSecretCredData1Fields['price'] = - CredentialStore::getInstance()->getSecret("carriers_dhl_account_eu"); + $I->getSecret("carriers_dhl_account_eu"); $I->comment("[createProductWithFieldOverridesUsingSecretCredData1] create '_defaultProduct' entity"); - PersistedObjectHandler::getInstance()->createEntity( + $I->createEntity( "createProductWithFieldOverridesUsingSecretCredData1", "test", "_defaultProduct", @@ -61,14 +60,14 @@ public function secretCredentialDataTest(AcceptanceTester $I) ); $I->fillField("#username", "Hardcoded"); // stepKey: fillFieldUsingHardCodedData1 - $I->fillSecretField("#username", CredentialStore::getInstance()->getSecret("carriers_dhl_id_eu")); + $I->fillSecretField("#username", $I->getSecret("carriers_dhl_id_eu")); // stepKey: fillFieldUsingSecretCredData1 $magentoCliUsingHardcodedData1 = $I->magentoCLI("config:set cms/wysiwyg/enabled 0"); // stepKey: magentoCliUsingHardcodedData1 $I->comment($magentoCliUsingHardcodedData1); $magentoCliUsingSecretCredData1 = $I->magentoCLI("config:set cms/wysiwyg/enabled " . - CredentialStore::getInstance()->getSecret("payment_authorizenet_login")); + $I->getSecret("payment_authorizenet_login")); // stepKey: magentoCliUsingSecretCredData1 $I->comment($magentoCliUsingSecretCredData1); } diff --git a/dev/tests/verification/Tests/SkippedGenerationTest.php b/dev/tests/verification/Tests/SkippedGenerationTest.php index 2d259e02c..b2d1c8fdc 100644 --- a/dev/tests/verification/Tests/SkippedGenerationTest.php +++ b/dev/tests/verification/Tests/SkippedGenerationTest.php @@ -43,13 +43,13 @@ public function testMultipleSkippedIssuesGeneration() } /** - * Tests skipped test generation with no specified issues. Will be deprecated after MFTF 3.0.0 + * Tests skipped test doesnt fail to generate when there is issue in test * * @throws \Exception * @throws \Magento\FunctionalTestingFramework\Exceptions\TestReferenceException */ - public function testSkippedNoIssueGeneration() + public function testSkippedTestMustNotFailToGenerateWithErrorWhenThereIsIssueWithAnyOfTheStepsAsTheTestIsSkipped() { - $this->generateAndCompareTest('SkippedTestNoIssues'); + $this->generateAndCompareTest('SkippedTestWithIssueMustGetSkippedWithoutErrorExitCode'); } } diff --git a/dev/tests/verification/Tests/StaticCheck/DeprecationStaticCheckTest.php b/dev/tests/verification/Tests/StaticCheck/DeprecationStaticCheckTest.php new file mode 100644 index 000000000..58875d660 --- /dev/null +++ b/dev/tests/verification/Tests/StaticCheck/DeprecationStaticCheckTest.php @@ -0,0 +1,62 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace tests\verification\Tests; + +use Magento\FunctionalTestingFramework\StaticCheck\DeprecatedEntityUsageCheck; +use Magento\FunctionalTestingFramework\StaticCheck\StaticChecksList; +use ReflectionProperty; +use Symfony\Component\Console\Input\InputInterface; +use tests\util\MftfStaticTestCase; + +class DeprecationStaticCheckTest extends MftfStaticTestCase +{ + const LOG_FILE = self::STATIC_RESULTS_DIR . + DIRECTORY_SEPARATOR . + DeprecatedEntityUsageCheck::ERROR_LOG_FILENAME . + '.txt'; + + const TEST_MODULE_PATH = TESTS_MODULE_PATH . + DIRECTORY_SEPARATOR . + 'DeprecationCheckModule'. + DIRECTORY_SEPARATOR; + + /** + * test static-check DeprecatedEntityUsageCheck. + * + * @throws \Exception + */ + public function testDeprecatedEntityUsageCheck() + { + $staticCheck = new DeprecatedEntityUsageCheck(); + + $input = $this->mockInputInterface(self::TEST_MODULE_PATH); + $property = new ReflectionProperty(StaticChecksList::class, 'errorFilesPath'); + $property->setAccessible(true); + $property->setValue(self::STATIC_RESULTS_DIR); + + /** @var InputInterface $input */ + $staticCheck->execute($input); + + $this->assertTrue(file_exists(self::LOG_FILE)); + $this->assertFileEquals( + self::RESOURCES_PATH. + DIRECTORY_SEPARATOR . + DeprecatedEntityUsageCheck::ERROR_LOG_FILENAME . + ".txt", + self::LOG_FILE + ); + } + + /** + * @inheritdoc + */ + public function tearDown(): void + { + $property = new ReflectionProperty(StaticChecksList::class, 'errorFilesPath'); + $property->setAccessible(true); + $property->setValue(null); + } +} diff --git a/dev/tests/verification/Tests/StaticCheck/PauseActionStaticCheckTest.php b/dev/tests/verification/Tests/StaticCheck/PauseActionStaticCheckTest.php new file mode 100644 index 000000000..4c579b6c0 --- /dev/null +++ b/dev/tests/verification/Tests/StaticCheck/PauseActionStaticCheckTest.php @@ -0,0 +1,64 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace tests\verification\Tests; + +use Exception; +use Magento\FunctionalTestingFramework\StaticCheck\PauseActionUsageCheck; +use Magento\FunctionalTestingFramework\StaticCheck\StaticChecksList; +use ReflectionProperty; +use Symfony\Component\Console\Input\InputInterface; +use tests\util\MftfStaticTestCase; + +class PauseActionStaticCheckTest extends MftfStaticTestCase +{ + const LOG_FILE = self::STATIC_RESULTS_DIR . + DIRECTORY_SEPARATOR . + PauseActionUsageCheck::ERROR_LOG_FILENAME . + '.txt'; + + const TEST_MODULE_PATH = TESTS_MODULE_PATH . + DIRECTORY_SEPARATOR . + 'PauseCheckModule'. + DIRECTORY_SEPARATOR; + + /** + * test static-check PauseActionUsageCheck. + * + * @throws Exception + */ + public function testPauseActionUsageCheck() + { + $staticCheck = new PauseActionUsageCheck(); + + $input = $this->mockInputInterface(self::TEST_MODULE_PATH); + + $property = new ReflectionProperty(StaticChecksList::class, 'errorFilesPath'); + $property->setAccessible(true); + $property->setValue(self::STATIC_RESULTS_DIR); + + /** @var InputInterface $input */ + $staticCheck->execute($input); + + $this->assertTrue(file_exists(self::LOG_FILE)); + $this->assertFileEquals( + self::RESOURCES_PATH. + DIRECTORY_SEPARATOR . + PauseActionUsageCheck::ERROR_LOG_FILENAME . + ".txt", + self::LOG_FILE + ); + } + + /** + * @inheritdoc + */ + public static function tearDownAfterClass(): void + { + $property = new ReflectionProperty(StaticChecksList::class, 'errorFilesPath'); + $property->setAccessible(true); + $property->setValue(null); + } +} diff --git a/dev/tests/verification/Tests/SuiteGenerationTest.php b/dev/tests/verification/Tests/SuiteGenerationTest.php index d2242fb52..85fbbacc9 100644 --- a/dev/tests/verification/Tests/SuiteGenerationTest.php +++ b/dev/tests/verification/Tests/SuiteGenerationTest.php @@ -6,12 +6,14 @@ namespace tests\verification\Tests; +use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; use Magento\FunctionalTestingFramework\Suite\SuiteGenerator; use Magento\FunctionalTestingFramework\Util\Filesystem\DirSetupUtil; use Magento\FunctionalTestingFramework\Util\Manifest\DefaultTestManifest; -use Magento\FunctionalTestingFramework\Util\Manifest\ParallelTestManifest; +use Magento\FunctionalTestingFramework\Util\Manifest\ParallelByTimeTestManifest; +use Magento\FunctionalTestingFramework\Util\Manifest\ParallelByGroupTestManifest; use Magento\FunctionalTestingFramework\Util\Manifest\TestManifestFactory; -use PHPUnit\Util\Filesystem; +use Magento\FunctionalTestingFramework\Util\Path\FilePathFormatter; use Symfony\Component\Yaml\Yaml; use tests\unit\Util\TestLoggingUtil; use tests\util\MftfTestCase; @@ -37,7 +39,7 @@ class SuiteGenerationTest extends MftfTestCase /** * Set up config.yml for testing */ - public static function setUpBeforeClass() + public static function setUpBeforeClass(): void { // destroy _generated if it exists if (file_exists(self::GENERATE_RESULT_DIR)) { @@ -45,7 +47,7 @@ public static function setUpBeforeClass() } } - public function setUp() + public function setUp(): void { // copy config yml file to test dir $fileSystem = new \Symfony\Component\Filesystem\Filesystem(); @@ -72,12 +74,7 @@ public function testSuiteGeneration1() { $groupName = 'functionalSuite1'; - $expectedContents = [ - 'additionalTestCest.php', - 'additionalIncludeTest2Cest.php', - 'IncludeTest2Cest.php', - 'IncludeTestCest.php' - ]; + $expectedContents = SuiteTestReferences::$data[$groupName]; // Generate the Suite SuiteGenerator::getInstance()->generateSuite($groupName); @@ -111,27 +108,22 @@ public function testSuiteGeneration1() /** * Test generation of parallel suite groups */ - public function testSuiteGenerationParallel() + public function testSuiteGenerationParallelByTime() { $groupName = 'functionalSuite1'; $expectedGroups = [ - 'functionalSuite1_0', - 'functionalSuite1_1', - 'functionalSuite1_2', - 'functionalSuite1_3' + 'functionalSuite1_0_G', + 'functionalSuite1_1_G', + 'functionalSuite1_2_G', + 'functionalSuite1_3_G' ]; - $expectedContents = [ - 'additionalTestCest.php', - 'additionalIncludeTest2Cest.php', - 'IncludeTest2Cest.php', - 'IncludeTestCest.php' - ]; + $expectedContents = SuiteTestReferences::$data[$groupName]; //createParallelManifest - /** @var ParallelTestManifest $parallelManifest */ - $parallelManifest = TestManifestFactory::makeManifest("parallel", ["functionalSuite1" => []]); + /** @var ParallelByTimeTestManifest $parallelManifest */ + $parallelManifest = TestManifestFactory::makeManifest("parallelByTime", ["functionalSuite1" => []]); // Generate the Suite $parallelManifest->createTestGroups(1); @@ -166,6 +158,57 @@ public function testSuiteGenerationParallel() } } + /** + * Test generation of parallel suite groups + */ + public function testSuiteGenerationParallelByGroup() + { + $groupName = 'functionalSuite1'; + + $expectedGroups = [ + 'functionalSuite1_0_G', + 'functionalSuite1_1_G', + ]; + + $expectedContents = SuiteTestReferences::$data[$groupName]; + + //createParallelManifest + /** @var ParallelByGroupTestManifest $parallelManifest */ + $parallelManifest = TestManifestFactory::makeManifest("parallelByGroup", ["functionalSuite1" => []]); + + // Generate the Suite + $parallelManifest->createTestGroups(2); + SuiteGenerator::getInstance()->generateAllSuites($parallelManifest); + + // Validate log message (for final group) and add group name for later deletion + $expectedGroup = $expectedGroups[count($expectedGroups)-1] ; + TestLoggingUtil::getInstance()->validateMockLogStatement( + 'info', + "suite generated", + ['suite' => $expectedGroup, 'relative_path' => "_generated" . DIRECTORY_SEPARATOR . $expectedGroup] + ); + + self::$TEST_GROUPS[] = $groupName; + + // Validate Yaml file updated + $yml = Yaml::parse(file_get_contents(self::CONFIG_YML_FILE)); + $this->assertEquals(array_intersect($expectedGroups, array_keys($yml['groups'])), $expectedGroups); + + foreach ($expectedGroups as $expectedFolder) { + $suiteResultBaseDir = self::GENERATE_RESULT_DIR . + DIRECTORY_SEPARATOR . + $expectedFolder . + DIRECTORY_SEPARATOR; + + // Validate tests have been generated + $dirContents = array_diff(scandir($suiteResultBaseDir), ['..', '.']); + + //Validate two test has been added to each group since lines are set to 1 + $this->assertEquals(2, count($dirContents)); + $this->assertContains(array_values($dirContents)[0], $expectedContents); + } + } + /** * Test hook groups generated during suite generation */ @@ -173,9 +216,7 @@ public function testSuiteGenerationHooks() { $groupName = 'functionalSuiteHooks'; - $expectedContents = [ - 'IncludeTestCest.php' - ]; + $expectedContents = SuiteTestReferences::$data[$groupName]; // Generate the Suite SuiteGenerator::getInstance()->generateSuite($groupName); @@ -233,12 +274,7 @@ public function testSuiteGenerationSingleRun() //using functionalSuite2 to avoid directory caching $groupName = 'functionalSuite2'; - $expectedContents = [ - 'additionalTestCest.php', - 'additionalIncludeTest2Cest.php', - 'IncludeTest2Cest.php', - 'IncludeTestCest.php' - ]; + $expectedContents = SuiteTestReferences::$data[$groupName]; //createParallelManifest /** @var DefaultTestManifest $parallelManifest */ @@ -292,9 +328,7 @@ public function testSuiteGenerationWithExtends() { $groupName = 'suiteExtends'; - $expectedFileNames = [ - 'ExtendedChildTestInSuiteCest' - ]; + $expectedFileNames = SuiteTestReferences::$data[$groupName]; // Generate the Suite SuiteGenerator::getInstance()->generateSuite($groupName); @@ -319,10 +353,12 @@ public function testSuiteGenerationWithExtends() $dirContents = array_diff(scandir($suiteResultBaseDir), ['..', '.']); foreach ($expectedFileNames as $expectedFileName) { - $this->assertTrue(in_array($expectedFileName . ".php", $dirContents)); + $this->assertTrue(in_array($expectedFileName, $dirContents)); $this->assertFileEquals( - self::RESOURCES_PATH . DIRECTORY_SEPARATOR . $expectedFileName . ".txt", - $suiteResultBaseDir . $expectedFileName . ".php" + self::RESOURCES_PATH . DIRECTORY_SEPARATOR + . substr($expectedFileName, 0, strlen($expectedFileName)-4) + . ".txt", + $suiteResultBaseDir . $expectedFileName ); } } @@ -334,9 +370,64 @@ public function testSuiteCommentsGeneration() { $groupName = 'functionalSuiteWithComments'; - $expectedContents = [ - 'IncludeTestCest.php' - ]; + $expectedContents = SuiteTestReferences::$data[$groupName]; + + // Generate the Suite + SuiteGenerator::getInstance()->generateSuite($groupName); + + // Validate log message and add group name for later deletion + TestLoggingUtil::getInstance()->validateMockLogStatement( + 'info', + "suite generated", + ['suite' => $groupName, 'relative_path' => "_generated" . DIRECTORY_SEPARATOR . $groupName] + ); + self::$TEST_GROUPS[] = $groupName; + + // Validate Yaml file updated + $yml = Yaml::parse(file_get_contents(self::CONFIG_YML_FILE)); + $this->assertArrayHasKey($groupName, $yml['groups']); + + $suiteResultBaseDir = self::GENERATE_RESULT_DIR . + DIRECTORY_SEPARATOR . + $groupName . + DIRECTORY_SEPARATOR; + + // Validate tests have been generated + $dirContents = array_diff(scandir($suiteResultBaseDir), ['..', '.']); + + foreach ($expectedContents as $expectedFile) { + $this->assertTrue(in_array($expectedFile, $dirContents)); + } + + //assert group file created and contains correct contents + $groupFile = PROJECT_ROOT . + DIRECTORY_SEPARATOR . + "src" . + DIRECTORY_SEPARATOR . + "Magento" . + DIRECTORY_SEPARATOR . + "FunctionalTestingFramework" . + DIRECTORY_SEPARATOR . + "Group" . + DIRECTORY_SEPARATOR . + $groupName . + ".php"; + + $this->assertTrue(file_exists($groupFile)); + $this->assertFileEquals( + self::RESOURCES_PATH . DIRECTORY_SEPARATOR . $groupName . ".txt", + $groupFile + ); + } + + /** + * Test suite generation with actions from different modules + */ + public function testSuiteGenerationActionsInDifferentModules() + { + $groupName = 'ActionsInDifferentModulesSuite'; + + $expectedContents = SuiteTestReferences::$data[$groupName]; // Generate the Suite SuiteGenerator::getInstance()->generateSuite($groupName); @@ -390,7 +481,7 @@ public function testSuiteCommentsGeneration() * revert any changes made to config.yml * remove _generated directory */ - public function tearDown() + public function tearDown(): void { DirSetupUtil::rmdirRecursive(self::GENERATE_RESULT_DIR); @@ -399,12 +490,16 @@ public function tearDown() $fileSystem->remove( self::CONFIG_YML_FILE ); + + $property = new \ReflectionProperty(DirSetupUtil::class, "DIR_CONTEXT"); + $property->setAccessible(true); + $property->setValue([]); } /** * Remove yml if created during tests and did not exist before */ - public static function tearDownAfterClass() + public static function tearDownAfterClass(): void { TestLoggingUtil::getInstance()->clearMockLoggingUtil(); } @@ -413,11 +508,11 @@ public static function tearDownAfterClass() * Getter for manifest file path * * @return string + * @throws TestFrameworkException */ private static function getManifestFilePath() { - return TESTS_BP . - DIRECTORY_SEPARATOR . + return FilePathFormatter::format(TESTS_BP) . "verification" . DIRECTORY_SEPARATOR . "_generated" . diff --git a/dev/tests/verification/Tests/SuiteTestReferences.php b/dev/tests/verification/Tests/SuiteTestReferences.php new file mode 100644 index 000000000..83481cabf --- /dev/null +++ b/dev/tests/verification/Tests/SuiteTestReferences.php @@ -0,0 +1,67 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace tests\verification\Tests; + +class SuiteTestReferences +{ + /** + * Array of suite to tests that the suite contains + * + * @var array + */ + public static $data = [ + 'functionalSuite1' => [ + 'additionalTestCest.php', + 'additionalIncludeTest2Cest.php', + 'IncludeTest2Cest.php', + 'IncludeTestCest.php' + ], + 'functionalSuiteHooks' => [ + 'IncludeTestCest.php' + ], + 'functionalSuite2' => [ + 'additionalTestCest.php', + 'additionalIncludeTest2Cest.php', + 'IncludeTest2Cest.php', + 'IncludeTestCest.php' + ], + 'suiteExtends' => [ + 'ExtendedChildTestInSuiteCest.php' + ], + 'functionalSuiteWithComments' => [ + 'IncludeTestCest.php' + ], + 'ActionsInDifferentModulesSuite' => [ + 'IncludeActionsInDifferentModulesTestCest.php' + ], + 'suiteWithMultiplePauseActionsSuite' => [ + 'additionalTestCest.php', + 'ExcludeTest2Cest.php', + 'IncludeTest2Cest.php' + ], + 'suiteWithPauseActionSuite' => [ + 'additionalTestCest.php', + 'ExcludeTest2Cest.php', + 'IncludeTest2Cest.php' + ], + 'PartialGenerateForIncludeSuite' => [ + 'IncludeTestCest.php' + ], + 'PartialGenerateNoExcludeSuite' => [ + 'IncludeTestCest.php' + ], + 'NotGenerateHookBeforeSuite' => [ + ], + 'NotGenerateHookAfterSuite' => [ + ], + 'deprecationCheckSuite' => [ + 'DeprecationCheckDeprecatedTestCest.php', + 'DeprecationCheckTestCest.php' + ], + 'NotGenerateEmptySuite' => [ + ], + ]; +} diff --git a/dev/tests/verification/_suite/functionalSuite.xml b/dev/tests/verification/_suite/functionalSuite.xml deleted file mode 100644 index cfdaa3557..000000000 --- a/dev/tests/verification/_suite/functionalSuite.xml +++ /dev/null @@ -1,56 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ ---> - -<suites xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../src/Magento/FunctionalTestingFramework/Suite/etc/suiteSchema.xsd"> - <suite name="functionalSuite1"> - <include> - <group name="include"/> - <test name="IncludeTest"/> - <module name="TestModule" file="SampleSuite2Test.xml"/> - </include> - <exclude> - <group name="exclude"/> - <test name="ExcludeTest2"/> - </exclude> - </suite> - <suite name="functionalSuite2"> - <include> - <group name="include"/> - <test name="IncludeTest"/> - <module name="TestModule" file="SampleSuite2Test.xml"/> - </include> - <exclude> - <group name="exclude"/> - <test name="ExcludeTest2"/> - </exclude> - </suite> - <suite name="functionalSuiteWithComments"> - <include> - <!-- Comment Block--> - <test name="IncludeTest"/> - </include> - <before> - <!-- Comment in Before--> - <amOnPage url="some.url" stepKey="before"/> - <createData entity="createThis" stepKey="create"> - <!--Comment in Nested Element--> - <field key="someKey">dataHere</field> - </createData> - <!-- <click stepKey="comment with element" userInput="helloworld"/> --> - <click stepKey="clickWithData" userInput="$create.data$"/> - <actionGroup ref="actionGroupWithTwoArguments" stepKey="AC"> - <!--Comment in AG Args--> - <argument name="somePerson" value="simpleData"/> - <argument name="anotherPerson" value="uniqueData"/> - </actionGroup> - </before> - <after> - <comment userInput="afterBlock" stepKey="afterBlock"/> - </after> - </suite> -</suites> diff --git a/docs/actiongroup-list.md b/docs/actiongroup-list.md deleted file mode 100644 index 7b469f4ed..000000000 --- a/docs/actiongroup-list.md +++ /dev/null @@ -1,6 +0,0 @@ -# MFTF action group reference - -Action groups are important building blocks for quickly creating tests for the Magento Functional Testing Framework. -This page lists all current action groups so developers can see what is available to them. - -{% include mftf/actiongroup_data.md %} diff --git a/docs/backward-incompatible-changes.md b/docs/backward-incompatible-changes.md new file mode 100644 index 000000000..5ee285941 --- /dev/null +++ b/docs/backward-incompatible-changes.md @@ -0,0 +1,169 @@ +--- +title: MFTF 3.0.0 backward incompatible changes +redirect_to: https://developer.adobe.com/commerce/testing/functional-testing-framework/backward-incompatible-changes/ +status: migrated +--- + +# MFTF 3.0.0 backward incompatible changes + +This page highlights backward incompatible changes between releases that have a major impact and require detailed explanation and special instructions to ensure third-party tests continue working with Magento core tests. + +## Minimum supported PHP version changes + +We changed the minimum PHP version requirement from 7.0 to 7.3. Because of the PHP version requirement change, this MFTF version only supports Magento 2.4 or later. + +## Folder structure changes + +We removed support to read test modules from the deprecated path `dev/tests/acceptance/tests/functional/Magento/FunctionalTest`. If there are test modules in this path, they should be moved to `dev/tests/acceptance/tests/functional/Magento`. + +## XSD schema changes + +- Files under test modules `ActionGroup`, `Page`, `Section`, `Test` and `Suite` only support a single entity per file. +- The `file` attribute from `<module>` has been removed from the suite schema. `<module file=""/>` is no longer supported in suites. +- Metadata filename format changed to ***`*Meta.xml`***. +- Only nested assertion syntax will be supported. See the [assertions page](test/assertions.md) for details. Here is an example of the nested assertion syntax: + ```xml + <assertEquals stepKey="assertAddressOrderPage"> + <actualResult type="const">$billingAddressOrderPage</actualResult> + <expectedResult type="const">$shippingAddressOrderPage</expectedResult> + </assertEquals> + ``` + +### Upgrading tests to the new schema + +The following table lists the upgrade scripts that are available to upgrade tests to the new schema. + +| Script name | Description | +|-----------------------|-----------------------------------------------------------------------------------------------------------| +|`splitMultipleEntitiesFiles`| Splits files that have multiple entities into multiple files with one entity per file. | +|`upgradeAssertionSchema`| Updates assert actions that uses the old assertion syntax into the new nested syntax.| +|`renameMetadataFiles`| Renames Metadata filenames to `*Meta.xml`.| +|`removeModuleFileInSuiteFiles`| Removes occurrences of `<module file=""/>` from all `<suite>`s.| +|`removeUnusedArguments`| Removes unused arguments from action groups.| +|`upgradeTestSchema`| Replaces relative schema paths to URN in test files.| + +To run the upgrade tests: + +1. Run `bin/mftf reset --hard` to remove old generated configurations. +1. Run `bin/mftf build:project` to generate new configurations. +1. Run `bin/mftf upgrade:tests`. [See command page for details](commands/mftf.md#upgradetests). +1. Lastly, try to generate all tests. Tests should all be generated as a result of the upgrades. If not, the most likely issue will be a changed XML schema. Check error messaging and search your codebase for the attributes listed. + +## MFTF commands + +`--debug` option `NONE` removed for strict schema validation. Ensure there are no schema validation errors in test modules before running MFTF commands. + +## MFTF actions + +### `executeInSelenium` and `performOn` removed + +**Action**: Deprecated actions `executeInSelenium` and `performOn` are removed in favor of new action `helper`. + +**Reason**: `executeInSelenium` and `performOn` allowed custom PHP code to be written inline inside of XML files which was difficult to maintain, troubleshoot, and modify. + +**Details**: + +The `helper` allows test writers to solve advanced requirements beyond what MFTF offers out of the box. See [custom-helpers](custom-helpers.md) for more information on usage. + +Here is an example of using `helper` in place of `executeSelenium` to achieve same workflow. + +Old usage: + +```xml +<executeInSelenium function="function ($webdriver) use ($I) { + $heading = $webdriver->findElement(\Facebook\WebDriver\WebDriverBy::xpath('//div[contains(@class, \'inline-wysiwyg\')]//h2')); + $actions = new \Facebook\WebDriver\Interactions\WebDriverActions($webdriver); + $actions->moveToElement($heading, {{TinyMCEPartialHeadingSelection.startX}}, {{TinyMCEPartialHeadingSelection.startY}}) + ->clickAndHold() + ->moveToElement($heading, {{TinyMCEPartialHeadingSelection.endX}}, {{TinyMCEPartialHeadingSelection.endY}}) + ->release() + ->perform(); + }" stepKey="selectHeadingTextInTinyMCE"/> +``` + +New usage: + +```xml +<helper class="\Magento\PageBuilder\Test\Mftf\Helper\SelectText" method="selectText" stepKey="selectHeadingTextInTinyMCE"> + <argument name="context">//div[contains(@class, 'inline-wysiwyg')]//h2</argument> + <argument name="startX">{{TinyMCEPartialHeadingSelection.startX}}</argument> + <argument name="startY">{{TinyMCEPartialHeadingSelection.startY}}</argument> + <argument name="endX">{{TinyMCEPartialHeadingSelection.endX}}</argument> + <argument name="endY">{{TinyMCEPartialHeadingSelection.endY}}</argument> +</helper> +``` + +### `pauseExecution` removed + +**Action**: `pauseExecution` is removed in favor of `pause`. + +**Reason**: `[WebDriver]pauseExecution` is removed in Codeception 3 in favor of `I->pause()`. + +**Details**: + +See the [actions page for details](test/actions.md#pause). Here is a usage example: + +```xml +<pause stepKey="pauseExecutionKey"/> +``` + +### Removed assert actions + +**Action**: Assert actions `assertInternalType`, `assertNotInternalType` and `assertArraySubset` are removed. + +**Reason**: PHPUnit 9 has dropped support for these assertions. + +### Updated assert actions + +**Action**: The `delta` attribute has been removed from `assertEquals` and `assertNotEquals`. Instead, new assert actions have been introduced: + + - `assertEqualsWithDelta` + - `assertNotEqualsWithDelta` + - `assertEqualsCanonicalizing` + - `assertNotEqualsCanonicalizing` + - `assertEqualsIgnoringCase` + - `assertNotEqualsIgnoringCase` + +**Reason**: PHPUnit 9 has dropped support for optional parameters for `assertEquals` and `assertNotEquals` and has introduced these new assertions. + +**Details**: + +Usage of `assertEquals` or `assertNotEquals` with a specified `delta`, should be replaced with appropriate assertion from the above list. + +### `assertContains` supports only iterable haystacks + +**Action**: `assertContains` and `assertNotContains` now only supports iterable haystacks. These assert actions have been added to work with string haystacks: + +- `assertStringContainsString` +- `assertStringNotContainsString` +- `assertStringContainsStringIgnoringCase` +- `assertStringNotContainsStringIgnoringCase` + +**Reason**: With PHPUnit 9, `assertContains` and `assertNotContains` only allows iterable haystacks. New assertions have been introduced to support string haystacks. + +**Details**: + +Usages of `assertContains` and `assertNotContains` with string haystacks should be replaced with appropriate assertion from the above list. + +Usage example for string haystacks: + +```xml +<assertStringContainsString stepKey="assertDiscountOnPrice2"> + <actualResult type="const">$grabSimpleProdPrice2</actualResult> + <expectedResult type="string">$110.70</expectedResult> +</assertStringContainsString> +``` + +### `formatMoney` removed + +**Action**: `formatMoney` has been removed in favor of `formatCurrency`. + +**Reason**: PHP 7.4 has deprecated use of `formatMoney`. + +**Details**: Format input to specified currency according to the locale specified. + +Usage example: + +```xml +<formatCurrency userInput="1234.56789000" locale="de_DE" currency="USD" stepKey="usdInDE"/> +``` diff --git a/docs/best-practices.md b/docs/best-practices.md index a3e5f2629..ec6237b1c 100644 --- a/docs/best-practices.md +++ b/docs/best-practices.md @@ -1,16 +1,60 @@ +--- +title: Best practices +redirect_to: https://developer.adobe.com/commerce/testing/functional-testing-framework/test-writing/best-practices/ +status: migrated +--- + # Best practices Check out our best practices below to ensure you are getting the absolute most out of the Magento Functional Testing Framework. +## Focus on reusability + +### Use existing Tests and resources + +Magento offers more than **3000** acceptance tests, **2500** [Action group]s, **750** Page declarations with more than **1500** Section definitions. +It is very probable that behaviour you want to test already exists as a Test or Action Group. +Instead of writing everything by yourself - use `extends` attribute to refer to existing element and customize it. + +**Reusable Resources** + +{%raw%} + +* Tests (reusable with `<test extends="...">` argument) +* Action Group (reusable with including `<actionGroup ref="...">`, or extending `<actionGroup extends="...">`) +* Pages (reusable with reference `{{PageDefinition.url}}`) +* Sections (reusable with reference `{{SectionDefinition.elementDefinition}}`) +* Data Entities (reusable with reference `<createData entity="...">"` or extending `<entity extends="...">`) + +{%endraw%} + +<div class="bs-callout bs-callout-warning" markdown="1"> + +Avoid using resources that are marked as **Deprecated**. Usually there is a replacement provided for a deprecated resource. + +</div> + +### Extract repetitive Actions + +Instead of writing a few of Tests that perform mostly the same actions, you should thing about [Action group] that is a container for repetitive Actions. +If each run needs different data, use `<arguments>` to inject necessary information. + +We recommend to keep Action Groups having single responsibility, for example `AdminLoginActionGroup`, which expected outcome is being logged in as Administrator when [Action group] is executed. + +## Contribute + +Although the Magento Core team and Contributors join forces to cover most of the features with tests, it is impossible to have this done quickly. +If you've covered Magento Core feature with Functional Tests - you are more than welcome to contribute. + ## Action group -1. [Action group] names should be sufficiently descriptive to inform a test writer of what the action group does and when it should be used. - Add additional explanation in comments if needed. +1. [Action group] names should be sufficiently descriptive to inform a test writer of what the action group does and when it should be used. Add additional explanation in annotations if needed. 2. Provide default values for the arguments that apply to your most common case scenarios. +3. One `<actionGroup>` tag is allowed per action group XML file. ## `actionGroups` vs `extends` -Use an action group to wraps a set of actions to reuse them multiple times. +Use an action group to wrap a set of actions to reuse them multiple times. Use an [extension] when a test or action group needs to be repeated with the exception of a few steps. @@ -47,7 +91,7 @@ The following pattern is used when merging with `extends`: Use a corresponding [`<deleteData>`] test step in your [`<after>`] block when using a [`<createData>`] action in a [`<before>`] block. 2. Make specific data entries under test to be unique. Enable data uniqueness where data values are required to be unique in a database by test design. - Use `unique=”suffix”` or `unique=”prefix”` to append or prepend a unique value to the [entity] attribute. + Use `unique="suffix"` or `unique="prefix"` to append or prepend a unique value to the [entity] attribute. This ensures that tests using the entity can be repeated. 3. Do not modify existing data entity fields or merge additional data fields without complete understanding and verifying the usage of existing data in tests. Create a new data entity for your test if you are not sure. @@ -58,12 +102,20 @@ The following pattern is used when merging with `extends`: Name files according to the following patterns to make searching in future more easy: +<!-- {% raw %} --> + #### Test file name Format: {_Admin_ or _Storefront_}{Functionality}_Test.xml_, where Functionality briefly describes the testing functionality. Example: _StorefrontCreateCustomerTest.xml_. +#### Action Group file name + +Format: {_Admin_ or _Storefront_}{Action Group Summary}ActionGroup.xml`, where Action Group Summary is a short description of what the action group does. + +Example: _AdminCreateStoreActionGroup.xml_ + #### Section file name Format: {_Admin_ or _Storefront_}{UI Description}_Section.xml_, where UI Description briefly describes the testing UI. @@ -74,6 +126,8 @@ Example: _AdminNavbarSection.xml_. Format: {Type}_Data.xml_, where Type represents the entity type. +<!-- {% endraw %} --> + Example: _ProductData.xml_. ### Object names @@ -84,23 +138,25 @@ Use the _Foo.camelCase_ naming convention, which is similar to _Classes_ and _cl Use an upper case first letter for: -- File names. Example: _StorefrontCreateCustomerTest.xml_ -- Test name attributes. Example: `<test name="TestAllTheThingsTest">`. -- Data entity names. Example: `<entity name="OutOfStockProduct">`. -- Page name. Example: `<page name="AdminLoginPage">`. -- Section name. Example: `<section name="AdminCategorySidebarActionSection">`. -- Action group name. Example: `<actionGroup name="LoginToAdminActionGroup">`. +* File names. Example: _StorefrontCreateCustomerTest.xml_ +* Test name attributes. Example: `<test name="TestAllTheThingsTest">` +* Data entity names. Example: `<entity name="OutOfStockProduct">` +* Page name. Example: `<page name="AdminLoginPage">` +* Section name. Example: `<section name="AdminCategorySidebarActionSection">` +* Action group name. Example: `<actionGroup name="LoginToAdminActionGroup">` #### Lower case Use a lower case first letter for: -- Data keys. Example: `<data key="firstName">`. -- Element names. Examples: `<element name="confirmDeleteButton"/>`. +* Data keys. Example: `<data key="firstName">` +* Element names. Examples: `<element name="confirmDeleteButton"/>` +* Step keys. For example: `<click selector="..." stepKey="clickLogin"/>` ## Page object -Use [parameterized selectors] for constructing a selector when test specific or runtime generated information is needed. +1. One `<page>` tag is allowed per page XML file. +2. Use [parameterized selectors] for constructing a selector when test-specific or runtime-generated information is needed. Do not use them for static elements. <span class="color:red"> @@ -134,10 +190,11 @@ Define these three elements and reference them by name in the tests. 1. Keep your tests short and granular for target testing, easier reviews, and easier merge conflict resolution. It also helps you to identify the cause of test failure. 1. Use comments to keep tests readable and maintainable: - - Keep the inline `<!-- XML comments -->` and [`<comment>`] tags up to date. + * Keep the inline `<!-- XML comments -->` and [`<comment>`] tags up to date. It helps to inform the reader of what you are testing and to yield a more descriptive Allure report. - - Explain in comments unclear or tricky test steps. + * Explain in comments unclear or tricky test steps. 1. Refer to [sections] instead of writing selectors. +1. One `<test>` tag is allowed per test XML file. ## Test step merging order diff --git a/docs/commands/codeception.md b/docs/commands/codeception.md index 6a96a89c7..1905d7a1e 100644 --- a/docs/commands/codeception.md +++ b/docs/commands/codeception.md @@ -1,7 +1,13 @@ +--- +title: CLI commands - vendor/bin/codecept +redirect_to: https://developer.adobe.com/commerce/testing/functional-testing-framework/commands/codeception/ +status: migrated +--- + # CLI commands: vendor/bin/codecept <div class="bs-callout bs-callout-warning" markdown="1"> -We do not recommend using Codeception commands directly as they can break the MFTF basic workflow. +We do not recommend using Codeception commands directly as they can break MFTF basic workflow. All the Codeception commands you need are wrapped using the [mftf tool][]. To run the Codeception testing framework commands directly, change your directory to the `<Magento root>`. @@ -36,7 +42,7 @@ vendor/bin/codecept run ``` <div class="bs-callout bs-callout-info"> -The following documentation corresponds to Codeception 2.3.8. +The following documentation corresponds to Codeception 4.1.4. </div> ```bash @@ -47,36 +53,44 @@ Arguments: test test to be run Options: - -o, --override=OVERRIDE Override config values (multiple values allowed) - --config (-c) Use custom path for config - --report Show output in compact style - --html Generate html with results (default: "report.html") - --xml Generate JUnit XML Log (default: "report.xml") - --tap Generate Tap Log (default: "report.tap.log") - --json Generate Json Log (default: "report.json") - --colors Use colors in output - --no-colors Force no colors in output (useful to override config file) - --silent Only outputs suite names and final results - --steps Show steps in output - --debug (-d) Show debug and scenario output - --coverage Run with code coverage (default: "coverage.serialized") - --coverage-html Generate CodeCoverage HTML report in path (default: "coverage") - --coverage-xml Generate CodeCoverage XML report in file (default: "coverage.xml") - --coverage-text Generate CodeCoverage text report in file (default: "coverage.txt") - --coverage-phpunit Generate CodeCoverage PHPUnit report in file (default: "coverage-phpunit") - --no-exit Do not finish with exit code - --group (-g) Groups of tests to be executed (multiple values allowed) - --skip (-s) Skip selected suites (multiple values allowed) - --skip-group (-x) Skip selected groups (multiple values allowed) - --env Run tests in selected environments. (multiple values allowed, environments can be merged with ',') - --fail-fast (-f) Stop after first failure - --help (-h) Display this help message. - --quiet (-q) Do not output any message. - --verbose (-v|vv|vvv) Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug - --version (-V) Display this application version. - --ansi Force ANSI output. - --no-ansi Disable ANSI output. - --no-interaction (-n) Do not ask any interactive question. + -o, --override=OVERRIDE Override config values (multiple values allowed) + -e, --ext=EXT Run with extension enabled (multiple values allowed) + --report Show output in compact style + --html[=HTML] Generate html with results [default: "report.html"] + --xml[=XML] Generate JUnit XML Log [default: "report.xml"] + --phpunit-xml[=PHPUNIT-XML] Generate PhpUnit XML Log [default: "phpunit-report.xml"] + --tap[=TAP] Generate Tap Log [default: "report.tap.log"] + --json[=JSON] Generate Json Log [default: "report.json"] + --colors Use colors in output + --no-colors Force no colors in output (useful to override config file) + --silent Only outputs suite names and final results + --steps Show steps in output + -d, --debug Show debug and scenario output + --bootstrap[=BOOTSTRAP] Execute custom PHP script before running tests. Path can be absolute or relative to current working directory [default: false] + --no-redirect Do not redirect to Composer-installed version in vendor/codeception + --coverage[=COVERAGE] Run with code coverage + --coverage-html[=COVERAGE-HTML] Generate CodeCoverage HTML report in path + --coverage-xml[=COVERAGE-XML] Generate CodeCoverage XML report in file + --coverage-text[=COVERAGE-TEXT] Generate CodeCoverage text report in file + --coverage-crap4j[=COVERAGE-CRAP4J] Generate CodeCoverage report in Crap4J XML format + --coverage-phpunit[=COVERAGE-PHPUNIT] Generate CodeCoverage PHPUnit report in path + --no-exit Don't finish with exit code + -g, --group=GROUP Groups of tests to be executed (multiple values allowed) + -s, --skip=SKIP Skip selected suites (multiple values allowed) + -x, --skip-group=SKIP-GROUP Skip selected groups (multiple values allowed) + --env=ENV Run tests in selected environments. (multiple values allowed) + -f, --fail-fast Stop after first failure + --no-rebuild Do not rebuild actor classes on start + --seed=SEED Define random seed for shuffle setting + --no-artifacts Don't report about artifacts + -h, --help Display this help message + -q, --quiet Do not output any message + -V, --version Display this application version + --ansi Force ANSI output + --no-ansi Disable ANSI output + -n, --no-interaction Do not ask any interactive question + -c, --config[=CONFIG] Use custom path for config + -v|vv|vvv, --verbose Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug ``` <!-- Link definitions --> diff --git a/docs/commands/mftf.md b/docs/commands/mftf.md index 58be6501e..4cfbdab93 100644 --- a/docs/commands/mftf.md +++ b/docs/commands/mftf.md @@ -1,3 +1,9 @@ +--- +title: CLI commands - vendor/bin/mftf +redirect_to: https://developer.adobe.com/commerce/testing/functional-testing-framework/commands/mftf/ +status: migrated +--- + # CLI commands: vendor/bin/mftf The Magento Functional Testing Framework (MFTF) introduces the command line interface (CLI) tool `vendor/bin/mftf` to facilitate your interaction with the framework. @@ -28,7 +34,7 @@ vendor/bin/mftf build:project vendor/bin/mftf build:project --upgrade ``` -Upgrades the existing MFTF tests after the MFTF major upgrade. +Upgrades all installed MFTF tests after a major MFTF upgrade. ### Generate all tests @@ -39,7 +45,44 @@ vendor/bin/mftf generate:tests ### Generate tests by test name ```bash -vendor/bin/mftf generate:tests LoginAsAdminTest LoginAsCustomerTest +vendor/bin/mftf generate:tests AdminLoginSuccessfulTest StorefrontPersistedCustomerLoginTest +``` + +### Generate tests by testNames.txt file + +```bash +vendor/bin/mftf generate:tests -p path/to/your/testNames.txt +``` + +This command generate all tests specified in a testNames.txt file. + +#### Example + +```bash +testName1 +testName2 +testNameN +suiteName:testInSuite +``` + +### Generate test by test and suite name + +```bash +vendor/bin/mftf generate:tests WYSIWYGDisabledSuite:AdminCMSPageCreatePageTest +``` + +### Generate test dependencies + +```bash +vendor/bin/mftf generate:tests -l testEntityJson +``` + +This command generate json file consist of all test dependent module. + +### Generate test dependencies by test name + +```bash +vendor/bin/mftf generate:tests testName1 testName2 .. testNameN -l testEntityJson ``` ### Generate and run the tests for a specified group @@ -53,10 +96,18 @@ This command cleans up the previously generated tests; generates and runs tests ### Generate and run particular tests ```bash -vendor/bin/mftf run:test LoginAsAdminTest LoginAsCustomerTest -r +vendor/bin/mftf run:test AdminLoginSuccessfulTest StorefrontPersistedCustomerLoginTest -r +``` + +This command cleans up the previously generated tests; generates and runs the `AdminLoginSuccessfulTest` and `StorefrontPersistedCustomerLoginTest` tests. + +### Generate and run particular test in a specific suite's context + +```bash +vendor/bin/mftf run:test WYSIWYGDisabledSuite:AdminCMSPageCreatePageTest -r ``` -This command cleans up the previously generated tests; generates and runs the `LoginAsAdminTest` and `LoginAsCustomerTest` tests. +This command cleans up previously generated tests; generates and run `AdminCMSPageCreatePageTest` within the context of the `WYSIWYGDisabledSuite`. ### Generate and run a testManifest.txt file @@ -66,22 +117,32 @@ vendor/bin/mftf run:manifest path/to/your/testManifest.txt This command runs all tests specified in a `testManifest.txt` file. When you generate tests, a `testManifest.txt` file is also generated for you. You can pass this file directly to the `run:manifest` command and it will execute all listed tests. You can also create your own file in the same format to execute a subset of tests. Note: This command does not generate tests. -### Generate and run previously failed tests +### Generate previously failed tests ```bash -vendor/bin/mftf run:failed +vendor/bin/mftf generate:failed ``` -This command cleans up the previously generated tests; generates and runs the tests listed in `dev/tests/acceptance/tests/_output/failed`. +This command cleans up the previously generated tests; generates the tests listed in `dev/tests/acceptance/tests/_output/failed`. For more details about `failed`, refer to [Reporting][]. -### Generate documentation for action groups +### Run previously failed tests ```bash -vendor/bin/mftf generate:docs +vendor/bin/mftf run:failed ``` -This command generates documentation for action groups. +This command runs the tests listed in `dev/tests/acceptance/tests/_output/failed`. +For more details about `failed`, refer to [Reporting][]. + +## Error tolerance during generation + +Starting from version 3.2.0, MFTF will not fail right away when encountering generation errors. +Instead, MFTF will generate as many tests and suites as it can, log errors to `mftf.log`, and exit with a non-zero generation status. + +Note: +- Not all errors are tolerable at generation. For example, schema validation errors, parser errors, and WebApi authentication errors will cause `hard` failures, with no tests or suites being generated. +- Error tolerance in generation is meant to help local test development and testing and is expected to be run locally. All generation errors must be fixed in order to use other framework functionality, pass static checks, and to deliver MFTF tests. ## Reference @@ -101,7 +162,7 @@ vendor/bin/mftf build:project [--upgrade] [config_param_options] | Option | Description | | ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `-u`, `--upgrade` | Upgrades existing MFTF tests according to requirements of the last major release. Specifying this flag upgrades only those tests in the default location. Example: `build:project --upgrade`. | +| `-u`, `--upgrade` | Upgrades all installed MFTF tests according to requirements of the last major release. Specifying this flag upgrades only those tests in the default location. Example: `build:project --upgrade`. | You can include options to set configuration parameter values for your environment since the project build process also [sets up the environment][setup]. @@ -109,6 +170,24 @@ You can include options to set configuration parameter values for your environme vendor/bin/mftf build:project --MAGENTO_BASE_URL=http://magento.local/ --MAGENTO_BACKEND_NAME=admin214365 ``` +### `doctor` + +#### Description + +Diagnose MFTF configuration and setup. Currently this command will check the following: +- Verify admin credentials are valid. Allowing MFTF authenticates and runs API requests to Magento through cURL +- Verify that Selenium is up and running and available for MFTF +- Verify that new session of browser can open Magento admin and store front urls +- Verify that MFTF can run MagentoCLI commands + +#### Usage + +```bash +vendor/bin/mftf doctor +``` + +#### Options + ### `generate:tests` #### Description @@ -124,22 +203,25 @@ vendor/bin/mftf generate:tests [option] [<test name>] [<test name>] [--remove] #### Options -| Option | Description| -| ---| --- | -| `--config=[<default> or <singleRun> or <parallel>]` | Creates a single manifest file with a list of all tests. The default location is `tests/functional/Magento/FunctionalTest/_generated/testManifest.txt`.<br/> You can split the list into multiple groups using `--config=parallel`; the groups will be generated in `_generated/groups/` like `_generated/groups/group1.txt, group2.txt, ...`.</br> Available values: `default` (default), `singleRun`(same as `default`), and `parallel`.</br> Example: `generate:tests --config=parallel`. | -| `--force` | Forces test generation, regardless of the module merge order defined in the Magento instance. Example: `generate:tests --force`. | -| `-i,--time` | Set time in minutes to determine the group size when `--config=parallel` is used. The __default value__ is `10`. Example: `generate:tests --config=parallel --time=15`| -| `--tests` | Defines the test configuration as a JSON string.| -| `--allow-skipped` | Allows MFTF to generate and run tests marked with `<skip>.`| -| `--debug or --debug=[<none>]`| Performs schema validations on XML files. <br/> DEFAULT: `generate:tests` implicitly performs schema validation on merged files. It does not indicate the file name where the error is encountered. <br/> DEVELOPER: `--debug` performs per-file validation and returns additional debug information (such as the filename where an error occurred) when test generation fails because of an invalid XML schema. This option takes extra processing time. Use it after test generation has failed once.</br><br/> NONE: `--debug=none` skips debugging during test generation. Added for backward compatibility, it will be removed in the next MAJOR release.</br>| -| `-r,--remove`| Removes the existing generated suites and tests cleaning up the `_generated` directory before the actual run. For example, `generate:tests SampleTest --remove` cleans up the entire `_generated` directory and generates `SampleTest` only.| +| Option | Description | +|-----------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `--config=[<default> or <singleRun> or <parallel>]` | Creates a single manifest file with a list of all tests. The default location is `tests/functional/Magento/FunctionalTest/_generated/testManifest.txt`.<br/> You can split the list into multiple groups using `--config=parallel`; the groups will be generated in `_generated/groups/` like `_generated/groups/group1.txt, group2.txt, ...`.<br/> Available values: `default` (default), `singleRun`(same as `default`), and `parallel`.<br/> Example: `generate:tests --config=parallel`. | +| `--filter` | Option to filter tests to be generated.<br/>Template: '<filterName>:<filterValue>'.<br/>Existing filter types: severity, includeGroup, excludeGroup.<br/>Existing severity values: BLOCKER, CRITICAL, MAJOR, AVERAGE, MINOR.<br/>Example: `vendor/bin/mftf generate:tests --filter=severity:CRITICAL --filter=severity:BLOCKER --filter=includeGroup:customer` | +| `--force` | Forces test generation, regardless of the module merge order defined in the Magento instance. Example: `generate:tests --force`. | +| `-i,--time` | Set time in minutes to determine the group size when `--config=parallel` is used. <br/>Example: `generate:tests --config=parallel --time=15` <br/>Option `--time` will be the default and the __default value__ is `10` when neither `--time` nor `--groups` is specified. <br/>Example: `generate:tests --config=parallel` | +| `-g,--groups` | Set number of groups to be split into when `--config=parallel` is used. <br>Example: `generate:tests --config=parallel --groups=300` <br/>Options `--time` and `--groups` are mutually exclusive and only one should be used. | +| `--tests` | Defines the test configuration as a JSON string or JSON file path. | +| `--allow-skipped` | Allows MFTF to generate and run tests marked with `<skip>.` | +| `--debug` | Performs schema validations on XML files. <br/> DEFAULT: `generate:tests` implicitly performs schema validation on merged files. It does not indicate the file name where the error is encountered. <br/> DEVELOPER: `--debug` performs per-file validation and returns additional debug information (such as the filename where an error occurred) when test generation fails because of an invalid XML schema. This option takes extra processing time. Use it after test generation has failed once.<br/> | +| `-r,--remove` | Removes the existing generated suites and tests cleaning up the `_generated` directory before the actual run. For example, `generate:tests SampleTest --remove` cleans up the entire `_generated` directory and generates `SampleTest` only. | +| `-l,--log` | Generate metadata files during test generation. Accepted parameters are: testEntityJson. | #### Examples of the JSON configuration The configuration to generate a single test with no suites: ```json -{ +{ "tests":[ "general_test1" //Generate the "general_test1" test. ], @@ -150,9 +232,9 @@ The configuration to generate a single test with no suites: The configuration to generate a single test in the suite: ```json -{ +{ "tests": null, // No tests outside the suite configuration will be generated. - "suites":{ + "suites":{ "sample":[ // The suite that contains the test. "suite_test1" // The test to be generated. ] @@ -163,8 +245,8 @@ The configuration to generate a single test in the suite: Complex configuration to generate a few non-suite tests, a single test in a suite, and an entire suite: ```json -{ - "tests":[ +{ + "tests":[ "general_test1", "general_test2", "general_test3" @@ -180,12 +262,20 @@ Complex configuration to generate a few non-suite tests, a single test in a suit The command that encodes this complex configuration: +Command to generate test by json string: + ```bash vendor/bin/mftf generate:tests --tests '{"tests":["general_test1","general_test2","general_test3"],"suites":{"sample":["suite_test1"],"sample2":null}}' ``` Note that the strings must be escaped and surrounded in quotes. +Command to generate test by json file: + +```bash +vendor/bin/mftf generate:tests --tests ./foldername/filename.json +``` + ### `generate:suite` #### Description @@ -220,10 +310,10 @@ It also enables auto-completion in PhpStorm. #### Usage ```bash -vendor/bin/mftf generate:urn-catalog [--force] [<path to the directory with misc.xml>] +vendor/bin/mftf generate:urn-catalog [--force] [<path to misc.xml>] ``` -`misc.xml` is typically located in `<project root>/.idea/`. +`misc.xml` is typically located at `<project root>/.idea/misc.xml`. #### Options @@ -234,33 +324,7 @@ vendor/bin/mftf generate:urn-catalog [--force] [<path to the directory with misc #### Example ```bash -vendor/bin/mftf generate:urn-catalog .idea/ -``` - -### `generate:docs` - -#### Description - -Generates documentation that lists all action groups available in the codebase. -The default path is `<projectRoot>/dev/tests/docs/documentation.md`. - -#### Usage - -```bash -vendor/bin/mftf generate:docs [--clean] [--output=/path/to/alternate/dir] -``` - -#### Options - -| Option | Description | -| ------------- | --------------------------------------------------------------------- | -| `-c, --clean` | Overwrites previously existing documentation | -| `-o, --output` | Changes the default output directory to a user specified directory | - -#### Example - -```bash -vendor/bin/mftf generate:docs --clean +vendor/bin/mftf generate:urn-catalog .idea/misc.xml ``` ### `reset` @@ -295,7 +359,7 @@ Generates and executes the listed groups of tests using Codeception. #### Usage ```bash -vendor/bin/mftf run:group [--skip-generate|--remove] [--] <group1> [<group2>] +vendor/bin/mftf run:group [--skip-generate|--remove|--xml] [--] <group1> [<group2>] ``` #### Options @@ -304,7 +368,8 @@ vendor/bin/mftf run:group [--skip-generate|--remove] [--] <group1> [<group2>] | --------------------- | --------------------------------------------------------------------------------------------------------- | | `-k, --skip-generate` | Skips generating from the source XML. Instead, the command executes previously-generated groups of tests. | | `-r, --remove` | Removes previously generated suites and tests before the actual generation and run. | -| `--debug or --debug=[<none>]`| Performs schema validations on XML files. `run:group` implicitly performs schema validation on merged files. It does not indicate the file name where the error is encountered. `--debug` performs per-file validation and returns additional debug information (such as the filename where an error occurred). `--debug=none` skips debugging during test run. Added for backward compatibility, it will be removed in the next MAJOR release.| +| `--debug` | Performs schema validations on XML files. `run:group` implicitly performs schema validation on merged files. It does not indicate the file name where the error is encountered. `--debug` performs per-file validation and returns additional debug information (such as the filename where an error occurred).| +| `--xml` | Generate JUnit XML Log (default: "report.xml") | #### Examples @@ -327,7 +392,7 @@ Generates and executes tests by name using Codeception. #### Usage ```bash -vendor/bin/mftf run:test [--skip-generate|--remove] [--] <name1> [<name2>] +vendor/bin/mftf run:test [--skip-generate|--remove|--xml] [--] <name1> [<name2>] ``` #### Options @@ -336,7 +401,8 @@ vendor/bin/mftf run:test [--skip-generate|--remove] [--] <name1> [<name2>] |-----------------------|-----------------------------------------------------------------------------------------------------------| | `-k, --skip-generate` | Skips generating from the source XML. Instead, the command executes previously-generated groups of tests. | | `-r, --remove` | Remove previously generated suites and tests. | -| `--debug or --debug=[<none>]`| Performs schema validations on XML files. `run:test` implicitly performs schema validation on merged files. It does not indicate the file name where the error is encountered. `--debug` performs per-file validation and returns additional debug information (such as the filename where an error occurred). `--debug=none` skips debugging during test run. Added for backward compatibility, it will be removed in the next MAJOR release. +| `--debug` | Performs schema validations on XML files. `run:test` implicitly performs schema validation on merged files. It does not indicate the file name where the error is encountered. `--debug` performs per-file validation and returns additional debug information (such as the filename where an error occurred).| +| `--xml` | Generate JUnit XML Log (default: "report.xml") | #### Examples @@ -350,7 +416,7 @@ vendor/bin/mftf run:test LoginCustomerTest StorefrontCreateCustomerTest Runs a testManifest.txt file. -This command runs all tests specified in a testManifest.xml file. It does not generate tests for you. You must do that as first. +This command runs all tests specified in a testManifest.xml file. It does not generate tests for you. You must do that as first. #### Usage @@ -363,7 +429,7 @@ vendor/bin/mftf run:manifest path/to/your/testManifest.txt Each line should contain either: one test path or one group (-g) reference. ``` -tests/functional/tests/MFTF/_generated/default/AdminLoginTestCest.php +tests/functional/tests/MFTF/_generated/default/AdminLoginSuccessfulTestCest.php -g PaypalTestSuite tests/functional/tests/MFTF/_generated/default/SomeOtherTestCest.php tests/functional/tests/MFTF/_generated/default/ThirdTestCest.php @@ -386,7 +452,7 @@ vendor/bin/mftf run:failed | Option | Description | |-----------------------|-----------------------------------------------------------------------------------------------------------| -| `--debug or --debug=[<none>]`| Performs schema validations on XML files. `run:failed` implicitly performs schema validation on merged files. It does not indicate the file name where the error is encountered. `--debug` performs per-file validation and returns additional debug information (such as the filename where an error occurred). Use it after test run has failed once. `--debug=none` skips debugging during test run. Added for backward compatibility, it will be removed in the next MAJOR release.| +| `--debug` | Performs schema validations on XML files. `run:failed` implicitly performs schema validation on merged files. It does not indicate the file name where the error is encountered. `--debug` performs per-file validation and returns additional debug information (such as the filename where an error occurred). Use it after test run has failed once.| #### Examples @@ -429,35 +495,134 @@ vendor/bin/mftf setup:env The example parameters are taken from the `etc/config/.env.example` file. -### `static:checks` +### `static-checks` -Runs all MFTF static:checks on the test codebase that MFTF is currently attached to. +Runs all or specific MFTF static-checks on the test codebase that MFTF is currently attached to. +Behavior for determining what tests to run is as follows: -#### Existing static checks +* If test names are specified, only those tests are run. +* If no test names are specified, tests are run according to `staticRuleset.json`. +* If no `staticRuleset.json` is found, all tests are run. -* Test Dependency: Checks that test dependencies do not violate Magento module's composer dependencies. +Static checks errors are written to *.txt files under TEST_BP/tests/_output/static-results/ #### Usage ```bash -vendor/bin/mftf static:checks +vendor/bin/mftf static-checks [<names>]... +``` + +#### Options + +| Option | Description | +|-----------------------|-----------------------------------------------------------------------------------------------------------| +| `-p, --path` | Path to a MFTF test module to run "deprecatedEntityUsage" and "pauseActionUsage" static check scripts. Option is ignored by other static check scripts. + +#### Examples + +To check what existing static check scripts are available + +```bash +vendor/bin/mftf static-checks --help +``` + +To run all existing static check scripts + +```bash +vendor/bin/mftf static-checks +``` + +To run specific static check scripts + +```bash +vendor/bin/mftf static-checks testDependencies +``` + +```bash +vendor/bin/mftf static-checks actionGroupArguments +``` + +```bash +vendor/bin/mftf static-checks deprecatedEntityUsage +``` + +```bash +vendor/bin/mftf static-checks pauseActionUsage +``` + +```bash +vendor/bin/mftf static-checks annotations +``` + +```bash +vendor/bin/mftf static-checks deprecatedEntityUsage -p path/to/mftf/test/module +``` + +```bash +vendor/bin/mftf static-checks pauseActionUsage -p path/to/mftf/test/module +``` + +```bash +vendor/bin/mftf static-checks testDependencies actionGroupArguments +``` + +#### Existing static checks + +| Argument | Description | +|-----------------------|-----------------------------------------------------------------------------------------------------------| +|`testDependencies` | Checks that test dependencies do not violate Magento module's composer dependencies.| +|`actionGroupArguments` | Checks that action groups do not have unused arguments.| +|`deprecatedEntityUsage`| Checks that deprecated test entities are not being referenced.| +|`annotations`| Checks various details of test annotations, such as missing annotations or duplicate annotations.| +|`pauseUsage`| Checks that pause action is not used in action groups, tests or suites.| + +#### Defining ruleset + +The `static-checks` command will look for a `staticRuleset.json` file under either: + +* `dev/tests/acceptance/staticRuleset.json`, if embedded with Magento2 +* `dev/staticRuleset.json`, if standalone + +This file works as the default configuration to easily allow for the integration of `static-checks` in a CI environment. +Currently, the ruleset only defines the tests to run. Here is an example of the expected format: + +```json +{ + "tests": [ + "actionGroupArguments", + "anotherTest" + ] +} ``` ### `upgrade:tests` -Applies all the MFTF major version upgrade scripts to test components in the given path (`test.xml`, `data.xml`, etc). +When the path argument is specified, this `upgrade` command applies all the major version MFTF upgrade scripts to a `Test Module` in the given path. +Otherwise, it will apply all the major version MFTF upgrade scripts to all installed test components. + +`Test Module` should have the directory structure of ActionGroup, Data, Metadata, Page, Section, Test, and Suite. + +**Note**: + +The upgrade scripts are meant to be used for Test Modules under source code control. If you have old versions of test modules under vendor, those test modules will get upgraded #### Usage ```bash -vendor/bin/mftf upgrade:tests <path> +vendor/bin/mftf upgrade:tests [<path>] ``` -`<path>` is the path that contains MFTF test components that need to be upgraded. +`<path>` is the path to a MFTF `Test Module` that needs to be upgraded. The command searches recursively for any `*.xml` files to upgrade. #### Examples +To upgrade all installed MFTF tests: + +```bash +vendor/bin/mftf upgrade:tests +``` + To upgrade all test components inside modules in the `dev/tests/acceptance/tests/` directory: ```bash @@ -470,6 +635,47 @@ To upgrade all test components inside the `Catalog` module: vendor/bin/mftf upgrade:tests /Users/user/magento2/app/code/Magento/Catalog/Test/Mftf/ ``` +### `codecept:run` + +A MFTF wrapper command that invokes `vendor/bin/codecept run`. This command runs tests in functional suite. Tests must be generated before using this command. + +#### Usage + +See the [Run Command](https://codeception.com/docs/reference/Commands#Run). + +```bash +vendor/bin/mftf codecept:run [<suite|test>] --[<option(s)>] +``` + +#### Examples + +```bash +# Run all tests in functional suite +vendor/bin/mftf codecept:run functional +``` + +```bash +# Run all tests in functional suite with options +vendor/bin/mftf codecept:run functional --verbose --steps --debug +``` + +```bash +# Run one test +vendor/bin/mftf codecept:run functional Magento/_generated/default/AdminCreateCmsPageTestCest.php --debug +``` + +```bash +# Run all tests in default group +vendor/bin/mftf codecept:run functional --verbose --steps -g default +``` + +<div class="bs-callout-warning"> +<p> +Note: You may want to limit the usage of this Codeception command with arguments and options for "acceptance" only, since it is what's supported by MFTF. +When using this command, you should change "acceptance" to "functional" when referring to Codeception documentation. +</p> +</div> + <!-- LINK DEFINITIONS --> [configuration]: ../configuration.md diff --git a/docs/configuration.md b/docs/configuration.md index c741e2f60..fff885e1f 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -1,12 +1,18 @@ +--- +title: Configuration +redirect_to: https://developer.adobe.com/commerce/testing/functional-testing-framework/configuration/ +status: migrated +--- + # Configuration The `*.env` file provides additional configuration for the Magento Functional Testing Framework (MFTF). -To run the MFTF on your Magento instance, specify the basic configuration values. +To run MFTF on your Magento instance, specify the basic configuration values. Advanced users can create custom configurations based on requirements and environment. ## Basic configuration -These basic configuration values are __required__ and must be set by the user before the MFTF can function correctly. +These basic configuration values are __required__ and must be set by the user before MFTF can function correctly. ### MAGENTO_BASE_URL @@ -32,6 +38,16 @@ Example: MAGENTO_BACKEND_NAME=admin_12346 ``` +### MAGENTO_BACKEND_BASE_URL + +(Optional) If you are running the Admin Panel on a separate domain, specify this value: + +Example: + +```conf +MAGENTO_BACKEND_BASE_URL=https://admin.magento2.test +``` + ### MAGENTO_ADMIN_USERNAME The username that tests can use to access the Magento Admin page @@ -54,7 +70,7 @@ MAGENTO_ADMIN_PASSWORD=1234reTyt%$7 ## Advanced configuration -Depending on the environment you use, you may need to configure the MFTF more precisely by setting more configuration parameters then for basic configuration. +Depending on the environment you use, you may need to configure MFTF more precisely by setting additional configuration parameters. This section describes available configuration parameters and their default values (where applicable). ### DEFAULT_TIMEZONE @@ -163,8 +179,8 @@ MAGENTO_RESTAPI_SERVER_PORT=5000 ### \*_BP Settings to override base paths for the framework. -You can use it when the MFTF is applied as a separate tool. -For example, when you need to place the MFTF and the Magento codebase in separate projects. +You can use it when MFTF is applied as a separate tool. +For example, when you need to place MFTF and the Magento codebase in separate projects. ```conf MAGENTO_BP @@ -211,18 +227,18 @@ The path to where the MFTF modules mirror Magento modules. Example: ```conf -TESTS_MODULE_PATH=~/magento2/dev/tests/acceptance/tests/functional/Magento/FunctionalTest +TESTS_MODULE_PATH=~/magento2/dev/tests/acceptance/tests/functional/Magento ``` -### MODULE_WHITELIST +### MODULE_ALLOWLIST Use for a new module. -When adding a new directory at `Magento/FunctionalTest`, add the directory name to `MODULE_WHITELIST` to enable the MFTF to process it. +When adding a new directory at `tests/functional/Magento`, add the directory name to `MODULE_ALLOWLIST` to enable MFTF to process it. Example: ```conf -MODULE_WHITELIST=Magento_Framework,Magento_ConfigurableProductWishlist,Magento_ConfigurableProductCatalogSearch +MODULE_ALLOWLIST=Magento_Framework,Magento_ConfigurableProductWishlist,Magento_ConfigurableProductCatalogSearch ``` ### MAGENTO_CLI_COMMAND_PATH @@ -235,7 +251,7 @@ It points to `MAGENTO_BASE_URL` + `dev/tests/acceptance/utils/command.php` Modify the default value: - for non-default Magento installation -- when use a subdirectory in the `MAGENTO_BASE_URL` +- when using a subdirectory in the `MAGENTO_BASE_URL` Example: `dev/tests/acceptance/utils/command.php` @@ -277,9 +293,160 @@ Example: CREDENTIAL_VAULT_SECRET_BASE_PATH=secret ``` +### CREDENTIAL_AWS_SECRETS_MANAGER_REGION + +The region that AWS Secrets Manager is located. + +Example: + +```conf +# Region of AWS Secrets Manager +CREDENTIAL_AWS_SECRETS_MANAGER_REGION=us-east-1 +``` + +### CREDENTIAL_AWS_SECRETS_MANAGER_PROFILE + +The profile used to connect to AWS Secrets Manager. + +Example: + +```conf +# Profile used to connect to AWS Secrets Manager. +CREDENTIAL_AWS_SECRETS_MANAGER_PROFILE=default +``` + +### VERBOSE_ARTIFACTS + +Determines if passed tests should still have all their Allure artifacts. These artifacts include `.txt` attachments for `dontSee` actions and `createData` actions. + +If enabled, all tests will have all of their normal Allure artifacts. + +If disabled, passed tests will have their Allure artifacts trimmed. Failed tests will still contain all their artifacts. + +This is set `false` by default. + +```conf +VERBOSE_ARTIFACTS=true +``` + +### ENABLE_BROWSER_LOG + +Enables addition of browser logs to Allure steps + +```conf +ENABLE_BROWSER_LOG=true +``` + +### SELENIUM_CLOSE_ALL_SESSIONS + +Forces MFTF to close all Selenium sessions after running a suite. + +Use this if you're having issues with sessions hanging in an MFTF suite. + +```conf +SELENIUM_CLOSE_ALL_SESSIONS=true +``` + +### BROWSER_LOG_BLOCKLIST + +Blocklists types of browser log entries from appearing in Allure steps. + +Denoted in browser log entry as `"SOURCE": "type"`. + +```conf +BROWSER_LOG_BLOCKLIST=other,console-api +``` + +### WAIT_TIMEOUT + +Global MFTF configuration for the default amount of time (in seconds) that a test will wait while loading a page. + +```conf +WAIT_TIMEOUT=30 +``` + +### ENABLE_PAUSE + +Enables the ability to pause test execution at any point, and enter an interactive shell where you can try commands in action. +When pause is enabled, MFTF will generate pause() command in _failed() hook so that test will pause execution when failed. + +```conf +ENABLE_PAUSE=true +``` + +### REMOTE_STORAGE_AWSS3_DRIVER + +The remote storage driver. To enable AWS S3, use `aws-s3`. + +Example: + +```conf +REMOTE_STORAGE_AWSS3_DRIVER=aws-s3 +``` + +### REMOTE_STORAGE_AWSS3_REGION + +The region of S3 bucket. + +Example: + +```conf +REMOTE_STORAGE_AWSS3_REGION=us-west-2 +``` + +### REMOTE_STORAGE_AWSS3_BUCKET + +The name of S3 bucket. + +Example: + +```conf +REMOTE_STORAGE_AWSS3_BUCKET=my-test-bucket +``` + +### REMOTE_STORAGE_AWSS3_PREFIX + +The optional prefix inside S3 bucket. + +Example: + +```conf +REMOTE_STORAGE_AWSS3_PREFIX=local +``` + +### REMOTE_STORAGE_AWSS3_ACCESS_KEY + +The optional access key for the S3 bucket. + +Example: + +```conf +REMOTE_STORAGE_AWSS3_ACCESS_KEY=access-key +``` + +### REMOTE_STORAGE_AWSS3_SECRET_KEY + +The optional secret key for the S3 bucket. + +Example: + +```conf +REMOTE_STORAGE_AWSS3_SECRET_KEY=secret-key +``` + +### MAGENTO_ADMIN_WEBAPI_TOKEN_LIFETIME + +The lifetime (in seconds) of Magento Admin WebAPI token; if token is older than this value a refresh attempt will be made just before the next WebAPI call. + +Example: + +```conf +MAGENTO_ADMIN_WEBAPI_TOKEN_LIFETIME=10800 +``` + <!-- Link definitions --> [`MAGENTO_CLI_COMMAND_PATH`]: #magento_cli_command_path [generateDate]: test/actions.md#generatedate [mftf]: commands/mftf.md -[timezones]: http://php.net/manual/en/timezones.php +[timezones]: https://php.net/manual/en/timezones.php diff --git a/docs/configure-2fa.md b/docs/configure-2fa.md new file mode 100644 index 000000000..783e9e360 --- /dev/null +++ b/docs/configure-2fa.md @@ -0,0 +1,59 @@ +--- +title: Configuring MFTF for two-factor authentication (2FA) +redirect_to: https://developer.adobe.com/commerce/testing/functional-testing-framework/two-factor-authentication/ +status: migrated +--- + +# Configuring MFTF for Two-Factor Authentication (2FA) + +Using two-factor authentication (2FA) with MFTF is possible with some configurations settings in Magento. +In this document, we will use Google as the authentication provider. + +## Configure Magento {#config-magento-2fa} + +To prepare Magento for MFTF testing when 2FA is enabled, set the following configurations through the Magento CLI. + +First, select `Google Authenticator` as Magento's 2FA provider: + +```bash +bin/magento config:set twofactorauth/general/force_providers google +``` + +Now set the OTP window to `60` seconds: + +```bash +bin/magento config:set twofactorauth/google/otp_window 60 +``` + +Set a base32-encoded `secret` for `Google Authenticator` to generate a OTP for the default admin user that you set for `MAGENTO_ADMIN_USERNAME` in `.env`: + +```bash +bin/magento security:tfa:google:set-secret <MAGENTO_ADMIN_USERNAME> <OTP_SHARED_SECRET> +``` + +## Configure the MFTF {#config-mftf-2fa} + +Save the same base32-encoded `secret` in a MFTF credential storage, e.g. `.credentials` file, `HashiCorp Vault` or `AWS Secrets Manager`. +More details are [here](./credentials.md). + +The path of the `secret` should be: + +```conf +magento/tfa/OTP_SHARED_SECRET +``` + +## GetOTP {#getOTP} + +A one-time password (OTP) is required when an admin user logs into the Magento admin. +Use the action `getOTP` [Reference](./test/actions.md#getotp) to generate the code and use it for the `Authenticator code` text field in 2FA - Google Auth page. + +Note: +You will need to set the `secret` for any non-default admin users first, before using `getOTP`. For example: + +{%raw%} + +```xml +<magentoCLI command="security:tfa:google:set-secret admin2 {{_CREDS.magento/tfa/OTP_SHARED_SECRET}}" stepKey="setSecret"/> +``` + +{%endraw%} diff --git a/docs/credentials.md b/docs/credentials.md index 3065454b0..dc77cdb39 100644 --- a/docs/credentials.md +++ b/docs/credentials.md @@ -1,21 +1,28 @@ +--- +title: Credentials +redirect_to: https://developer.adobe.com/commerce/testing/functional-testing-framework/credentials/ +status: migrated +--- + # Credentials -When you test functionality that involves external services such as UPS, FedEx, PayPal, or SignifyD, +When you test functionality that involves external services such as UPS, FedEx, PayPal, or SignifyD, use the MFTF credentials feature to hide sensitive [data][] like integration tokens and API keys. -Currently the MFTF supports two types of credential storage: +Currently MFTF supports three types of credential storage: - **.credentials file** -- **HashiCorp vault** +- **HashiCorp Vault** +- **AWS Secrets Manager** ## Configure File Storage -The MFTF creates a sample file for credentials during [initial setup][]: `magento2/dev/tests/acceptance/.credentials.example`. +MFTF creates a sample file for credentials during [initial setup][]: `magento2/dev/tests/acceptance/.credentials.example`. The file contains an example list of keys for fields that can require credentials. ### Create `.credentials` -To make the MFTF process the file with credentials, in the command line, navigate to `magento2/dev/tests/acceptance/` and rename `.credentials.example` to `.credentials`. +To make MFTF process the file with credentials, in the command line, navigate to `magento2/dev/tests/acceptance/` and rename `.credentials.example` to `.credentials`. ```bash cd dev/tests/acceptance/ @@ -53,9 +60,19 @@ magento/carriers_usps_password=Lmgxvrq89uPwECeV #magento/carriers_dhl_id_us=dhl_test_user #magento/carriers_dhl_password_us=Mlgxv3dsagVeG .... -``` +``` +### Add key and value pair for admin password . +magento/MAGENTO_ADMIN_PASSWORD must contain the user password required for authorization in the Admin area. Example: magento/MAGENTO_ADMIN_PASSWORD=mycustompassword -Or add new key & value pairs for your own credentials. The keys use the following format: +```conf +... +# Admin password +magento/MAGENTO_ADMIN_PASSWORD =123123q + +.... +``` + +Or add new key/value pairs for your own credentials. The keys use the following format: ```conf <vendor>/<key_name>=<key_value> @@ -64,7 +81,7 @@ Or add new key & value pairs for your own credentials. The keys use the followin <div class="bs-callout bs-callout-info" markdown="1"> The `/` symbol is not supported in a `key_name` other than the one after your vendor or extension name. </div> - + Otherwise you are free to use any other `key_name` you like, as they are merely the keys to reference from your tests. ```conf @@ -74,10 +91,10 @@ vendor/my_awesome_service_token=rRVSVnh3cbDsVG39oTMz4A ## Configure Vault Storage -Hashicorp vault secures, stores, and tightly controls access to data in modern computing. -It provides advanced data protection for your testing credentials. +Hashicorp vault secures, stores, and tightly controls access to data in modern computing. +It provides advanced data protection for your testing credentials. -The MFTF works with both `vault enterprise` and `vault open source` that use `KV Version 2` secret engine. +MFTF works with both `vault enterprise` and `vault open source` that use `KV Version 2` secret engine. ### Install vault CLI @@ -91,12 +108,12 @@ Authenticate to vault server via the vault CLI tool: [Login Vault][Login Vault]. vault login -method -path ``` -**Do not** use `-no-store` command option, as the MFTF will rely on the persisted token in the token helper (usually the local filesystem) for future API requests. +**Do not** use `-no-store` command option, as MFTF will rely on the persisted token in the token helper (usually the local filesystem) for future API requests. ### Store secrets in vault -The MFTF uses the `KV Version 2` secret engine for secret storage. -More information for working with `KV Version 2` can be found in [Vault KV2][Vault KV2]. +MFTF uses the `KV Version 2` secret engine for secret storage. +More information for working with `KV Version 2` can be found in [Vault KV2][Vault KV2]. #### Secrets path and key convention @@ -125,9 +142,9 @@ vault kv put secret/mftf/magento/carriers_usps_password carriers_usps_password=L ### Setup MFTF to use vault -Add vault configuration environment variables [`CREDENTIAL_VAULT_ADDRESS`][] and [`CREDENTIAL_VAULT_SECRET_BASE_PATH`][] +Add vault configuration environment variables [`CREDENTIAL_VAULT_ADDRESS`][] and [`CREDENTIAL_VAULT_SECRET_BASE_PATH`][] from `etc/config/.env.example` in `.env`. -Set values according to your vault server configuration. +Set values according to your vault server configuration. ```conf # Default vault dev server @@ -135,11 +152,100 @@ CREDENTIAL_VAULT_ADDRESS=http://127.0.0.1:8200 CREDENTIAL_VAULT_SECRET_BASE_PATH=secret ``` -## Configure both File Storage and Vault Storage +## Configure AWS Secrets Manager + +AWS Secrets Manager offers secret management that supports: +- Secret rotation with built-in integration for Amazon RDS, Amazon Redshift, and Amazon DocumentDB +- Fine-grained policies and permissions +- Audit secret rotation centrally for resources in the AWS Cloud, third-party services, and on-premises + +### Prerequisites + +#### Use AWS Secrets Manager from your own AWS account + +- An AWS account with Secrets Manager service +- An IAM user with AWS Secrets Manager access permission + +#### Use AWS Secrets Manager in CI/CD + +- AWS account ID where the AWS Secrets Manager service is hosted +- Authorized CI/CD EC2 instances with AWS Secrets Manager service access IAM role attached + +### Store secrets in AWS Secrets Manager + +#### Secrets format + +`Secret Name` and `Secret Value` are two key pieces of information for creating a secret. + +`Secret Value` can be either plaintext or key/value pairs in JSON format. + +`Secret Name` must use the following format: + +```conf +mftf/<VENDOR>/<YOUR/SECRET/KEY> +``` + +`Secret Value` can be stored in two different formats: plaintext or key/value pairs. + +For plaintext format, `Secret Value` can be any string you want to secure. + +For key/value pairs format, `Secret Value` is a key/value pair with `key` the same as `Secret Name` without `mftf/<VENDOR>/` prefix, which is `<YOUR/SECRET/KEY>`, and value can be any string you want to secure. + +##### Create Secrets using AWS CLI + +```bash +aws secretsmanager create-secret --name "mftf/magento/shipping/carriers_usps_userid" --description "Carriers USPS user id" --secret-string "1234567" +``` + +##### Create Secrets using AWS Console + +- Sign in to the AWS Secrets Manager console +- Choose Store a new secret +- In the Select secret type section, specify "Other type of secret" +- For `Secret Name`, `Secret Key` and `Secret Value` field, for example, to save the same secret in key/value JSON format, you should use + +```conf +# Secret Name +mftf/magento/shipping/carriers_usps_userid + +# Secret Key +shipping/carriers_usps_userid + +# Secret Value +1234567 +``` + +### Setup MFTF to use AWS Secrets Manager -It is possible and sometimes useful to setup and use both `.credentials` file and vault for secret storage at the same time. -In this case, the MFTF tests are able to read secret data at runtime from both storage options, but the local `.credentials` file will take precedence. +To use AWS Secrets Manager, the AWS region to connect to is required. You can set it through environment variable [`CREDENTIAL_AWS_SECRETS_MANAGER_REGION`][] in `.env`. +MFTF uses the recommended [Default Credential Provider Chain][credential chain] to establish connection to AWS Secrets Manager service. +You can setup credentials according to [Default Credential Provider Chain][credential chain] and there is no MFTF specific setup required. +Optionally, however, you can explicitly set AWS profile through environment variable [`CREDENTIAL_AWS_SECRETS_MANAGER_PROFILE`][] in `.env`. + +```conf +# Sample AWS Secrets Manager configuration +CREDENTIAL_AWS_SECRETS_MANAGER_REGION=us-east-1 +CREDENTIAL_AWS_SECRETS_MANAGER_PROFILE=default +``` + +### Optionally set CREDENTIAL_AWS_ACCOUNT_ID environment variable + +In case AWS credentials cannot resolve to a valid AWS account, full AWS KMS ([Key Management Service][]) key ARN ([Amazon Resource Name][]) is required. +You will also need to set `CREDENTIAL_AWS_ACCOUNT_ID` environment variable so that MFTF can construct the full ARN. This is mostly used for CI/CD. + +```bash +export CREDENTIAL_AWS_ACCOUNT_ID=<Your_12_Digits_AWS_Account_ID> +``` + +## Configure multiple credential storage + +It is possible and sometimes useful to setup and use multiple credential storage at the same time. +In this case, the MFTF tests are able to read secret data at runtime from all storage options. MFTF will use the following precedence: + +``` +.credentials File > HashiCorp Vault > AWS Secrets Manager +``` <!-- {% raw %} --> ## Use credentials in a test @@ -150,11 +256,12 @@ Define the value as a reference to the corresponding key in the credentials file - `_CREDS` is an environment constant pointing to the `.credentials` file - `my_data_key` is a key in the the `.credentials` file or vault that contains the value to be used in a test step + - for File Storage, ensure your key contains the vendor prefix, which is `vendor/my_data_key` -For example, reference secret data in the [`fillField`][] action with the `userInput` attribute. +For example, to reference secret data in the [`fillField`][] action, use the `userInput` attribute using a typical File Storage: ```xml -<fillField stepKey="FillApiToken" selector=".api-token" userInput="{{_CREDS.my_data_key}}" /> +<fillField stepKey="FillApiToken" selector=".api-token" userInput="{{_CREDS.vendor/my_data_key}}" /> ``` <!-- {% endraw %} --> @@ -162,7 +269,7 @@ For example, reference secret data in the [`fillField`][] action with the `userI ## Implementation details The generated tests do not contain credentials values. -The MFTF dynamically retrieves, encrypts, and decrypts the sensitive data during test execution. +MFTF dynamically retrieves, encrypts, and decrypts the sensitive data during test execution. Decrypted credentials do not appear in the console, error logs, or [test reports][]. The decrypted values are only available in the `.credentials` file or within vault. @@ -182,3 +289,8 @@ The MFTF tests delivered with Magento application do not use credentials and do [Vault KV2]: https://www.vaultproject.io/docs/secrets/kv/kv-v2.html [`CREDENTIAL_VAULT_ADDRESS`]: configuration.md#credential_vault_address [`CREDENTIAL_VAULT_SECRET_BASE_PATH`]: configuration.md#credential_vault_secret_base_path +[credential chain]: https://docs.aws.amazon.com/sdk-for-php/v3/developer-guide/guide_credentials.html +[`CREDENTIAL_AWS_SECRETS_MANAGER_PROFILE`]: configuration.md#credential_aws_secrets_manager_profile +[`CREDENTIAL_AWS_SECRETS_MANAGER_REGION`]: configuration.md#credential_aws_secrets_manager_region +[Key Management Service]: https://aws.amazon.com/kms/ +[Amazon Resource Name]: https://docs.aws.amazon.com/general/latest/gr/aws-arns-and-namespaces.html diff --git a/docs/custom-helpers.md b/docs/custom-helpers.md new file mode 100644 index 000000000..d6647705f --- /dev/null +++ b/docs/custom-helpers.md @@ -0,0 +1,135 @@ +--- +title: Custom helpers +redirect_to: https://developer.adobe.com/commerce/testing/functional-testing-framework/custom-helpers/ +status: migrated +--- + +# Custom Helpers + +<div class="bs-callout-warning"> +Due to complexity, you should only write new custom helpers as a last resort, after trying to implement your test using built-in actions. +</div> + +Custom Helpers allow test writers to write custom test actions to solve advanced requirements beyond what MFTF offers out of the box. + +In MFTF version 3.0.0, the following test actions were removed: + +* `<executeInSelenium>` +* `<performOn>` + +These actions were removed because they allowed custom PHP code to be written inline inside of XML files. This code was difficult to read. It had no proper syntax highlighting and no linting. It was difficult to maintain, troubleshoot, and modify. + +However, sometimes custom logic beyond what MFTF offers is necessary so we have provided an alternative solution: the `<helper>` action. + +## Example + +Custom helpers are implemented in PHP files that must be placed in this directory: + +```text +<ModuleName>/Test/Mftf/Helper +``` + +This custom helper selects text on the page with this approach: + +1. Move to a very specific X,Y starting position. +1. Click and hold the mouse button down. +1. Move to another specific X,Y position. +1. Release the mouse button. + +This functionality is used to select text on the page and cannot be accomplished using built-in test actions. + +### PHP File + +```php +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\PageBuilder\Test\Mftf\Helper; + +use Magento\FunctionalTestingFramework\Helper\Helper; + +/** + * Class SelectText provides an ability to select needed text. + */ +class SelectText extends Helper +{ + /** + * Select needed text. + * + * @param string $context + * @param int $startX + * @param int $startY + * @param int $endX + * @param int $endY + * @return void + */ + public function selectText(string $context, int $startX, int $startY, int $endX, int $endY) + { + try { + /** @var \Magento\FunctionalTestingFramework\Module\MagentoWebDriver $webDriver */ + $webDriver = $this->getModule('\Magento\FunctionalTestingFramework\Module\MagentoWebDriver'); + + $contextElement = $webDriver->webDriver->findElement(\Facebook\WebDriver\WebDriverBy::xpath($context)); + $actions = new \Facebook\WebDriver\Interactions\WebDriverActions($webDriver->webDriver); + $actions->moveToElement($contextElement, $startX, $startY) + ->clickAndHold() + ->moveToElement($contextElement, $endX, $endY) + ->release() + ->perform(); + } catch (\Exception $e) { + $this->fail($e->getMessage()); + } + } +} +``` + +### Notes about this PHP file + +The following details are important about the above file: + +1. The `namespace` must match the file location: `namespace Magento\PageBuilder\Test\Mftf\Helper;` +2. The class must `extends Helper` and have the corresponding `use` statement to match. +3. You may access the WebDriver object via `$this->getModule('\Magento\FunctionalTestingFramework\Module\MagentoWebDriver')`. +4. You may implement multiple related methods within the same class. +5. Specify the correct function argument types to match the type of values you want to pass in. In this case, we specified `string $context, int $startX, int $startY, int $endX, int $endY`. In the XML we will match these types. + +You should follow the same patterns in any custom helpers that you write yourself, but you may implement any logic or iteration that you need to solve for your use case. + +### Referencing in a test + +Once you have implemented something like the above PHP file, reference it in a test: + +```xml +<helper class="\Magento\PageBuilder\Test\Mftf\Helper\SelectText" method="selectText" stepKey="selectHeadingTextInTinyMCE"> + <argument name="context">//div[contains(@class, 'inline-wysiwyg')]//h2</argument> + <argument name="startX">{{TinyMCEPartialHeadingSelection.startX}}</argument> + <argument name="startY">{{TinyMCEPartialHeadingSelection.startY}}</argument> + <argument name="endX">{{TinyMCEPartialHeadingSelection.endX}}</argument> + <argument name="endY">{{TinyMCEPartialHeadingSelection.endY}}</argument> +</helper> +``` + +### Notes about the XML + +1. Specify an argument value for every argument that matches our PHP implementation. This allows us to pass other test data to the Custom Helper. +1. The `class` attribute matches the namespace specified in the PHP file. +1. Specify the method from the class via the `method` attribute. +1. If the function has a return value, it will be assigned to the `stepKey` variable. In this case, `$selectHeadingTextInTinyMCE` holds the return value. +1. The types of argument values must match the PHP implementation's expected types. + +## Key takeaways + +Custom helpers allow you to solve complex use cases such as conditional logic, iteration, or complex WebDriver usage. + +With access to the WebDriver object, you have a lot of flexibility available to you. See the [Codeception WebDriver](https://github.com/Codeception/module-webdriver/blob/master/src/Codeception/Module/WebDriver.php) for technical details and functionality available for use. + +A custom helper is written in a PHP file and then referenced in test XML, like other actions. + +You should only use these as a last resort after trying to implement your test using built-in actions. + +## References + +[Codeception WebDriver source code](https://github.com/Codeception/module-webdriver/blob/master/src/Codeception/Module/WebDriver.php) - Reference for using the WebDriver Object diff --git a/docs/data.md b/docs/data.md index 2a1b845d4..c555e2df1 100644 --- a/docs/data.md +++ b/docs/data.md @@ -1,12 +1,20 @@ +--- +title: Input testing data +redirect_to: https://developer.adobe.com/commerce/testing/functional-testing-framework/data/ +status: migrated +--- + # Input testing data -The MFTF enables you to specify and use `<data>` entities defined in XML. Default `<data>` entities are provided for use and as templates for entity creation and manipulation. +MFTF enables you to specify and use `<data>` entities defined in XML. Default `<data>` entities are provided for use and as templates for entity creation and manipulation. The following diagram shows the XML structure of an MFTF data object: ![MFTF Data Object](img/data-dia.svg) <!-- {% raw %} --> +The MFTF `<data>` entities are stored in `<module_dir>/Test/Mftf/Data/`. + ## Supply data to test by reference to a data entity Test steps requiring `<data>` input in an action, like filling a field with a string, may reference an attribute from a data entity: @@ -20,6 +28,20 @@ In this example: * `SimpleSubCategory` is an entity name. * `name` is a `<data>` key of the entity. The corresponding value will be assigned to `userInput` as a result. +The following is an example of the usage of `<data>` entity in the `Magento/Customer/Test/Mftf/Test/AdminCustomersAllCustomersNavigateMenuTest.xml` test: + +```xml +<actionGroup ref="AdminNavigateMenuActionGroup" stepKey="navigateToAllCustomerPage"> + <argument name="menuUiId" value="{{AdminMenuCustomers.dataUiId}}"/> + <argument name="submenuUiId" value="{{AdminMenuCustomersAllCustomers.dataUiId}}"/> +</actionGroup> +``` + +In the above example: + +* `AdminMenuCustomers` is an entity name. +* `dataUiId` is a `<data>` key of the entity. + ### Environmental data ```xml @@ -32,6 +54,12 @@ In this example: * `MAGENTO_ADMIN_USERNAME` is a name of an environment variable. The corresponding value will be assigned to `userInput` as a result. +The following is an example of the usage of `_ENV` in the `Magento/Braintree/Test/Mftf/ActionGroup/AdminDeleteRoleActionGroup.xml` action group: + +```xml +<fillField stepKey="TypeCurrentPassword" selector="{{AdminDeleteRoleSection.current_pass}}" userInput="{{_ENV.MAGENTO_ADMIN_PASSWORD}}"/> +``` + ### Sensitive data ```xml @@ -47,6 +75,14 @@ In this example: Learn more in [Credentials][]. +The following is an example of the usage of `_CREDS` in the `Magento/Braintree/Test/Mftf/Data/BraintreeData.xml` data entity: + +```xml +<entity name="MerchantId" type="merchant_id"> + <data key="value">{{_CREDS.magento/braintree_enabled_fraud_merchant_id}}</data> +</entity> +``` + ## Persist a data entity as a prerequisite of a test {#persist-data} A test can specify an entity to be persisted (created in the database) so that the test actions could operate on the existing known data. @@ -63,11 +99,19 @@ In this example: * `email` is a data key of the entity. The corresponding value will be assigned to `userInput` as a result. +The following is an example of the usage of the persistant data in `Magento/Customer/Test/Mftf/Test/AdminCreateCustomerWithCountryUSATest.xml` test: + +```xml +<actionGroup ref="AdminFilterCustomerByEmail" stepKey="filterTheCustomerByEmail"> + <argument name="email" value="$$createCustomer.email$$"/> +</actionGroup> +``` + <div class="bs-callout bs-callout-info"> As of MFTF 2.3.6, you no longer need to differentiate between scopes (a test, a hook, or a suite) for persisted data when referencing it in tests. </div> -The MFTF now stores the persisted data and attempts to retrieve it using the combination of `stepKey` and the scope of where it has been called. +MFTF now stores the persisted data and attempts to retrieve it using the combination of `stepKey` and the scope of where it has been called. The current scope is preferred, then widening to _test > hook > suite_ or _hook > test > suite_. This emphasizes the practice for the `stepKey` of `createData` to be descriptive and unique, as a duplicated `stepKey` in both a `<test>` and `<before>` prefers the `<test>` data. @@ -78,6 +122,8 @@ A test can also reference data that was returned as a result of [test actions][] Further in the test, the data grabbed by the `someSelector` selector can be referenced using the `stepKey` value. In this case, it is `grabStepKey`. +The `stepKey` value can only be referenced within the test scope that it is defined in (`test`, `before/after`). + The following example shows the usage of `grabValueFrom` in testing, where the returned value is used by action's `stepKey`: ```xml @@ -85,6 +131,16 @@ The following example shows the usage of `grabValueFrom` in testing, where the r <fillField selector=".functionalTestSelector" userInput="{$grabStepKey}" stepKey="fillFieldKey1"/> ``` +The following is an example of the `Magento/Catalog/Test/Mftf/ActionGroup/AssertDiscountsPercentageOfProductsActionGroup.xml` test: + +```xml +<grabValueFrom selector="{{AdminProductFormAdvancedPricingSection.productTierPricePercentageValuePriceInput('0')}}" stepKey="grabProductTierPriceInput"/> +<assertEquals stepKey="assertProductTierPriceInput"> + <expectedResult type="string">{{amount}}</expectedResult> + <actualResult type="string">$grabProductTierPriceInput</actualResult> +</assertEquals> +``` + ## Hard-coded data input The data to operate against can be included as literals in a test. Hard-coded data input can be useful in assertions. @@ -97,7 +153,7 @@ userInput="We'll email you an order confirmation with details and tracking info. ## Format -The format of `<data>` is: +The format of the `<data>` entity is: ```xml <?xml version="1.0" encoding="UTF-8"?> @@ -125,7 +181,7 @@ The following conventions apply to MFTF `<data>`: ## Example -Example (`.../Catalog/Data/CategoryData.xml` file): +Example (`Magento/Catalog/Test/Mftf/Data/CategoryData.xml` file): ```xml <?xml version="1.0" encoding="UTF-8"?> @@ -195,8 +251,9 @@ You can also call data from the xml definition of a `data` tag directly: Attributes|Type|Use|Description ---|---|---|--- -`name`|string|optional|Name of the `<entity>`. +`name`|string|optional|Name of the `<entity>`. Use camel case for entity names. `type`|string|optional|Node containing the exact name of `<entity>` type. Used later to find specific Persistence Layer Model class. `type` in `<data>` can be whatever the user wants; There are no constraints. It is important when persisting data, depending on the `type` given, as it will try to match a metadata definition with the operation being done. Example: A `myCustomer` entity with `type="customer"`, calling `<createData entity="myCustomer"/>`, will try to find a metadata entry with the following attributes: `<operation dataType="customer" type="create">`. +`deprecated`|string|optional|Used to warn about the future deprecation of the data entity. String will appear in Allure reports and console output at runtime. `<entity>` may contain one or more [`<data>`][], [`<var>`][], [`<required-entities>`][], or [`<array>`][] elements in any sequence. @@ -209,6 +266,12 @@ Attributes|Type|Use|Description `key`|string|optional|Key attribute of data/value pair. `unique`|enum: `"prefix"`, `"suffix"`|optional|Add suite or test wide unique sequence as "prefix" or "suffix" to the data value if specified. +Example: + +```xml +<data key="name" unique="suffix">simpleCategory</data> +``` + ### var {#var-tag} `<var>` is an element that can be used to grab a key value from another entity. For example, when creating a customer with the `<createData>` action, the server responds with the auto-incremented ID of that customer. Use `<var>` to access that ID and use it in another data entity. @@ -220,6 +283,12 @@ Attributes|Type|Use|Description `entityKey`|string|optional|Key attribute of the referenced entity from which to get a value. `unique`|--|--|*This attribute hasn't been implemented yet.* +Example: + +```xml +<var key="parent_id" entityType="category" entityKey="id" /> +``` + ### requiredEntity {#requiredentity-tag} `<requiredEntity>` is an element that specifies the parent/child relationship between complex types. @@ -265,6 +334,11 @@ Attributes|Type|Use|Description `<item>` is an individual piece of data to be passed in as part of the parent `<array>` type. +Attributes|Type|Use|Description +---|---|---|--- +`name`|string|optional|Key attribute of <item/> entity in which to assign a value. By default numeric key will be generated. + + <!-- Link Definitions --> [`<array>`]: #array-tag [`<data>`]: #data-tag @@ -272,6 +346,6 @@ Attributes|Type|Use|Description [`<required-entities>`]: #requiredentity-tag [`<var>`]: #var-tag [Actions]: ./test/actions.md -[category creation]: http://docs.magento.com/m2/ce/user_guide/catalog/category-create.html +[category creation]: https://docs.magento.com/user-guide/catalog/category-create.html [Credentials]: ./credentials.md [test actions]: ./test/actions.md#actions-returning-a-variable diff --git a/docs/debugging.md b/docs/debugging.md index be17e952a..ad4fe33ee 100644 --- a/docs/debugging.md +++ b/docs/debugging.md @@ -1,3 +1,9 @@ +--- +title: Debugging +redirect_to: https://developer.adobe.com/commerce/testing/functional-testing-framework/debugging/ +status: migrated +--- + # Debugging Debugging within the Magento Functional Testing Framework is helpful in identifying test bugs by allowing you to pause execution so that you may: diff --git a/docs/extending.md b/docs/extending.md index 91dc97c5d..9672c0732 100644 --- a/docs/extending.md +++ b/docs/extending.md @@ -1,3 +1,9 @@ +--- +title: Extending +redirect_to: https://developer.adobe.com/commerce/testing/functional-testing-framework/extending/ +status: migrated +--- + # Extending There are cases when you need to create many tests that are very similar to each other. @@ -15,31 +21,30 @@ Specify needed variations for a parent object and produce a copy of the original Unlike merging, the parent test (or action group) will still exist after the test generation. </div> +<div class="bs-callout-warning" markdown="1"> +<br> +Note: The extended test will be skipped if the parent test is skipped. +</div> + ## Extending tests ### Update a test step <!-- {% raw %} --> -__Use case__: Create two similar tests with different `url` (`"{{AdminCategoryPage.url}}"` and `"{{OtherCategoryPage.url}}"`) in a test step. +__Use case__: Create two similar tests with a different action group reference by overwriting a `stepKey`. > Test with "extends": ```xml -<tests > - <test name="AdminCategoryTest"> - <annotations> - ... - </annotations> - ...(several steps) - <amOnPage url="{{AdminCategoryPage.url}}" stepKey="navigateToAdminCategory"/> - ...(several steps) +<tests> + <test name="AdminLoginSuccessfulTest"> + <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> + <actionGroup ref="AssertAdminSuccessLoginActionGroup" stepKey="assertLoggedIn"/> + <actionGroup ref="AdminLogoutActionGroup" stepKey="logoutFromAdmin"/> </test> - <test name="OtherCategoryTest" extends="AdminCategoryTest"> - <annotations> - ... - </annotations> - <amOnPage url="{{OtherCategoryPage.url}}" stepKey="navigateToAdminCategory"/> + <test name="AdminLoginAsOtherUserSuccessfulTest" extends="AdminLoginSuccessfulTest"> + <actionGroup ref="AdminLoginAsOtherUserActionGroup" stepKey="loginAsAdmin"/> </test> </tests> ``` @@ -47,47 +52,36 @@ __Use case__: Create two similar tests with different `url` (`"{{AdminCategoryPa > Test without "extends": ```xml -<tests > - <test name="AdminCategoryTest"> - <annotations> - ... - </annotations> - ...(several steps) - <amOnPage url="{{AdminCategoryPage.url}}" stepKey="navigateToAdminCategory"/> - ...(several steps) +<tests> + <test name="AdminLoginSuccessfulTest"> + <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> + <actionGroup ref="AssertAdminSuccessLoginActionGroup" stepKey="assertLoggedIn"/> + <actionGroup ref="AdminLogoutActionGroup" stepKey="logoutFromAdmin"/> </test> - <test name="OtherCategoryTest"> - <annotations> - ... - </annotations> - ...(several steps) - <amOnPage url="{{OtherCategoryPage.url}}" stepKey="navigateToAdminCategory"/> - ...(several steps) + <test name="AdminLoginAsOtherUserSuccessfulTest"> + <actionGroup ref="AdminLoginAsOtherUserActionGroup" stepKey="loginAsAdmin"/> + <actionGroup ref="AssertAdminSuccessLoginActionGroup" stepKey="assertLoggedIn"/> + <actionGroup ref="AdminLogoutActionGroup" stepKey="logoutFromAdmin"/> </test> </tests> ``` ### Add a test step -__Use case__: Create two similar tests where the second test contains two additional steps: - -* `checkOption` before `click` (`stepKey="clickLogin"`) -* `seeInCurrentUrl` after `click` in the `LogInAsAdminTest` test (in the `.../Backend/Test/LogInAsAdminTest.xml` file) +__Use case__: Create two similar tests where the second test contains two additional steps specified to occur `before` or `after` other `stepKeys`. > Tests with "extends": ```xml -<tests > - <test name="LogInAsAdminTest"> - <amOnPage url="{{AdminLoginPage}}" stepKey="navigateToAdmin"/> - <fillField selector="{{AdminLoginFormSection.username}}" userInput="admin" stepKey="fillUsername"/> - <fillField selector="{{AdminLoginFormSection.password}}" userInput="password" stepKey="fillPassword"/> - <click selector="{{AdminLoginFormSection.signIn}}" stepKey="clickLogin"/> - <see userInput="Lifetime Sales" stepKey="seeLifetimeSales"/> +<tests> + <test name="AdminLoginSuccessfulTest"> + <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> + <actionGroup ref="AssertAdminSuccessLoginActionGroup" stepKey="assertLoggedIn"/> + <actionGroup ref="AdminLogoutActionGroup" stepKey="logoutFromAdmin"/> </test> - <test name="AlternativeLogInAsAdminTest" extends="LogInAsAdminTest"> - <checkOption selector="{{AdminLoginFormSection.rememberMe}}" stepKey="checkRememberMe" before="clickLogin"/> - <seeInCurrentUrl url="admin/admin/dashboard/" stepKey="seeAdminUrl" after="clickLogin"/> + <test name="AdminLoginCheckRememberMeSuccessfulTest" extends="AdminLoginSuccessfulTest"> + <actionGroup ref="AdminCheckRememberMeActionGroup" stepKey="checkRememberMe" after="loginAsAdmin"/> + <actionGroup ref="AssertAdminRememberMeActionGroup" stepKey="assertRememberMe" before="logoutFromAdmin"/> </test> </tests> ``` @@ -95,50 +89,40 @@ __Use case__: Create two similar tests where the second test contains two additi > Tests without "extends": ```xml -<tests > - <test name="LogInAsAdminTest"> - <amOnPage url="{{AdminLoginPage}}" stepKey="navigateToAdmin"/> - <fillField selector="{{AdminLoginFormSection.username}}" userInput="admin" stepKey="fillUsername"/> - <fillField selector="{{AdminLoginFormSection.password}}" userInput="password" stepKey="fillPassword"/> - <click selector="{{AdminLoginFormSection.signIn}}" stepKey="clickLogin"/> - <see userInput="Lifetime Sales" stepKey="seeLifetimeSales"/> +<tests> + <test name="AdminLoginSuccessfulTest"> + <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> + <actionGroup ref="AssertAdminSuccessLoginActionGroup" stepKey="assertLoggedIn"/> + <actionGroup ref="AdminLogoutActionGroup" stepKey="logoutFromAdmin"/> </test> - <test name="AlternativeLogInAsAdminTest"> - <amOnPage url="{{AdminLoginPage}}" stepKey="navigateToAdmin"/> - <fillField selector="{{AdminLoginFormSection.username}}" userInput="admin" stepKey="fillUsername"/> - <fillField selector="{{AdminLoginFormSection.password}}" userInput="password" stepKey="fillPassword"/> - <checkOption selector="{{AdminLoginFormSection.rememberMe}}" stepKey="checkRememberMe"/> - <click selector="{{AdminLoginFormSection.signIn}}" stepKey="clickLogin"/> - <seeInCurrentUrl url="admin/admin/dashboard/" stepKey="seeAdminUrl"/> - <see userInput="Lifetime Sales" stepKey="seeLifetimeSales"/> + <test name="AdminLoginCheckRememberMeSuccessfulTest"> + <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> + <actionGroup ref="AdminCheckRememberMeActionGroup" stepKey="checkRememberMe"/> + <actionGroup ref="AssertAdminSuccessLoginActionGroup" stepKey="assertLoggedIn"/> + <actionGroup ref="AssertAdminRememberMeActionGroup" stepKey="assertRememberMe"/> + <actionGroup ref="AdminLogoutActionGroup" stepKey="logoutFromAdmin"/> </test> </tests> ``` -### Update a test annotation - -__Use case__: Create two similar tests where the second one contains two additional actions in the `before` hook: +### Update a test before hook -* `checkOption` before `click` (`stepKey="clickLogin"`) -* `seeInCurrentUrl` after `click` in the `LogInAsAdminTest` test (in the `.../Backend/Test/LogInAsAdminTest.xml` file) +__Use case__: Create two similar tests where the second test contains an additional action in the `before` hook. > Tests with "extends": ```xml -<tests > - <test name="LogInAsAdminTest"> +<tests> + <test name="AdminLoginSuccessfulTest"> <before> - <amOnPage url="{{AdminLoginPage}}" stepKey="navigateToAdmin"/> - <fillField selector="{{AdminLoginFormSection.username}}" userInput="admin" stepKey="fillUsername"/> - <fillField selector="{{AdminLoginFormSection.password}}" userInput="password" stepKey="fillPassword"/> - <click selector="{{AdminLoginFormSection.signIn}}" stepKey="clickLogin"/> + <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> </before> - <see userInput="Lifetime Sales" stepKey="seeLifetimeSales"/> + <actionGroup ref="AssertAdminSuccessLoginActionGroup" stepKey="assertLoggedIn"/> + <actionGroup ref="AdminLogoutActionGroup" stepKey="logoutFromAdmin"/> </test> - <test name="AlternativeLogInAsAdminTest" extends="LogInAsAdminTest"> + <test name="AdminLoginCheckRememberMeSuccessfulTest" extends="AdminLoginSuccessfulTest"> <before> - <checkOption selector="{{AdminLoginFormSection.rememberMe}}" stepKey="checkRememberMe" before="clickLogin"/> - <seeInCurrentUrl url="admin/admin/dashboard/" stepKey="seeAdminUrl" after="clickLogin"/> + <actionGroup ref="AdminCheckRememberMeActionGroup" stepKey="checkRememberMe" after="loginAsAdmin"/> </before> </test> </tests> @@ -147,26 +131,21 @@ __Use case__: Create two similar tests where the second one contains two additio > Tests without "extends": ```xml -<tests > - <test name="LogInAsAdminTest"> +<tests> + <test name="AdminLoginSuccessfulTest"> <before> - <amOnPage url="{{AdminLoginPage}}" stepKey="navigateToAdmin"/> - <fillField selector="{{AdminLoginFormSection.username}}" userInput="admin" stepKey="fillUsername"/> - <fillField selector="{{AdminLoginFormSection.password}}" userInput="password" stepKey="fillPassword"/> - <click selector="{{AdminLoginFormSection.signIn}}" stepKey="clickLogin"/> + <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> </before> - <see userInput="Lifetime Sales" stepKey="seeLifetimeSales"/> + <actionGroup ref="AssertAdminSuccessLoginActionGroup" stepKey="assertLoggedIn"/> + <actionGroup ref="AdminLogoutActionGroup" stepKey="logoutFromAdmin"/> </test> - <test name="AlternativeLogInAsAdminTest"> + <test name="AdminLoginCheckRememberMeSuccessfulTest"> <before> - <amOnPage url="{{AdminLoginPage}}" stepKey="navigateToAdmin"/> - <fillField selector="{{AdminLoginFormSection.username}}" userInput="admin" stepKey="fillUsername"/> - <fillField selector="{{AdminLoginFormSection.password}}" userInput="password" stepKey="fillPassword"/> - <checkOption selector="{{AdminLoginFormSection.rememberMe}}" stepKey="checkRememberMe"/> - <click selector="{{AdminLoginFormSection.signIn}}" stepKey="clickLogin"/> - <seeInCurrentUrl url="admin/admin/dashboard/" stepKey="seeAdminUrl"/> + <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> + <actionGroup ref="AdminCheckRememberMeActionGroup" stepKey="checkRememberMe"/> </before> - <see userInput="Lifetime Sales" stepKey="seeLifetimeSales"/> + <actionGroup ref="AssertAdminSuccessLoginActionGroup" stepKey="assertLoggedIn"/> + <actionGroup ref="AdminLogoutActionGroup" stepKey="logoutFromAdmin"/> </test> </tests> ``` @@ -177,14 +156,14 @@ Extend an [action group] to add or update [actions] in your module. ### Update an action -__Use case__: The `CountProductA` test counts the particular product. +__Use case__: The `AssertAdminCountProductActionGroup` action group counts the particular product. Modify the action group to use another product. > Action groups with "extends": ```xml <actionGroups> - <actionGroup name="CountProductA"> + <actionGroup name="AssertAdminCountProductActionGroup"> <arguments> <argument name="count" type="string"/> </arguments> @@ -195,7 +174,7 @@ Modify the action group to use another product. </assertCount> </actionGroup> - <actionGroup name="CountProductB" extends="CountProductA"> + <actionGroup name="AssertAdminOtherCountProductActionGroup" extends="AssertAdminCountProductActionGroup"> <grabMultiple selector="selectorForProductB" stepKey="grabProducts"/> </actionGroup> </actionGroups> @@ -205,7 +184,7 @@ Modify the action group to use another product. ```xml <actionGroups> - <actionGroup name="CountProductA"> + <actionGroup name="AssertAdminCountProductActionGroup"> <arguments> <argument name="count" type="string"/> </arguments> @@ -216,7 +195,7 @@ Modify the action group to use another product. </assertCount> </actionGroup> - <actionGroup name="CountProductB"> + <actionGroup name="AssertAdminOtherCountProductActionGroup"> <arguments> <argument name="count" type="string"/> </arguments> @@ -231,21 +210,21 @@ Modify the action group to use another product. ### Add an action -__Use case__: The `GetProductCount` action group returns the count of products. -Add a new test `VerifyProductCount` that asserts the count of products: +__Use case__: The `AdminGetProductCountActionGroup` action group returns the count of products. +Add a new test `AssertAdminVerifyProductCountActionGroup` that asserts the count of products: > Action groups with "extends": ```xml <actionGroups> - <actionGroup name="GetProductCount"> + <actionGroup name="AdminGetProductCountActionGroup"> <arguments> <argument name="productSelector" type="string"/> </arguments> <grabMultiple selector="{{productSelector}}" stepKey="grabProducts"/> </actionGroup> - <actionGroup name="VerifyProductCount" extends="GetProductCount"> + <actionGroup name="AssertAdminVerifyProductCountActionGroup" extends="AdminGetProductCountActionGroup"> <arguments> <argument name="count" type="string"/> </arguments> @@ -261,14 +240,14 @@ Add a new test `VerifyProductCount` that asserts the count of products: ```xml <actionGroups> - <actionGroup name="GetProductCount"> + <actionGroup name="AdminGetProductCountActionGroup"> <arguments> <argument name="productSelector" type="string"/> </arguments> <grabMultiple selector="{{productSelector}}" stepKey="grabProducts"/> </actionGroup> - <actionGroup name="VerifyProductCount"> + <actionGroup name="AssertAdminVerifyProductCountActionGroup"> <arguments> <argument name="count" type="string"/> <argument name="productSelector" type="string"/> @@ -295,7 +274,7 @@ __Use case__: Create an entity named `DivPanelGreen`, which is similar to the `D > Entities with "extends": ```xml -<entities > +<entities> <entity name="DivPanel"> <data key="divColor">Red</data> <data key="divSize">80px</data> @@ -310,7 +289,7 @@ __Use case__: Create an entity named `DivPanelGreen`, which is similar to the `D > Entities without "extends": ```xml -<entities > +<entities> <entity name="DivPanel"> <data key="divColor">Red</data> <data key="divSize">80px</data> @@ -331,7 +310,7 @@ __Use case__: Create an entity named `DivPanelGreen`, which is similar to the `D > Entities with "extends": ```xml -<entities > +<entities> <entity name="DivPanel"> <data key="divColor">Red</data> <data key="divSize">80px</data> @@ -347,7 +326,7 @@ __Use case__: Create an entity named `DivPanelGreen`, which is similar to the `D > Entities without "extends": ```xml -<entities > +<entities> <entity name="DivPanel"> <data key="divColor">Red</data> <data key="divSize">80px</data> diff --git a/docs/getting-started.md b/docs/getting-started.md index 8f024b90d..efdd16dbc 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -1,12 +1,18 @@ +--- +title: Getting started +redirect_to: https://developer.adobe.com/commerce/testing/functional-testing-framework/getting-started/ +status: migrated +--- + # Getting started <div class="bs-callout bs-callout-info" markdown="1"> -[Find your MFTF version][] of the MFTF. -The latest Magento 2.3 release supports MFTF 2.3.13. -The latest Magento 2.2 release supports MFTF 2.3.8. +[Find your version] of MFTF. +The latest Magento 2.3.x release supports MFTF 2.6.4. +The latest Magento 2.2.x release supports MFTF 2.5.3. </div> -## Prepare environment {#prepare-environment} +## Prepare environment {#prepare-environment} Make sure that you have the following software installed and configured on your development environment: @@ -44,7 +50,7 @@ cd magento2/ ``` ```bash -git checkout 2.3-develop +git checkout 2.4-develop ``` Install the Magento application. @@ -53,18 +59,18 @@ Install the Magento application. composer install ``` -## Prepare Magento {#prepare-magento} +## Prepare Magento {#prepare-magento} Configure the following settings in Magento as described below. -### WYSIWYG settings {#wysiwyg-settings} +### WYSIWYG settings {#wysiwyg-settings} A Selenium web driver cannot enter data to fields with WYSIWYG. To disable the WYSIWYG and enable the web driver to process these fields as simple text areas: 1. Log in to the Magento Admin as an administrator. -2. Navigate to **Stores** > Settings > **Configuration** > **General** > **Content Management**. +2. Navigate to **Stores** > **Settings** > **Configuration** > **General** > **Content Management**. 3. In the WYSIWYG Options section set the **Enable WYSIWYG Editor** option to **Disabled Completely**. 4. Click **Save Config**. @@ -74,6 +80,12 @@ or via command line: bin/magento config:set cms/wysiwyg/enabled disabled ``` +Clean the cache after changing the configuration values: + +```bash +bin/magento cache:clean config full_page +``` + <div class="bs-callout bs-callout-tip"> When you want to test the WYSIWYG functionality, re-enable WYSIWYG in your test suite. </div> @@ -82,7 +94,7 @@ When you want to test the WYSIWYG functionality, re-enable WYSIWYG in your test To enable the **Admin Account Sharing** setting, to avoid unpredictable logout during a testing session, and disable the **Add Secret Key in URLs** setting, to open pages using direct URLs: -1. Navigate to **Stores** > Settings > **Configuration** > **Advanced** > **Admin** > **Security**. +1. Navigate to **Stores** > **Settings** > **Configuration** > **Advanced** > **Admin** > **Security**. 2. Set **Admin Account Sharing** to **Yes**. 3. Set **Add Secret Key to URLs** to **No**. 4. Click **Save Config**. @@ -97,11 +109,37 @@ bin/magento config:set admin/security/admin_account_sharing 1 bin/magento config:set admin/security/use_form_key 0 ``` +Clean the cache after changing the configuration values: + +```bash +bin/magento cache:clean config full_page +``` + +### Testing with the Magento Two-Factor Authentication (2FA) extension {#2fa} + +If the Magento instance under test has the [Magento Two-Factor Authentication (2FA) extension][] installed and enabled, additional configurations is needed to run MFTF tests. Learn more in [Configure MFTF for Magento with Two-Factor Authentication (2FA)](./configure-2fa.md). + +### Webserver configuration {#web-server-configuration} + +MFTF does not support executing CLI commands if your web server points to `<MAGE_ROOT_DIR>/pub` directory as recommended in the [Installation Guide][Installation Guide docroot]. For MFTF to execute the CLI commands, the web server must point to the Magento root directory. + ### Nginx settings {#nginx-settings} -If Nginx Web server is used on your development environment then **Use Web Server Rewrites** setting in **Stores** > Settings > **Configuration** > **Web** > **Search Engine Optimization** must be set to **Yes**. +If the Nginx Web server is used on your development environment, then **Use Web Server Rewrites** setting in **Stores** > Settings > **Configuration** > **General** > **Web** > **Search Engine Optimization** must be set to **Yes**. + +Or via command line: + +```bash +bin/magento config:set web/seo/use_rewrites 1 +``` + +You must clean the cache after changing the configuration values: -To be able to run Magento command line commands in tests add the following location block to Nginx configuration file: +```bash +bin/magento cache:clean config full_page +``` + +To be able to run Magento command line commands in tests, add the following location block to the Nginx configuration file in the Magento root directory: ```conf location ~* ^/dev/tests/acceptance/utils($|/) { @@ -117,11 +155,11 @@ location ~* ^/dev/tests/acceptance/utils($|/) { ## Set up an embedded MFTF {#setup-framework} -This is the default setup of the MFTF that you would need to cover your Magento project with functional tests. +This is the default setup of MFTF that you would need to cover your Magento project with functional tests. It installs the framework using an existing Composer dependency such as `magento/magento2-functional-testing-framework`. -If you want to set up the MFTF as a standalone tool, refer to [Set up a standalone MFTF][]. +If you want to set up MFTF as a standalone tool, refer to [Set up a standalone MFTF][]. -Install the MFTF. +Install MFTF. ```bash composer install @@ -138,13 +176,13 @@ vendor/bin/mftf build:project If you use PhpStorm, generate a URN catalog: ```bash -vendor/bin/mftf generate:urn-catalog .idea/ +vendor/bin/mftf generate:urn-catalog .idea/misc.xml ``` If the file does not exist, add the `--force` option to create it: ```bash -vendor/bin/mftf generate:urn-catalog --force .idea/ +vendor/bin/mftf generate:urn-catalog --force .idea/misc.xml ``` See [`generate:urn-catalog`][] for more details. @@ -173,8 +211,7 @@ Specify the following parameters, which are required to launch tests: - `MAGENTO_ADMIN_USERNAME` must contain the username required for authorization in the Admin area. Example: `MAGENTO_ADMIN_USERNAME=admin` -- `MAGENTO_ADMIN_PASSWORD` must contain the user password required for authorization in the Admin area. - Example: `MAGENTO_ADMIN_PASSWORD=123123q` +- `MAGENTO_ADMIN_PASSWORD` must now be set up in the credentials file. See [Credentials Page][] for details. <div class="bs-callout bs-callout-info" markdown="1"> If the `MAGENTO_BASE_URL` contains a subdirectory like `http://magento.test/magento2ce`, specify `MAGENTO_CLI_COMMAND_PATH`. @@ -184,7 +221,7 @@ Learn more about environmental settings in [Configuration][]. ### Step 3. Enable the Magento CLI commands -In the `magento2/dev/tests/acceptance` directory, run the following command to enable the MFTF to send Magento CLI commands to your Magento instance. +In the Magento project root, run the following command to enable MFTF to send Magento CLI commands to your Magento instance. ```bash cp dev/tests/acceptance/.htaccess.sample dev/tests/acceptance/.htaccess @@ -196,11 +233,11 @@ To run tests, you need a running Selenium server and [`mftf`][] commands. #### Run the Selenium server {#selenium-server} -Run the Selenium server in terminal. -For example, the following commands run the Selenium server for Google Chrome: +Run the Selenium server in the terminal. +For example, the following commands download and run the Selenium server for Google Chrome: ```bash -cd <path_to_directory_with_selenium_server_and_webdriver>/ +curl -O http://selenium-release.storage.googleapis.com/3.14/selenium-server-standalone-3.14.0.jar ``` ```bash @@ -213,10 +250,6 @@ java -Dwebdriver.chrome.driver=chromedriver -jar selenium-server-standalone-3.14 vendor/bin/mftf generate:tests ``` -```bash -cd dev/tests/acceptance -``` - ```bash vendor/bin/codecept run functional -c dev/tests/acceptance/codeception.yml ``` @@ -225,19 +258,17 @@ See more commands in [`codecept`][]. #### Run a simple test {#run-test} -To clean up the previously generated tests, and then generate and run a single test `AdminLoginTest`, run: +To clean up the previously generated tests, and then generate and run a single test `AdminLoginSuccessfulTest`, run: ```bash -vendor/bin/mftf run:test AdminLoginTest --remove +vendor/bin/mftf run:test AdminLoginSuccessfulTest --remove ``` See more commands in [`mftf`][]. ### Step 5. Generate reports {#reports} -During testing, the MFTF generates test reports in CLI. -You can generate visual representations of the report data using [Allure Framework][]. -To view the reports in GUI: +During testing, MFTF generates test reports in CLI. You can generate visual representations of the report data using the [Allure Framework][]. To view the reports in a GUI: - [Install Allure][] - Run the tool to serve the artifacts in `dev/tests/acceptance/tests/_output/allure-results/`: @@ -250,18 +281,16 @@ Learn more about Allure in the [official documentation][allure docs]. ## Set up a standalone MFTF -The MFTF is a root level Magento dependency, but it is also available for use as a standalone application. -You may want to use a standalone application when you develop for or contribute to MFTF, which facilitates debugging and tracking changes. -These guidelines demonstrate how to set up and run Magento acceptance functional tests using standalone MFTF. +MFTF is a root level Magento dependency, but it is also available for use as a standalone application. You may want to use a standalone application when you develop for or contribute to MFTF, which facilitates debugging and tracking changes. These guidelines demonstrate how to set up and run Magento acceptance functional tests using standalone MFTF. ### Prerequisites This installation requires a local instance of the Magento application. -The MFTF uses the [tests from Magento modules][mftf tests] as well as the `app/autoload.php` file. +MFTF uses the [tests from Magento modules][mftf tests] as well as the `app/autoload.php` file. ### Step 1. Clone the MFTF repository -If you develop or contribute to the MFTF, it makes sense to clone your fork of the MFTF repository. +If you develop or contribute to MFTF, it makes sense to clone your fork of the MFTF repository. For contribution guidelines, refer to the [Contribution Guidelines for the Magento Functional Testing Framework][contributing]. ### Step 2. Install the MFTF @@ -291,7 +320,7 @@ Create the `utils/` directory, if you didn't find it. ### Step 6. Remove the MFTF package dependency in Magento -The MFTF uses the Magento `app/autoload.php` file to read Magento modules. +MFTF uses the Magento `app/autoload.php` file to read Magento modules. The MFTF dependency in Magento supersedes the standalone registered namespaces unless it is removed at a Composer level. ```bash @@ -303,7 +332,7 @@ composer remove magento/magento2-functional-testing-framework --dev -d <path to Generate and run a single test that will check your logging to the Magento Admin functionality: ```bash -bin/mftf run:test AdminLoginTest +bin/mftf run:test AdminLoginSuccessfulTest ``` You can find the generated test at `dev/tests/functional/tests/MFTF/_generated/default/`. @@ -324,7 +353,7 @@ allure serve dev/tests/_output/allure-results/ [`MAGENTO_BP`]: configuration.html#magento_bp [`mftf`]: commands/mftf.html [allure docs]: https://docs.qameta.io/allure/ -[Allure Framework]: http://allure.qatools.ru/ +[Allure Framework]: https://github.com/allure-framework [basic configuration]: configuration.html#basic-configuration [chrome driver]: https://sites.google.com/a/chromium.org/chromedriver/downloads [Codeception Test execution]: https://blog.jetbrains.com/phpstorm/2017/03/codeception-support-comes-to-phpstorm-2017-1/ @@ -332,11 +361,14 @@ allure serve dev/tests/_output/allure-results/ [Configuration]: configuration.html [contributing]: https://github.com/magento/magento2-functional-testing-framework/blob/develop/.github/CONTRIBUTING.md [install Allure]: https://github.com/allure-framework/allure2#download -[java]: http://www.oracle.com/technetwork/java/javase/downloads/index.html +[java]: https://www.oracle.com/java/technologies/downloads/ [mftf tests]: introduction.html#mftf-tests -[php]: https://devdocs.magento.com/guides/v2.3/install-gde/system-requirements-tech.html#php +[php]: https://devdocs.magento.com/guides/v2.4/install-gde/system-requirements.html [PhpStorm]: https://www.jetbrains.com/phpstorm/ [selenium server]: https://www.seleniumhq.org/download/ [Set up a standalone MFTF]: #set-up-a-standalone-mftf [test suite]: suite.html -[Find your MFTF version]: introduction.html#find-your-mftf-version +[Find your version]: introduction.html#find-your-mftf-version +[Installation Guide docroot]: https://devdocs.magento.com/guides/v2.4/install-gde/tutorials/change-docroot-to-pub.html +[Magento Two-Factor Authentication (2FA) extension]: https://devdocs.magento.com/guides/v2.4/security/two-factor-authentication.html +[Credentials Page]: https://devdocs.magento.com/mftf/docs/credentials.html diff --git a/docs/guides/action-groups.md b/docs/guides/action-groups.md new file mode 100644 index 000000000..354ae707d --- /dev/null +++ b/docs/guides/action-groups.md @@ -0,0 +1,88 @@ +--- +title: Action group best practices +redirect_to: https://developer.adobe.com/commerce/testing/functional-testing-framework/test/action-group-best-practices/ +status: migrated +--- + +# Action Group Best Practices + +We strive to write tests using only action groups. Fortunately, we have built up a large set of action groups to get started. We can make use of them and extend them for our own specific needs. In some cases, we may never even need to write action groups of our own. We may be able to simply chain together calls to existing action groups to implement our new test case. + +## Why use Action Groups? + +Action groups simplify maintainability by reducing duplication. Because they are re-usable building blocks, odds are that they are already made use of by existing tests in the Magento codebase. This proves their stability through real-world use. Take for example, the action group named `LoginAsAdmin`: + +```xml +<actionGroup name="LoginAsAdmin"> + <annotations> + <description>Login to Backend Admin using provided User Data. PLEASE NOTE: This Action Group does NOT validate that you are Logged In.</description> + </annotations> + <arguments> + <argument name="adminUser" type="entity" defaultValue="DefaultAdminUser"/> + </arguments> + + <amOnPage url="{{AdminLoginPage.url}}" stepKey="navigateToAdmin"/> + <fillField selector="{{AdminLoginFormSection.username}}" userInput="{{adminUser.username}}" stepKey="fillUsername"/> + <fillField selector="{{AdminLoginFormSection.password}}" userInput="{{adminUser.password}}" stepKey="fillPassword"/> + <click selector="{{AdminLoginFormSection.signIn}}" stepKey="clickLogin"/> + <closeAdminNotification stepKey="closeAdminNotification"/> +</actionGroup> +``` + +Logging in to the admin panel is one of the most used action groups. It is used around 1,500 times at the time of this writing. + +Imagine if this was not an action group and instead we were to copy and paste these 5 actions every time. In that scenario, if a small change was needed, it would require a lot of work. But with the action group, we can make the change in one place. + +## How to extend action groups + +Again using `LoginAsAdmin` as our example, we trim away metadata to clearly reveal that this action group performs 5 actions: + +```xml +<actionGroup name="LoginAsAdmin"> + ... + <amOnPage url="{{AdminLoginPage.url}}" .../> + <fillField selector="{{AdminLoginFormSection.username}}" .../> + <fillField selector="{{AdminLoginFormSection.password}}" .../> + <click selector="{{AdminLoginFormSection.signIn}}" .../> + <closeAdminNotification .../> +</actionGroup> +``` + +This works against the standard Magento admin panel login page. Bu imagine we are working on a Magento extension that adds a CAPTCHA field to the login page. If we create and activate this extension and then run all existing tests, we can expect almost everything to fail because the CAPTCHA field is left unfilled. + +We can overcome this by making use of MFTF's extensibility. All we need to do is to provide a "merge" that modifies the existing `LoginAsAdmin` action group. Our merge file will look like: + +```xml +<actionGroup name="LoginAsAdmin"> + <fillField selector="{{CaptchaSection.captchaInput}}" before="signIn" .../> +</actionGroup> +``` + +Because the name of this merge is also `LoginAsAdmin`, the two get merged together and an additional step happens everytime this action group is used. + +To continue this example, imagine someone else is working on a 'Two-Factor Authentication' extension and they also provide a merge for the `LoginAsAdmin` action group. Their merge looks similar to what we have already seen. The only difference is that this time we fill a different field: + +```xml +<actionGroup name="LoginAsAdmin"> + <fillField selector="{{TwoFactorSection.twoFactorInput}}" before="signIn" .../> +</actionGroup> +``` + +Bringing it all together, our resulting `LoginAsAdmin` action group becomes this: + +```xml +<actionGroup name="LoginAsAdmin"> + ... + <amOnPage url="{{AdminLoginPage.url}}" .../> + <fillField selector="{{AdminLoginFormSection.username}}" .../> + <fillField selector="{{AdminLoginFormSection.password}}" .../> + <fillField selector="{{CaptchaSection.captchaInput}}" .../> + <fillField selector="{{TwoFactorSection.twoFactorInput}}" .../> + <click selector="{{AdminLoginFormSection.signIn}}" .../> + <closeAdminNotification .../> +</actionGroup> +``` + +No one file contains this exact content as above, but instead all three files come together to form this action group. + +This extensibility can be applied in many ways. We can use it to affect existing Magento entities such as tests, action groups, and data. Not so obvious is that this tehcnique can be used within your own entities to make them more maintainable as well. diff --git a/docs/guides/cicd.md b/docs/guides/cicd.md new file mode 100644 index 000000000..985a5123f --- /dev/null +++ b/docs/guides/cicd.md @@ -0,0 +1,120 @@ +--- +title: How to use MFTF in CICD +redirect_to: https://developer.adobe.com/commerce/testing/functional-testing-framework/cicd/ +status: migrated +--- + +# How to use MFTF in CICD + +To integrate MFTF tests into your CICD pipeline, it is best to start with the conceptual flow of the pipeline code. + +## Concept + +The overall workflow that tests should follow is: + +- Obtain a Magento instance + install pre-requisites. +- Generate the tests. + - Set options for single or parallel running. +- Delegate and run tests and gather test-run artifacts. + - Re-run options. +- Generate the Allure reports from the results. + +## Obtain a Magento instance + +To start, we need a Magento instance to operate against for test generation and execution. + +```bash +git clone https://github.com/magento/magento2 +``` + +or + +```bash +composer create-project --repository=https://repo.magento.com/ magento/project-community-edition magento2ce +``` + +For more information on installing magento see [Install Magento using Composer][]. + +After installing the Magento instance, set a couple of configurations to the Magento instance: + +```bash +bin/magento config:set general/locale/timezone America/Los_Angeles +bin/magento config:set admin/security/admin_account_sharing 1 +bin/magento config:set admin/security/use_form_key 0 +bin/magento config:set cms/wysiwyg/enabled disabled +``` + +These set the default state of the Magento instance. If you wish to change the default state of the application (and have updated your tests sufficiently to account for it), this is the step to do it. + +If your magento instance has Two-Factor Authentication enabled, see [Configure 2FA][] to configure MFTF tests. + +## Install Allure + +This is required for generating the report after your test runs. See [Allure][] for details. + +## Generate tests + +### Single execution + +Generate tests based on what you want to run: + +```bash +vendor/bin/mftf generate:tests +``` + +This will generate all tests and a single manifest file under `dev/tests/acceptance/tests/functional/Magento/_generated/testManifest.txt`. + +### Parallel execution + +To generate all tests for use in parallel nodes: + +```bash +vendor/bin/mftf generate:tests --config parallel +``` + +This generates a folder under `dev/tests/acceptance/tests/functional/Magento/_generated/groups`. This folder contains several `group#.txt` files that can be used later with the `mftf run:manifest` command. + +## Delegate and run tests + +### Single execution + +If you are running on a single node, call: + +```bash +vendor/bin/mftf run:manifest dev/tests/acceptance/tests/functional/Magento/_generated/testManifest.txt +``` + +### Parallel execution + +You can optimize your pipeline by running tests in parallel across multiple nodes. + +Tests can be split up into roughly equal running groups using `--config parallel`. + +You do not want to perform installations on each node again and build it. So, to save time, stash pre-made artifacts from earlier steps and un-stash on the nodes. + +The groups can be then distributed on each of the nodes and run separately in an isolated environment. + +- Stash artifacts from main node and un-stash on current node. +- Run `vendor/bin/mftf run:manifest <current_group.txt>` on current node. +- Gather artifacts from `dev/tests/acceptance/tests/_output` from current node to main node. + +### Rerun options + +In either single or parallel execution, to re-run failed tests, simply add the `run:failed` command after executing a manifest: + +```bash +vendor/bin/mftf run:failed +``` + +### Generate Allure report + +In the main node, generate reports using your `<path_to_results>` into a desired output path: + +```bash +allure generate <path_to_results> -c -o <path_to_output> +``` + +<!-- Link definitions --> +[Install Magento using Composer]: https://devdocs.magento.com/guides/v2.4/install-gde/composer.html +[Configure 2FA]: ../configure-2fa.md +[Allure]: https://docs.qameta.io/allure/ diff --git a/docs/guides/git-vs-composer-install.md b/docs/guides/git-vs-composer-install.md new file mode 100644 index 000000000..bf24ed219 --- /dev/null +++ b/docs/guides/git-vs-composer-install.md @@ -0,0 +1,89 @@ +--- +title: Git vs Composer installation of Magento with MFTF +redirect_to: https://developer.adobe.com/commerce/testing/functional-testing-framework/git-vs-composer-install/ +status: migrated +--- + +# Git vs Composer installation of Magento with MFTF + +Depending on how you plan to use Magnto code, there are different options for installing Magento. + +## GitHub Installation + +If you are contributing a pull request to the Magento 2 codebase, download Magento 2 from our GitHub repository. Contribution to the codebase is done using the 'fork and pull' model where contributors maintain their own fork of the repo. This repo is then used to submit a pull request to the base repo. + +Install guide: [GitHub Installation][] + +## Composer based Installation + +A Composer install downloads released packages of Magento 2 from the composer repo [https://repo.magento.com](https://repo.magento.com). + +All Magento modules and their MFTF tests are put under `<vendor>` directory, for convenience of 3rd party developers. With this setup, you can keep your custom modules separate from core modules. You can also develop modules in a separate VCS repository and add them to your `composer.json` which installs them into the `vendor` directory. + +Install guide: [Composer based Installation][] + +## MFTF Installation + +After installing your Magento project in either of the above ways, the composer dependency `magento/magento2-functional-testing-framework` downloads and installs MFTF. MFTF is embedded in your Magento 2 installation and will cover your project with functional tests. + +If you want to contribute a pull request into MFTF codebase, you will need to install MFTF in the [Standalone][] mode. + +## Managing modules - Composer vs GitHub + +### Via GitHub + +Cloning the Magento 2 git repository is a way of installing where you do not have to worry about matching your codebase with production. Your version control system generally holds and manages your `app/code` folder and you can do manual, ad-hoc development here. + +### Via Composer + +Magento advocates the use of composer for managing modules. When you install a module through composer, it is added to `vendor/<vendor-name>/<module>`. + +When developing your own module or adding MFTF tests to a module, you should not edit in `vendor` because a composer update could overwrite your changes. Instead, overwrite a module under `vendor` by adding files or cloning your module-specific Git repo to `app/code/<vendor-name>/<module>`. + +To distribute the module and its tests, you can initialize a git repo and create a [composer package][]. In this way others will be able to download and install your module and access your tests as a composer package, in their `<vendor>` folder. + +## MFTF test materials location + +- For GitHub installations, MFTF test materials are located in `<magento_root>/app/code/<vendor_name>/<module_name>/Test/Mftf/`. This is the directory for new tests or to maintain existing ones. +- For Composer-based installations, MFTF test materials are located at `<magento_root>/vendor/<vendor_name>/<module_name>/Test/Mftf/`. This is the directory to run tests fetched by Composer. + +The file structure under both paths is the same: + +```tree +<Path> +├── ActionGroup +│   └── ... +├── Data +│   └── ... +├── Metadata +│   └── ... +├── Page +│   └── ... +├── Section +│   └── ... +├── Suite +│   └── ... +└── Test + └── ... +``` + +## How ModuleResolver reads modules + +With either type of installation, all tests and test data are read and merged by MFTF's ModuleResolver in this order: + +1. `<magento_root>/app/code/<vendor_name>/<module_name>/Test/Mftf/` +1. `<magento_root>/vendor/<vendor_name>/<module_name>/Test/Mftf/` +1. `<magento_root>/dev/tests/acceptance/tests/functional/<vendor_name>/<module_name>/` + +## Conclusion + +There is no difference between having the test materials in `app/code` or in `/vendor`: it works the same. Composer-based installs may benefit teams when there is a need to match file systems in `development` and `production`. + +If you are a contributing developer with an understanding of Git and Composer commands, you can choose the GitHub installation method instead. + +<!-- Link definitions --> + +[Composer based Installation]: https://devdocs.magento.com/guides/v2.3/install-gde/composer.html +[GitHub Installation]: https://devdocs.magento.com/guides/v2.3/install-gde/prereq/dev_install.html +[Standalone]: ../getting-started.html#set-up-a-standalone-mftf +[composer package]: https://devdocs.magento.com/guides/v2.3/extension-dev-guide/package/package_module.html diff --git a/docs/guides/selectors.md b/docs/guides/selectors.md index 3e019bb75..cdbfa5fcc 100644 --- a/docs/guides/selectors.md +++ b/docs/guides/selectors.md @@ -1,3 +1,9 @@ +--- +title: How to write good selectors +redirect_to: https://developer.adobe.com/commerce/testing/functional-testing-framework/test-writing/selectors/ +status: migrated +--- + # How To write good selectors Selectors are the atomic unit of test writing. They fit into the hierarchy like this: MFTF tests make use of action groups > which are made up of actions > which interact with page objects > which contain elements > which are specified by selectors. Because they are fundamental building blocks, we must take care when writing them. @@ -208,14 +214,16 @@ Here is an example of what NOT to do, but this demonstrates how the selector wor In the BAD example above, we are specifying a very precise path to an input element in the DOM, starting from the very top of the document. -Similarly, the relative XPath selector is a double forward slash `//`. It is used to start searching for an element anywhere in the DOM. +Similarly, the relative XPath selector is a double forward slash `//`. It is used to start searching for an element anywhere in the DOM starting from the specified element. If no element is defined, the entire DOM is searched. Example: ```xpath -//div[@class=’form-group’]//input[@id='user-message'] +//div[@class='form-group']//input[@id='user-message'] ``` +In the `GOOD` example above, all `<div class='form-group'/>` elements in the DOM are matched first. Then all `<input id='user-message'/>` with `<div class='form-group'/>` as one of its parents are matched. The parent does not have to immediately precede it since it uses another double forward slash `//`. + #### Parent Selectors The parent selector (`..`) allows you to jump to the parent element. @@ -245,7 +253,7 @@ Given this HTML: ```html <tr> <td> - <a href=“#”>Edit</a> + <a href="#">Edit</a> </td> <td> <div>Unique Value</div> diff --git a/docs/guides/test-isolation.md b/docs/guides/test-isolation.md index 474867ff2..64a04a152 100644 --- a/docs/guides/test-isolation.md +++ b/docs/guides/test-isolation.md @@ -1,3 +1,9 @@ +--- +title: Test isolation +redirect_to: https://developer.adobe.com/commerce/testing/functional-testing-framework/test-writing/test-isolation/ +status: migrated +--- + # Test Isolation Because MFTF is a framework for testing a highly customizable and ever changing application, MFTF tests need to be properly isolated. diff --git a/docs/guides/test-modularity.md b/docs/guides/test-modularity.md index ed182313e..e9a407c3b 100644 --- a/docs/guides/test-modularity.md +++ b/docs/guides/test-modularity.md @@ -1,3 +1,9 @@ +--- +title: Test modularity +redirect_to: https://developer.adobe.com/commerce/testing/functional-testing-framework/test-writing/test-modularity/ +status: migrated +--- + # Test Modularity One of MFTF's most distinguishing functionalities is the framework's modularity. diff --git a/docs/guides/using-suites.md b/docs/guides/using-suites.md index 99413cb78..ea5f4c924 100644 --- a/docs/guides/using-suites.md +++ b/docs/guides/using-suites.md @@ -1,3 +1,9 @@ +--- +title: Using suites +redirect_to: https://developer.adobe.com/commerce/testing/functional-testing-framework/test-writing/using-suites/ +status: migrated +--- + # Using suites With an increasing number of MFTF tests, it is important to have a mechanism to organize and consolidate them for ease-of-use. diff --git a/docs/img/action-groups-dia.svg b/docs/img/action-groups-dia.svg index 853420d54..9fbe68886 100644 --- a/docs/img/action-groups-dia.svg +++ b/docs/img/action-groups-dia.svg @@ -1,516 +1 @@ -<?xml version="1.0" encoding="UTF-8" standalone="no"?> -<svg - xmlns:dc="http://purl.org/dc/elements/1.1/" - xmlns:cc="http://creativecommons.org/ns#" - xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" - xmlns:svg="http://www.w3.org/2000/svg" - xmlns="http://www.w3.org/2000/svg" - xmlns:xlink="http://www.w3.org/1999/xlink" - xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" - xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" - width="643" - height="114" - version="1.1" - id="svg152" - sodipodi:docname="action-groups-dia.svg" - inkscape:version="0.92.2 (5c3e80d, 2017-08-06)"> - <metadata - id="metadata158"> - <rdf:RDF> - <cc:Work - rdf:about=""> - <dc:format>image/svg+xml</dc:format> - <dc:type - rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> - </cc:Work> - </rdf:RDF> - </metadata> - <defs - id="defs156" /> - <sodipodi:namedview - pagecolor="#ffffff" - bordercolor="#666666" - borderopacity="1" - objecttolerance="10" - gridtolerance="10" - guidetolerance="10" - inkscape:pageopacity="0" - inkscape:pageshadow="2" - inkscape:window-width="1920" - inkscape:window-height="1017" - id="namedview154" - showgrid="false" - inkscape:zoom="1.2492586" - inkscape:cx="149.25073" - inkscape:cy="58.965954" - inkscape:window-x="3278" - inkscape:window-y="-8" - inkscape:window-maximized="1" - inkscape:current-layer="svg152" /> - <a - href="actions.html" - title="any actions" - id="a3851"> - <g - id="g242"> - <path - d="M404 25 L498 25 L504 31 L504 41 L498 47 L404 47 L398 41 L398 31 Z" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1" - id="path2" /> - <text - x="451" - y="36" - style="font-family:Arial;font-size:8pt;fill:rgb(0,0,0);font-weight:bold;text-anchor:middle;dominant-baseline:central" - id="text4">actionTypeTags</text> - <line - x1="401" - y1="44" - x2="404" - y2="41" - style="stroke:rgb(0,0,0);stroke-width:2" - id="line6" /> - <path - d="M404 41 L406 43 L407 38 L402 39 L404 41 Z" - style="fill:rgb(0,0,0)" - id="path8" /> - <rect - x="499" - y="31" - width="10" - height="10" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1" - id="rect10" /> - <line - x1="501" - y1="36" - x2="507" - y2="36" - style="stroke:rgb(0,0,0);stroke-width:1" - id="line12" /> - <line - x1="504" - y1="33" - x2="504" - y2="39" - style="stroke:rgb(0,0,0);stroke-width:1" - id="line14" /> - </g> - </a> - <a - id="a3924" - href="#argument-tag" - title="zero or more <argument> elements"> - <g - id="g233"> - <rect - x="566" - y="80" - width="70" - height="22" - style="fill:#ffffff;stroke:#000000;stroke-width:1;stroke-dasharray:4, 1" - id="rect16" /> - <rect - x="563" - y="77" - width="70" - height="22" - style="fill:#ffffff;stroke:#000000;stroke-width:1;stroke-dasharray:4, 1" - id="rect18" /> - <text - x="598" - y="88" - style="font-weight:bold;font-size:10.66666698px;font-family:Arial;dominant-baseline:central;text-anchor:middle;fill:#000000" - id="text20">argument</text> - <text - x="623" - y="109" - style="font-size:9.59999943px;font-family:Arial;dominant-baseline:central;text-anchor:end;fill:#000000" - id="text22">0..∞</text> - </g> - </a> - <line - x1="543" - y1="88" - x2="563" - y2="88" - style="stroke:rgb(0,0,0);stroke-width:1;stroke-linecap:round" - id="line24" /> - <a - id="a3914" - title="contains a sequence of"> - <g - id="g227"> - <path - d="M509 78 L532 78 L538 84 L538 92 L532 98 L509 98 L503 92 L503 84 Z" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1" - id="path26" /> - <line - x1="506" - y1="88" - x2="535" - y2="88" - style="stroke:rgb(0,0,0);stroke-width:1" - id="line28" /> - <ellipse - cx="515" - cy="88" - rx="2" - ry="2" - style="rgb(0,0,0)" - id="ellipse30" /> - <ellipse - cx="520" - cy="88" - rx="2" - ry="2" - style="rgb(0,0,0)" - id="ellipse32" /> - <ellipse - cx="525" - cy="88" - rx="2" - ry="2" - style="rgb(0,0,0)" - id="ellipse34" /> - <rect - x="533" - y="83" - width="10" - height="10" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1" - id="rect36" /> - <line - x1="535" - y1="88" - x2="541" - y2="88" - style="stroke:rgb(0,0,0);stroke-width:1" - id="line38" /> - </g> - </a> - <line - x1="483" - y1="88" - x2="503" - y2="88" - style="stroke:rgb(0,0,0);stroke-width:1;stroke-linecap:round" - id="line40" /> - <a - id="a3907" - href="#arguments-tag" - title="wrapping element <arguments>"> - <g - id="g218"> - <rect - x="398" - y="77" - width="80" - height="22" - style="fill:#ffffff;stroke:#000000;stroke-width:1" - id="rect42" /> - <text - x="438" - y="88" - style="font-weight:bold;font-size:10.66666698px;font-family:Arial;dominant-baseline:central;text-anchor:middle;fill:#000000" - id="text44">arguments</text> - <rect - x="473" - y="83" - width="10" - height="10" - style="fill:#ffffff;stroke:#000000;stroke-width:1" - id="rect46" /> - <line - x1="475" - y1="88" - x2="481" - y2="88" - style="stroke:#000000;stroke-width:1" - id="line48" /> - </g> - </a> - <line - x1="388" - y1="36" - x2="398" - y2="36" - style="stroke:rgb(0,0,0);stroke-width:1;stroke-linecap:round" - id="line50" /> - <line - x1="388" - y1="88" - x2="398" - y2="88" - style="stroke:rgb(0,0,0);stroke-width:1;stroke-linecap:round" - id="line52" /> - <line - x1="388" - y1="36" - x2="388" - y2="88" - style="stroke:rgb(0,0,0);stroke-width:1;stroke-linecap:round" - id="line54" /> - <a - id="a3833" - title="optionally contains muiliple times one of the following nodes"> - <g - id="g212"> - <line - x1="378" - y1="62" - x2="388" - y2="62" - style="stroke:rgb(0,0,0);stroke-width:1;stroke-linecap:round" - id="line56" /> - <path - d="M347 55 L370 55 L376 61 L376 69 L370 75 L347 75 L341 69 L341 61 Z" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1;stroke-dasharray:4,1" - id="path58" /> - <path - d="M344 52 L367 52 L373 58 L373 66 L367 72 L344 72 L338 66 L338 58 Z" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1;stroke-dasharray:4,1" - id="path60" /> - <line - x1="343" - y1="62" - x2="347" - y2="62" - style="stroke:rgb(0,0,0);stroke-width:1" - id="line62" /> - <line - x1="347" - y1="62" - x2="351" - y2="58" - style="stroke:rgb(0,0,0);stroke-width:1" - id="line64" /> - <line - x1="359" - y1="58" - x2="363" - y2="58" - style="stroke:rgb(0,0,0);stroke-width:1" - id="line66" /> - <line - x1="359" - y1="62" - x2="367" - y2="62" - style="stroke:rgb(0,0,0);stroke-width:1" - id="line68" /> - <line - x1="359" - y1="66" - x2="363" - y2="66" - style="stroke:rgb(0,0,0);stroke-width:1" - id="line70" /> - <line - x1="363" - y1="58" - x2="363" - y2="66" - style="stroke:rgb(0,0,0);stroke-width:1" - id="line72" /> - <ellipse - cx="355" - cy="58" - rx="2" - ry="2" - style="rgb(0,0,0)" - id="ellipse74" /> - <ellipse - cx="355" - cy="62" - rx="2" - ry="2" - style="rgb(0,0,0)" - id="ellipse76" /> - <ellipse - cx="355" - cy="66" - rx="2" - ry="2" - style="rgb(0,0,0)" - id="ellipse78" /> - <text - x="368" - y="82" - style="font-family:Arial;font-size:7.2pt;fill:rgb(0,0,0);text-anchor:end;dominant-baseline:central" - id="text80">0..∞</text> - <rect - x="368" - y="57" - width="10" - height="10" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1" - id="rect82" /> - <line - x1="370" - y1="62" - x2="376" - y2="62" - style="stroke:rgb(0,0,0);stroke-width:1" - id="line84" /> - </g> - </a> - <line - x1="318" - y1="62" - x2="338" - y2="62" - style="stroke:rgb(0,0,0);stroke-width:1;stroke-linecap:round" - id="line86" /> - <a - id="a82" - href="#actiongroup-tag" - title="one or more <actionGroup> elements"> - <g - id="g195"> - <rect - x="227" - y="54" - width="89" - height="22" - style="fill:#ffffff;stroke:#000000;stroke-width:1" - id="rect88" /> - <rect - x="224" - y="51" - width="89" - height="22" - style="fill:#ffffff;stroke:#000000;stroke-width:1" - id="rect90" /> - <text - x="268.5" - y="62" - style="font-weight:bold;font-size:10.66666698px;font-family:Arial;dominant-baseline:central;text-anchor:middle;fill:#000000" - id="text92">actionGroup</text> - <text - x="308" - y="83" - style="font-size:9.59999943px;font-family:Arial;dominant-baseline:central;text-anchor:end;fill:#000000" - id="text94">1..∞</text> - <rect - x="308" - y="57" - width="10" - height="10" - style="fill:#ffffff;stroke:#000000;stroke-width:1" - id="rect96" /> - <line - x1="310" - y1="62" - x2="316" - y2="62" - style="stroke:#000000;stroke-width:1" - id="line98" /> - </g> - </a> - <line - x1="204" - y1="62" - x2="224" - y2="62" - style="stroke:rgb(0,0,0);stroke-width:1;stroke-linecap:round" - id="line100" /> - <line - x1="146.40559" - y1="62" - x2="166.40559" - y2="62" - style="stroke:#000000;stroke-width:1;stroke-linecap:round" - id="line126" /> - <a - id="a106" - title="Root element <actionGroups>" - href="#actiongroups-tag"> - <g - transform="translate(-10)" - id="g104"> - <line - x1="124.97701" - y1="62" - x2="146.40559" - y2="62" - style="stroke:#000000;stroke-width:1.03509831;stroke-linecap:round" - id="line142" /> - <rect - x="59.977016" - y="51" - width="95.357147" - height="22" - style="fill:#ffffff;stroke:#000000;stroke-width:1.03509831" - id="rect144" /> - <text - x="102.44111" - y="63.59021" - style="font-weight:bold;font-size:11.041049px;font-family:Arial;dominant-baseline:central;text-anchor:middle;fill:#000000;stroke-width:1.03509831" - id="text146" - transform="scale(1.0350984,0.96609173)">actionGroups</text> - <rect - x="147.83417" - y="57" - width="10.714286" - height="10" - style="fill:#ffffff;stroke:#000000;stroke-width:1.03509831" - id="rect148" /> - <line - x1="149.97702" - y1="62" - x2="156.40558" - y2="62" - style="stroke:#000000;stroke-width:1.03509831" - id="line150" /> - </g> - </a> - <a - id="a3823" - title="contains a sequence of"> - <g - id="g227-6" - transform="translate(-339.59441,-26)"> - <path - d="m 509,78 h 23 l 6,6 v 8 l -6,6 h -23 l -6,-6 v -8 z" - style="fill:#ffffff;stroke:#000000;stroke-width:1" - id="path26-0" - inkscape:connector-curvature="0" /> - <line - x1="506" - y1="88" - x2="535" - y2="88" - style="stroke:#000000;stroke-width:1" - id="line28-3" /> - <circle - cx="515" - cy="88" - id="ellipse30-8" - r="2" /> - <circle - cx="520" - cy="88" - id="ellipse32-3" - r="2" /> - <circle - cx="525" - cy="88" - id="ellipse34-4" - r="2" /> - <rect - x="533" - y="83" - width="10" - height="10" - style="fill:#ffffff;stroke:#000000;stroke-width:1" - id="rect36-0" /> - <line - x1="535" - y1="88" - x2="541" - y2="88" - style="stroke:#000000;stroke-width:1" - id="line38-2" /> - </g> - </a> -</svg> +<svg xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" id="svg152" width="643" height="114" version="1.1"><metadata id="metadata158"/><a id="a3851" href="actions.html" title="any actions"><g id="g242"><path d="M404 25 L498 25 L504 31 L504 41 L498 47 L404 47 L398 41 L398 31 Z" style="fill:#fff;stroke:#000;stroke-width:1" id="path2"/><text x="451" y="36" style="font-family:Arial;font-size:8pt;fill:#000;font-weight:700;text-anchor:middle;dominant-baseline:central" id="text4">actionTypeTags</text><line id="line6" x1="401" x2="404" y1="44" y2="41" style="stroke:#000;stroke-width:2"/><path d="M404 41 L406 43 L407 38 L402 39 L404 41 Z" style="fill:#000" id="path8"/><rect id="rect10" width="10" height="10" x="499" y="31" style="fill:#fff;stroke:#000;stroke-width:1"/><line id="line12" x1="501" x2="507" y1="36" y2="36" style="stroke:#000;stroke-width:1"/><line id="line14" x1="504" x2="504" y1="33" y2="39" style="stroke:#000;stroke-width:1"/></g></a><a id="a3924" href="#argument-tag" title="zero or more <argument> elements"><g id="g233"><rect id="rect16" width="70" height="22" x="566" y="80" style="fill:#fff;stroke:#000;stroke-width:1;stroke-dasharray:4,1"/><rect id="rect18" width="70" height="22" x="563" y="77" style="fill:#fff;stroke:#000;stroke-width:1;stroke-dasharray:4,1"/><text x="598" y="88" style="font-weight:700;font-size:10.66666698px;font-family:Arial;dominant-baseline:central;text-anchor:middle;fill:#000" id="text20">argument</text><text x="623" y="109" style="font-size:9.59999943px;font-family:Arial;dominant-baseline:central;text-anchor:end;fill:#000" id="text22">0..∞</text></g></a><line id="line24" x1="543" x2="563" y1="88" y2="88" style="stroke:#000;stroke-width:1;stroke-linecap:round"/><a id="a3914" title="contains a sequence of"><g id="g227"><path d="M509 78 L532 78 L538 84 L538 92 L532 98 L509 98 L503 92 L503 84 Z" style="fill:#fff;stroke:#000;stroke-width:1" id="path26"/><line id="line28" x1="506" x2="535" y1="88" y2="88" style="stroke:#000;stroke-width:1"/><ellipse id="ellipse30" cx="515" cy="88" rx="2" ry="2"/><ellipse id="ellipse32" cx="520" cy="88" rx="2" ry="2"/><ellipse id="ellipse34" cx="525" cy="88" rx="2" ry="2"/><rect id="rect36" width="10" height="10" x="533" y="83" style="fill:#fff;stroke:#000;stroke-width:1"/><line id="line38" x1="535" x2="541" y1="88" y2="88" style="stroke:#000;stroke-width:1"/></g></a><line id="line40" x1="483" x2="503" y1="88" y2="88" style="stroke:#000;stroke-width:1;stroke-linecap:round"/><a id="a3907" href="#arguments-tag" title="wrapping element <arguments>"><g id="g218"><rect id="rect42" width="80" height="22" x="398" y="77" style="fill:#fff;stroke:#000;stroke-width:1"/><text x="438" y="88" style="font-weight:700;font-size:10.66666698px;font-family:Arial;dominant-baseline:central;text-anchor:middle;fill:#000" id="text44">arguments</text><rect id="rect46" width="10" height="10" x="473" y="83" style="fill:#fff;stroke:#000;stroke-width:1"/><line id="line48" x1="475" x2="481" y1="88" y2="88" style="stroke:#000;stroke-width:1"/></g></a><line id="line50" x1="388" x2="398" y1="36" y2="36" style="stroke:#000;stroke-width:1;stroke-linecap:round"/><line id="line52" x1="388" x2="398" y1="88" y2="88" style="stroke:#000;stroke-width:1;stroke-linecap:round"/><line id="line54" x1="388" x2="388" y1="36" y2="88" style="stroke:#000;stroke-width:1;stroke-linecap:round"/><a id="a3833" title="optionally contains muiliple times one of the following nodes"><g id="g212"><line id="line56" x1="378" x2="388" y1="62" y2="62" style="stroke:#000;stroke-width:1;stroke-linecap:round"/><path d="M347 55 L370 55 L376 61 L376 69 L370 75 L347 75 L341 69 L341 61 Z" style="fill:#fff;stroke:#000;stroke-width:1;stroke-dasharray:4,1" id="path58"/><path d="M344 52 L367 52 L373 58 L373 66 L367 72 L344 72 L338 66 L338 58 Z" style="fill:#fff;stroke:#000;stroke-width:1;stroke-dasharray:4,1" id="path60"/><line id="line62" x1="343" x2="347" y1="62" y2="62" style="stroke:#000;stroke-width:1"/><line id="line64" x1="347" x2="351" y1="62" y2="58" style="stroke:#000;stroke-width:1"/><line id="line66" x1="359" x2="363" y1="58" y2="58" style="stroke:#000;stroke-width:1"/><line id="line68" x1="359" x2="367" y1="62" y2="62" style="stroke:#000;stroke-width:1"/><line id="line70" x1="359" x2="363" y1="66" y2="66" style="stroke:#000;stroke-width:1"/><line id="line72" x1="363" x2="363" y1="58" y2="66" style="stroke:#000;stroke-width:1"/><ellipse id="ellipse74" cx="355" cy="58" rx="2" ry="2"/><ellipse id="ellipse76" cx="355" cy="62" rx="2" ry="2"/><ellipse id="ellipse78" cx="355" cy="66" rx="2" ry="2"/><text x="368" y="82" style="font-family:Arial;font-size:7.2pt;fill:#000;text-anchor:end;dominant-baseline:central" id="text80">0..∞</text><rect id="rect82" width="10" height="10" x="368" y="57" style="fill:#fff;stroke:#000;stroke-width:1"/><line id="line84" x1="370" x2="376" y1="62" y2="62" style="stroke:#000;stroke-width:1"/></g></a><line id="line86" x1="318" x2="338" y1="62" y2="62" style="stroke:#000;stroke-width:1;stroke-linecap:round"/><a id="a82" href="#actiongroup-tag" title="one or more <actionGroup> elements"><g id="g195"><rect id="rect88" width="89" height="22" x="227" y="54" style="fill:#fff;stroke:#000;stroke-width:1"/><rect id="rect90" width="89" height="22" x="224" y="51" style="fill:#fff;stroke:#000;stroke-width:1"/><text x="268.5" y="62" style="font-weight:700;font-size:10.66666698px;font-family:Arial;dominant-baseline:central;text-anchor:middle;fill:#000" id="text92">actionGroup</text><text x="308" y="83" style="font-size:9.59999943px;font-family:Arial;dominant-baseline:central;text-anchor:end;fill:#000" id="text94">1..∞</text><rect id="rect96" width="10" height="10" x="308" y="57" style="fill:#fff;stroke:#000;stroke-width:1"/><line id="line98" x1="310" x2="316" y1="62" y2="62" style="stroke:#000;stroke-width:1"/></g></a><line id="line100" x1="204" x2="224" y1="62" y2="62" style="stroke:#000;stroke-width:1;stroke-linecap:round"/><line id="line126" x1="146.406" x2="166.406" y1="62" y2="62" style="stroke:#000;stroke-width:1;stroke-linecap:round"/><a id="a106" href="#actiongroups-tag" title="Root element <actionGroups>"><g id="g104" transform="translate(-10)"><line id="line142" x1="124.977" x2="146.406" y1="62" y2="62" style="stroke:#000;stroke-width:1.03509831;stroke-linecap:round"/><rect id="rect144" width="95.357" height="22" x="59.977" y="51" style="fill:#fff;stroke:#000;stroke-width:1.03509831"/><text x="102.441" y="63.59" style="font-weight:700;font-size:11.041049px;font-family:Arial;dominant-baseline:central;text-anchor:middle;fill:#000;stroke-width:1.03509831" id="text146" transform="scale(1.0350984,0.96609173)">actionGroups</text><rect id="rect148" width="10.714" height="10" x="147.834" y="57" style="fill:#fff;stroke:#000;stroke-width:1.03509831"/><line id="line150" x1="149.977" x2="156.406" y1="62" y2="62" style="stroke:#000;stroke-width:1.03509831"/></g></a><a id="a3823" title="contains a sequence of"><g id="g227-6" transform="translate(-339.59441,-26)"><path d="m 509,78 h 23 l 6,6 v 8 l -6,6 h -23 l -6,-6 v -8 z" style="fill:#fff;stroke:#000;stroke-width:1" id="path26-0"/><line id="line28-3" x1="506" x2="535" y1="88" y2="88" style="stroke:#000;stroke-width:1"/><circle id="ellipse30-8" cx="515" cy="88" r="2"/><circle id="ellipse32-3" cx="520" cy="88" r="2"/><circle id="ellipse34-4" cx="525" cy="88" r="2"/><rect id="rect36-0" width="10" height="10" x="533" y="83" style="fill:#fff;stroke:#000;stroke-width:1"/><line id="line38-2" x1="535" x2="541" y1="88" y2="88" style="stroke:#000;stroke-width:1"/></g></a></svg> \ No newline at end of file diff --git a/docs/img/data-dia.svg b/docs/img/data-dia.svg index fbaf7d8ec..1b2c315e7 100644 --- a/docs/img/data-dia.svg +++ b/docs/img/data-dia.svg @@ -1,489 +1 @@ -<?xml version="1.0" encoding="UTF-8" standalone="no"?> -<svg - xmlns:dc="http://purl.org/dc/elements/1.1/" - xmlns:cc="http://creativecommons.org/ns#" - xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" - xmlns:svg="http://www.w3.org/2000/svg" - xmlns="http://www.w3.org/2000/svg" - xmlns:xlink="http://www.w3.org/1999/xlink" - xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" - xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" - width="496" - height="218" - version="1.1" - id="svg176" - sodipodi:docname="data-dia.svg" - inkscape:version="0.92.2 (5c3e80d, 2017-08-06)"> - <metadata - id="metadata182"> - <rdf:RDF> - <cc:Work - rdf:about=""> - <dc:format>image/svg+xml</dc:format> - <dc:type - rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> - </cc:Work> - </rdf:RDF> - </metadata> - <defs - id="defs180" /> - <sodipodi:namedview - pagecolor="#ffffff" - bordercolor="#666666" - borderopacity="1" - objecttolerance="10" - gridtolerance="10" - guidetolerance="10" - inkscape:pageopacity="0" - inkscape:pageshadow="2" - inkscape:window-width="1920" - inkscape:window-height="1017" - id="namedview178" - showgrid="false" - inkscape:zoom="1.6195026" - inkscape:cx="156.50572" - inkscape:cy="97.15766" - inkscape:window-x="3278" - inkscape:window-y="-8" - inkscape:window-maximized="1" - inkscape:current-layer="svg176" /> - <text - x="338" - y="57" - style="font-family:Arial;font-size:7.2pt;fill:rgb(0,0,0);text-anchor:end;dominant-baseline:central" - id="text8">0..∞</text> - <a - id="a3925" - href="#data-tag" - title="zero or more <data> elements"> - <g - id="g195"> - <rect - x="307" - y="28" - width="44" - height="22" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1;stroke-dasharray:4,1" - id="rect2" /> - <rect - x="304" - y="25" - width="44" - height="22" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1;stroke-dasharray:4,1" - id="rect4" /> - <text - x="326" - y="36" - style="font-family:Arial;font-size:8pt;fill:rgb(0,0,0);font-weight:bold;text-anchor:middle;dominant-baseline:central" - id="text6">data</text> - </g> - </a> - <text - x="332" - y="109" - style="font-family:Arial;font-size:7.2pt;fill:rgb(0,0,0);text-anchor:end;dominant-baseline:central" - id="text24">0..∞</text> - <a - id="a3935" - href="#var-tag" - title="zero or more <var> elements"> - <g - id="g204"> - <rect - x="307" - y="80" - width="38" - height="22" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1;stroke-dasharray:4,1" - id="rect18" /> - <rect - x="304" - y="77" - width="38" - height="22" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1;stroke-dasharray:4,1" - id="rect20" /> - <text - x="323" - y="88" - style="font-family:Arial;font-size:8pt;fill:rgb(0,0,0);font-weight:bold;text-anchor:middle;dominant-baseline:central" - id="text22">var</text> - </g> - </a> - <text - x="389" - y="161" - style="font-family:Arial;font-size:7.2pt;fill:rgb(0,0,0);text-anchor:end;dominant-baseline:central" - id="text40">0..∞</text> - <a - id="a3945" - href="#requiredentity-tag" - title="zero or more <requiredEntity> elements"> - <g - id="g213"> - <rect - x="307" - y="132" - width="95" - height="22" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1;stroke-dasharray:4,1" - id="rect34" /> - <rect - x="304" - y="129" - width="95" - height="22" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1;stroke-dasharray:4,1" - id="rect36" /> - <text - x="351.5" - y="140" - style="font-family:Arial;font-size:8pt;fill:rgb(0,0,0);font-weight:bold;text-anchor:middle;dominant-baseline:central" - id="text38">requiredEntity</text> - </g> - </a> - <text - x="476" - y="213" - style="font-family:Arial;font-size:7.2pt;fill:rgb(0,0,0);text-anchor:end;dominant-baseline:central" - id="text56">0..∞</text> - <a - id="a3963" - href="#item-tag" - title="zero or more <item> elements"> - <g - id="g229"> - <rect - x="445" - y="184" - width="44" - height="22" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1;stroke-dasharray:4,1" - id="rect50" /> - <rect - x="442" - y="181" - width="44" - height="22" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1;stroke-dasharray:4,1" - id="rect52" /> - <text - x="464" - y="192" - style="font-family:Arial;font-size:8pt;fill:rgb(0,0,0);font-weight:bold;text-anchor:middle;dominant-baseline:central" - id="text54">item</text> - </g> - </a> - <line - x1="417.84494" - y1="192" - x2="442" - y2="192" - style="stroke:#000000;stroke-width:1.09897804;stroke-linecap:round" - id="line66" /> - <a - id="a3968" - title="a sequence of the following elements"> - <g - id="g3966"> - <path - inkscape:connector-curvature="0" - d="m 388,182 h 23 l 6,6 v 8 l -6,6 h -23 l -6,-6 v -8 z" - style="fill:#ffffff;stroke:#000000;stroke-width:1" - id="path68" /> - <line - x1="385" - y1="192" - x2="414" - y2="192" - style="stroke:#000000;stroke-width:1" - id="line70" /> - <circle - r="2" - cx="394" - cy="192" - id="ellipse72" /> - <circle - r="2" - cx="399" - cy="192" - id="ellipse74" /> - <circle - r="2" - cx="404" - cy="192" - id="ellipse76" /> - </g> - </a> - <line - x1="360.46375" - y1="192" - x2="382" - y2="192" - style="stroke:#000000;stroke-width:1.03769612;stroke-linecap:round" - id="line82" /> - <text - x="352" - y="213" - style="font-family:Arial;font-size:7.2pt;fill:rgb(0,0,0);text-anchor:end;dominant-baseline:central" - id="text90">0..∞</text> - <a - id="a99" - href="#array-tag" - title="zero or more <array> elements"> - <g - id="g3864"> - <rect - x="307" - y="184" - width="53" - height="22" - style="fill:#ffffff;stroke:#000000;stroke-width:1;stroke-dasharray:4, 1" - id="rect84" /> - <rect - x="304" - y="181" - width="53" - height="22" - style="fill:#ffffff;stroke:#000000;stroke-width:1;stroke-dasharray:4, 1" - id="rect86" /> - <text - x="330.5" - y="192" - style="font-weight:bold;font-size:10.66666698px;font-family:Arial;dominant-baseline:central;text-anchor:middle;fill:#000000" - id="text88">array</text> - </g> - </a> - <line - x1="294" - y1="36" - x2="304" - y2="36" - style="stroke:rgb(0,0,0);stroke-width:1;stroke-linecap:round" - id="line96" /> - <line - x1="294" - y1="88" - x2="304" - y2="88" - style="stroke:rgb(0,0,0);stroke-width:1;stroke-linecap:round" - id="line98" /> - <line - x1="294" - y1="140" - x2="304" - y2="140" - style="stroke:rgb(0,0,0);stroke-width:1;stroke-linecap:round" - id="line100" /> - <line - x1="294" - y1="192" - x2="304" - y2="192" - style="stroke:rgb(0,0,0);stroke-width:1;stroke-linecap:round" - id="line102" /> - <line - x1="294" - y1="36" - x2="294" - y2="192" - style="stroke:rgb(0,0,0);stroke-width:1;stroke-linecap:round" - id="line104" /> - <line - x1="282.8219" - y1="114" - x2="294" - y2="114" - style="stroke:#000000;stroke-width:1.05726469;stroke-linecap:round" - id="line106" /> - <a - id="a3984" - title="that contains one of the following nodes one or times"> - <g - id="g3959"> - <g - id="g3922"> - <path - inkscape:connector-curvature="0" - id="path110" - style="fill:#ffffff;stroke:#000000;stroke-width:1" - d="m 250,104 h 23 l 6,6 v 8 l -6,6 h -23 l -6,-6 v -8 z" /> - <path - id="path108" - style="fill:#ffffff;stroke:#000000;stroke-width:1" - d="m 253,107 h 23 l 6,6 v 8 l -6,6 h -23 l -6,-6 v -8 z" - inkscape:connector-curvature="0" /> - <line - id="line112" - style="stroke:#000000;stroke-width:1" - y2="114" - x2="253" - y1="114" - x1="249" /> - <line - id="line114" - style="stroke:#000000;stroke-width:1" - y2="110" - x2="257" - y1="114" - x1="253" /> - <line - id="line116" - style="stroke:#000000;stroke-width:1" - y2="110" - x2="269" - y1="110" - x1="265" /> - <line - id="line118" - style="stroke:#000000;stroke-width:1" - y2="114" - x2="273" - y1="114" - x1="265" /> - <line - id="line120" - style="stroke:#000000;stroke-width:1" - y2="118" - x2="269" - y1="118" - x1="265" /> - <line - id="line122" - style="stroke:#000000;stroke-width:1" - y2="118" - x2="269" - y1="110" - x1="269" /> - <circle - id="ellipse124" - cy="110" - cx="261" - r="2" /> - <circle - id="ellipse126" - cy="114" - cx="261" - r="2" /> - <circle - id="ellipse128" - cy="118" - cx="261" - r="2" /> - <text - id="text130" - style="font-size:9.59999943px;font-family:Arial;dominant-baseline:central;text-anchor:end;fill:#000000" - y="134" - x="274">1..∞</text> - </g> - </g> - </a> - <line - x1="222.68196" - y1="114" - x2="244" - y2="114" - style="stroke:#000000;stroke-width:1.03242517;stroke-linecap:round" - id="line136" /> - <rect - x="167" - y="106" - width="55" - height="22" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1;stroke-dasharray:4,1" - id="rect138" /> - <a - id="a231" - href="#entity-tag" - title="zero or more <entity> elements"> - <g - id="g186"> - <rect - x="164" - y="103" - width="55" - height="22" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1;stroke-dasharray:4,1" - id="rect140" /> - <text - x="191.5" - y="114" - style="font-family:Arial;font-size:8pt;fill:rgb(0,0,0);font-weight:bold;text-anchor:middle;dominant-baseline:central" - id="text142">entity</text> - </g> - </a> - <text - x="214" - y="135" - style="font-family:Arial;font-size:7.2pt;fill:rgb(0,0,0);text-anchor:end;dominant-baseline:central" - id="text144">0..∞</text> - <line - x1="136" - y1="114" - x2="164" - y2="114" - style="stroke:#000000;stroke-width:1.18321598;stroke-linecap:round" - id="line150" /> - <a - id="a3976" - title="that contains a sequence of"> - <g - id="g3944"> - <path - d="m 110,104 h 23 l 6,6 v 8 l -6,6 h -23 l -6,-6 v -8 z" - style="fill:#ffffff;stroke:#000000;stroke-width:1" - id="path152" - inkscape:connector-curvature="0" /> - <line - x1="107" - y1="114" - x2="136" - y2="114" - style="stroke:#000000;stroke-width:1" - id="line154" /> - <circle - cx="116" - cy="114" - id="ellipse156" - r="2" /> - <circle - cx="121" - cy="114" - id="ellipse158" - r="2" /> - <circle - cx="126" - cy="114" - id="ellipse160" - r="2" /> - </g> - </a> - <line - x1="79.899483" - y1="114" - x2="104" - y2="114" - style="stroke:#000000;stroke-width:1.09773672;stroke-linecap:round" - id="line166" /> - <a - id="a104" - title="Root element <entities>" - href="#entities-tag"> - <g - id="g102"> - <rect - x="20" - y="103" - width="59" - height="22" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1" - id="rect168" /> - <text - x="49.5" - y="114" - style="font-family:Arial;font-size:8pt;fill:rgb(0,0,0);font-weight:bold;text-anchor:middle;dominant-baseline:central" - id="text170">entities</text> - </g> - </a> -</svg> +<svg xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" id="svg176" width="496" height="218" version="1.1"><metadata id="metadata182"/><text x="338" y="57" style="font-family:Arial;font-size:7.2pt;fill:#000;text-anchor:end;dominant-baseline:central" id="text8">0..∞</text><a id="a3925" href="#data-tag" title="zero or more <data> elements"><g id="g195"><rect id="rect2" width="44" height="22" x="307" y="28" style="fill:#fff;stroke:#000;stroke-width:1;stroke-dasharray:4,1"/><rect id="rect4" width="44" height="22" x="304" y="25" style="fill:#fff;stroke:#000;stroke-width:1;stroke-dasharray:4,1"/><text x="326" y="36" style="font-family:Arial;font-size:8pt;fill:#000;font-weight:700;text-anchor:middle;dominant-baseline:central" id="text6">data</text></g></a><text x="332" y="109" style="font-family:Arial;font-size:7.2pt;fill:#000;text-anchor:end;dominant-baseline:central" id="text24">0..∞</text><a id="a3935" href="#var-tag" title="zero or more <var> elements"><g id="g204"><rect id="rect18" width="38" height="22" x="307" y="80" style="fill:#fff;stroke:#000;stroke-width:1;stroke-dasharray:4,1"/><rect id="rect20" width="38" height="22" x="304" y="77" style="fill:#fff;stroke:#000;stroke-width:1;stroke-dasharray:4,1"/><text x="323" y="88" style="font-family:Arial;font-size:8pt;fill:#000;font-weight:700;text-anchor:middle;dominant-baseline:central" id="text22">var</text></g></a><text x="389" y="161" style="font-family:Arial;font-size:7.2pt;fill:#000;text-anchor:end;dominant-baseline:central" id="text40">0..∞</text><a id="a3945" href="#requiredentity-tag" title="zero or more <requiredEntity> elements"><g id="g213"><rect id="rect34" width="95" height="22" x="307" y="132" style="fill:#fff;stroke:#000;stroke-width:1;stroke-dasharray:4,1"/><rect id="rect36" width="95" height="22" x="304" y="129" style="fill:#fff;stroke:#000;stroke-width:1;stroke-dasharray:4,1"/><text x="351.5" y="140" style="font-family:Arial;font-size:8pt;fill:#000;font-weight:700;text-anchor:middle;dominant-baseline:central" id="text38">requiredEntity</text></g></a><text x="476" y="213" style="font-family:Arial;font-size:7.2pt;fill:#000;text-anchor:end;dominant-baseline:central" id="text56">0..∞</text><a id="a3963" href="#item-tag" title="zero or more <item> elements"><g id="g229"><rect id="rect50" width="44" height="22" x="445" y="184" style="fill:#fff;stroke:#000;stroke-width:1;stroke-dasharray:4,1"/><rect id="rect52" width="44" height="22" x="442" y="181" style="fill:#fff;stroke:#000;stroke-width:1;stroke-dasharray:4,1"/><text x="464" y="192" style="font-family:Arial;font-size:8pt;fill:#000;font-weight:700;text-anchor:middle;dominant-baseline:central" id="text54">item</text></g></a><line id="line66" x1="417.845" x2="442" y1="192" y2="192" style="stroke:#000;stroke-width:1.09897804;stroke-linecap:round"/><a id="a3968" title="a sequence of the following elements"><g id="g3966"><path d="m 388,182 h 23 l 6,6 v 8 l -6,6 h -23 l -6,-6 v -8 z" style="fill:#fff;stroke:#000;stroke-width:1" id="path68"/><line id="line70" x1="385" x2="414" y1="192" y2="192" style="stroke:#000;stroke-width:1"/><circle id="ellipse72" cx="394" cy="192" r="2"/><circle id="ellipse74" cx="399" cy="192" r="2"/><circle id="ellipse76" cx="404" cy="192" r="2"/></g></a><line id="line82" x1="360.464" x2="382" y1="192" y2="192" style="stroke:#000;stroke-width:1.03769612;stroke-linecap:round"/><text x="352" y="213" style="font-family:Arial;font-size:7.2pt;fill:#000;text-anchor:end;dominant-baseline:central" id="text90">0..∞</text><a id="a99" href="#array-tag" title="zero or more <array> elements"><g id="g3864"><rect id="rect84" width="53" height="22" x="307" y="184" style="fill:#fff;stroke:#000;stroke-width:1;stroke-dasharray:4,1"/><rect id="rect86" width="53" height="22" x="304" y="181" style="fill:#fff;stroke:#000;stroke-width:1;stroke-dasharray:4,1"/><text x="330.5" y="192" style="font-weight:700;font-size:10.66666698px;font-family:Arial;dominant-baseline:central;text-anchor:middle;fill:#000" id="text88">array</text></g></a><line id="line96" x1="294" x2="304" y1="36" y2="36" style="stroke:#000;stroke-width:1;stroke-linecap:round"/><line id="line98" x1="294" x2="304" y1="88" y2="88" style="stroke:#000;stroke-width:1;stroke-linecap:round"/><line id="line100" x1="294" x2="304" y1="140" y2="140" style="stroke:#000;stroke-width:1;stroke-linecap:round"/><line id="line102" x1="294" x2="304" y1="192" y2="192" style="stroke:#000;stroke-width:1;stroke-linecap:round"/><line id="line104" x1="294" x2="294" y1="36" y2="192" style="stroke:#000;stroke-width:1;stroke-linecap:round"/><line id="line106" x1="282.822" x2="294" y1="114" y2="114" style="stroke:#000;stroke-width:1.05726469;stroke-linecap:round"/><a id="a3984" title="that contains one of the following nodes one or times"><g id="g3959"><g id="g3922"><path id="path110" style="fill:#fff;stroke:#000;stroke-width:1" d="m 250,104 h 23 l 6,6 v 8 l -6,6 h -23 l -6,-6 v -8 z"/><path id="path108" style="fill:#fff;stroke:#000;stroke-width:1" d="m 253,107 h 23 l 6,6 v 8 l -6,6 h -23 l -6,-6 v -8 z"/><line id="line112" style="stroke:#000;stroke-width:1" x1="249" x2="253" y1="114" y2="114"/><line id="line114" style="stroke:#000;stroke-width:1" x1="253" x2="257" y1="114" y2="110"/><line id="line116" style="stroke:#000;stroke-width:1" x1="265" x2="269" y1="110" y2="110"/><line id="line118" style="stroke:#000;stroke-width:1" x1="265" x2="273" y1="114" y2="114"/><line id="line120" style="stroke:#000;stroke-width:1" x1="265" x2="269" y1="118" y2="118"/><line id="line122" style="stroke:#000;stroke-width:1" x1="269" x2="269" y1="110" y2="118"/><circle id="ellipse124" cx="261" cy="110" r="2"/><circle id="ellipse126" cx="261" cy="114" r="2"/><circle id="ellipse128" cx="261" cy="118" r="2"/><text id="text130" style="font-size:9.59999943px;font-family:Arial;dominant-baseline:central;text-anchor:end;fill:#000" x="274" y="134">1..∞</text></g></g></a><line id="line136" x1="222.682" x2="244" y1="114" y2="114" style="stroke:#000;stroke-width:1.03242517;stroke-linecap:round"/><rect id="rect138" width="55" height="22" x="167" y="106" style="fill:#fff;stroke:#000;stroke-width:1;stroke-dasharray:4,1"/><a id="a231" href="#entity-tag" title="zero or more <entity> elements"><g id="g186"><rect id="rect140" width="55" height="22" x="164" y="103" style="fill:#fff;stroke:#000;stroke-width:1;stroke-dasharray:4,1"/><text x="191.5" y="114" style="font-family:Arial;font-size:8pt;fill:#000;font-weight:700;text-anchor:middle;dominant-baseline:central" id="text142">entity</text></g></a><text x="214" y="135" style="font-family:Arial;font-size:7.2pt;fill:#000;text-anchor:end;dominant-baseline:central" id="text144">0..∞</text><line id="line150" x1="136" x2="164" y1="114" y2="114" style="stroke:#000;stroke-width:1.18321598;stroke-linecap:round"/><a id="a3976" title="that contains a sequence of"><g id="g3944"><path d="m 110,104 h 23 l 6,6 v 8 l -6,6 h -23 l -6,-6 v -8 z" style="fill:#fff;stroke:#000;stroke-width:1" id="path152"/><line id="line154" x1="107" x2="136" y1="114" y2="114" style="stroke:#000;stroke-width:1"/><circle id="ellipse156" cx="116" cy="114" r="2"/><circle id="ellipse158" cx="121" cy="114" r="2"/><circle id="ellipse160" cx="126" cy="114" r="2"/></g></a><line id="line166" x1="79.899" x2="104" y1="114" y2="114" style="stroke:#000;stroke-width:1.09773672;stroke-linecap:round"/><a id="a104" href="#entities-tag" title="Root element <entities>"><g id="g102"><rect id="rect168" width="59" height="22" x="20" y="103" style="fill:#fff;stroke:#000;stroke-width:1"/><text x="49.5" y="114" style="font-family:Arial;font-size:8pt;fill:#000;font-weight:700;text-anchor:middle;dominant-baseline:central" id="text170">entities</text></g></a></svg> \ No newline at end of file diff --git a/docs/img/issue.png b/docs/img/issue.png deleted file mode 100644 index 1dcf78ce2..000000000 Binary files a/docs/img/issue.png and /dev/null differ diff --git a/docs/img/metadata-dia.svg b/docs/img/metadata-dia.svg index 0ce0fc27a..9e898be3b 100644 --- a/docs/img/metadata-dia.svg +++ b/docs/img/metadata-dia.svg @@ -1,1050 +1 @@ -<?xml version="1.0" encoding="UTF-8" standalone="no"?> -<svg - xmlns:dc="http://purl.org/dc/elements/1.1/" - xmlns:cc="http://creativecommons.org/ns#" - xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" - xmlns:svg="http://www.w3.org/2000/svg" - xmlns="http://www.w3.org/2000/svg" - xmlns:xlink="http://www.w3.org/1999/xlink" - xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" - xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" - width="561" - height="478" - version="1.1" - id="svg318" - sodipodi:docname="metadata-dia.svg" - inkscape:version="0.92.2 (5c3e80d, 2017-08-06)"> - <metadata - id="metadata324"> - <rdf:RDF> - <cc:Work - rdf:about=""> - <dc:format>image/svg+xml</dc:format> - <dc:type - rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> - </cc:Work> - </rdf:RDF> - </metadata> - <defs - id="defs322" /> - <sodipodi:namedview - pagecolor="#ffffff" - bordercolor="#666666" - borderopacity="1" - objecttolerance="10" - gridtolerance="10" - guidetolerance="10" - inkscape:pageopacity="0" - inkscape:pageshadow="2" - inkscape:window-width="1920" - inkscape:window-height="1017" - id="namedview320" - showgrid="false" - inkscape:zoom="1.3964619" - inkscape:cx="235.1642" - inkscape:cy="256.75664" - inkscape:window-x="3278" - inkscape:window-y="-8" - inkscape:window-maximized="1" - inkscape:current-layer="svg318" /> - <a - id="a4301" - href="#field-tag" - title="zero or more <field> elements"> - <g - id="g417"> - <rect - x="491" - y="28" - width="44" - height="22" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1;stroke-dasharray:4,1" - id="rect2" /> - <rect - x="488" - y="25" - width="44" - height="22" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1;stroke-dasharray:4,1" - id="rect4" /> - <text - x="510" - y="36" - style="font-family:Arial;font-size:8pt;fill:rgb(0,0,0);font-weight:bold;text-anchor:middle;dominant-baseline:central" - id="text6">field</text> - <text - x="522" - y="57" - style="font-family:Arial;font-size:7.2pt;fill:rgb(0,0,0);text-anchor:end;dominant-baseline:central" - id="text8">0..∞</text> - </g> - </a> - <a - id="a4308" - title="zero or more <array> elements" - href="#array-tag"> - <g - id="g442"> - <rect - x="491" - y="80" - width="53" - height="22" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1;stroke-dasharray:4,1" - id="rect18" /> - <rect - x="488" - y="77" - width="53" - height="22" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1;stroke-dasharray:4,1" - id="rect20" /> - <text - x="514.5" - y="88" - style="font-family:Arial;font-size:8pt;fill:rgb(0,0,0);font-weight:bold;text-anchor:middle;dominant-baseline:central" - id="text22">array</text> - <text - x="536" - y="109" - style="font-family:Arial;font-size:7.2pt;fill:rgb(0,0,0);text-anchor:end;dominant-baseline:central" - id="text24">0..∞</text> - <rect - x="536" - y="83" - width="10" - height="10" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1" - id="rect26" /> - <line - x1="538" - y1="88" - x2="544" - y2="88" - style="stroke:rgb(0,0,0);stroke-width:1" - id="line28" /> - <line - x1="541" - y1="85" - x2="541" - y2="91" - style="stroke:rgb(0,0,0);stroke-width:1" - id="line30" /> - </g> - </a> - <line - x1="478" - y1="36" - x2="488" - y2="36" - style="stroke:rgb(0,0,0);stroke-width:1;stroke-linecap:round" - id="line46" /> - <line - x1="478" - y1="88" - x2="488" - y2="88" - style="stroke:rgb(0,0,0);stroke-width:1;stroke-linecap:round" - id="line48" /> - <a - id="a4318" - href="#object-tag" - title="zero or more <object> elements"> - <g - id="g452"> - <rect - x="491" - y="132" - width="58" - height="22" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1;stroke-dasharray:4,1" - id="rect32" /> - <rect - x="488" - y="129" - width="58" - height="22" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1;stroke-dasharray:4,1" - id="rect34" /> - <text - x="517" - y="140" - style="font-family:Arial;font-size:8pt;fill:rgb(0,0,0);font-weight:bold;text-anchor:middle;dominant-baseline:central" - id="text36">object</text> - <text - x="541" - y="161" - style="font-family:Arial;font-size:7.2pt;fill:rgb(0,0,0);text-anchor:end;dominant-baseline:central" - id="text38">0..∞</text> - <rect - x="541" - y="135" - width="10" - height="10" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1" - id="rect40" /> - <line - x1="543" - y1="140" - x2="549" - y2="140" - style="stroke:rgb(0,0,0);stroke-width:1" - id="line42" /> - <line - x1="546" - y1="137" - x2="546" - y2="143" - style="stroke:rgb(0,0,0);stroke-width:1" - id="line44" /> - <line - x1="478" - y1="140" - x2="488" - y2="140" - style="stroke:rgb(0,0,0);stroke-width:1;stroke-linecap:round" - id="line50" /> - </g> - </a> - <line - x1="478" - y1="36" - x2="478" - y2="140" - style="stroke:rgb(0,0,0);stroke-width:1;stroke-linecap:round" - id="line52" /> - <line - x1="468" - y1="88" - x2="478" - y2="88" - style="stroke:rgb(0,0,0);stroke-width:1;stroke-linecap:round" - id="line54" /> - <a - id="a4284" - title="that may contain zero or more times one of the following nodes"> - <g - id="g433"> - <path - d="M437 81 L460 81 L466 87 L466 95 L460 101 L437 101 L431 95 L431 87 Z" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1;stroke-dasharray:4,1" - id="path56" /> - <path - d="M434 78 L457 78 L463 84 L463 92 L457 98 L434 98 L428 92 L428 84 Z" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1;stroke-dasharray:4,1" - id="path58" /> - <line - x1="433" - y1="88" - x2="437" - y2="88" - style="stroke:rgb(0,0,0);stroke-width:1" - id="line60" /> - <line - x1="437" - y1="88" - x2="441" - y2="84" - style="stroke:rgb(0,0,0);stroke-width:1" - id="line62" /> - <line - x1="449" - y1="84" - x2="453" - y2="84" - style="stroke:rgb(0,0,0);stroke-width:1" - id="line64" /> - <line - x1="449" - y1="88" - x2="457" - y2="88" - style="stroke:rgb(0,0,0);stroke-width:1" - id="line66" /> - <line - x1="449" - y1="92" - x2="453" - y2="92" - style="stroke:rgb(0,0,0);stroke-width:1" - id="line68" /> - <line - x1="453" - y1="84" - x2="453" - y2="92" - style="stroke:rgb(0,0,0);stroke-width:1" - id="line70" /> - <ellipse - cx="445" - cy="84" - rx="2" - ry="2" - style="rgb(0,0,0)" - id="ellipse72" /> - <ellipse - cx="445" - cy="88" - rx="2" - ry="2" - style="rgb(0,0,0)" - id="ellipse74" /> - <ellipse - cx="445" - cy="92" - rx="2" - ry="2" - style="rgb(0,0,0)" - id="ellipse76" /> - <text - x="458" - y="108" - style="font-family:Arial;font-size:7.2pt;fill:rgb(0,0,0);text-anchor:end;dominant-baseline:central" - id="text78">0..∞</text> - <rect - x="458" - y="83" - width="10" - height="10" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1" - id="rect80" /> - <line - x1="460" - y1="88" - x2="466" - y2="88" - style="stroke:rgb(0,0,0);stroke-width:1" - id="line82" /> - </g> - </a> - <line - x1="408" - y1="88" - x2="428" - y2="88" - style="stroke:rgb(0,0,0);stroke-width:1;stroke-linecap:round" - id="line84" /> - <a - id="a4227" - title="zero or more <field> elements" - href="#field-tag"> - <g - id="g387"> - <rect - x="348" - y="184" - width="44" - height="22" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1;stroke-dasharray:4,1" - id="rect98" /> - <rect - x="345" - y="181" - width="44" - height="22" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1;stroke-dasharray:4,1" - id="rect100" /> - <text - x="367" - y="192" - style="font-family:Arial;font-size:8pt;fill:rgb(0,0,0);font-weight:bold;text-anchor:middle;dominant-baseline:central" - id="text102">field</text> - <text - x="379" - y="213" - style="font-family:Arial;font-size:7.2pt;fill:rgb(0,0,0);text-anchor:end;dominant-baseline:central" - id="text104">0..∞</text> - </g> - </a> - <a - id="a4262" - title="zero or one <value> element" - href="#value-tag"> - <g - id="g466"> - <rect - x="483" - y="285" - width="49" - height="22" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1;stroke-dasharray:4,1" - id="rect128" /> - <text - x="507.5" - y="296" - style="font-family:Arial;font-size:8pt;fill:rgb(0,0,0);font-weight:bold;text-anchor:middle;dominant-baseline:central" - id="text130">value</text> - </g> - </a> - <a - id="a4329" - title="zero or more <object> elements" - href="#object-tag"> - <g - id="g462"> - <rect - x="486" - y="236" - width="58" - height="22" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1;stroke-dasharray:4,1" - id="rect114" /> - <rect - x="483" - y="233" - width="58" - height="22" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1;stroke-dasharray:4,1" - id="rect116" /> - <text - x="512" - y="244" - style="font-family:Arial;font-size:8pt;fill:rgb(0,0,0);font-weight:bold;text-anchor:middle;dominant-baseline:central" - id="text118">object</text> - <text - x="536" - y="265" - style="font-family:Arial;font-size:7.2pt;fill:rgb(0,0,0);text-anchor:end;dominant-baseline:central" - id="text120">0..∞</text> - <rect - x="536" - y="239" - width="10" - height="10" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1" - id="rect122" /> - <line - x1="538" - y1="244" - x2="544" - y2="244" - style="stroke:rgb(0,0,0);stroke-width:1" - id="line124" /> - <line - x1="541" - y1="241" - x2="541" - y2="247" - style="stroke:rgb(0,0,0);stroke-width:1" - id="line126" /> - <line - x1="473" - y1="244" - x2="483" - y2="244" - style="stroke:rgb(0,0,0);stroke-width:1;stroke-linecap:round" - id="line140" /> - </g> - </a> - <line - x1="473" - y1="296" - x2="483" - y2="296" - style="stroke:rgb(0,0,0);stroke-width:1;stroke-linecap:round" - id="line142" /> - <line - x1="473" - y1="244" - x2="473" - y2="296" - style="stroke:rgb(0,0,0);stroke-width:1;stroke-linecap:round" - id="line144" /> - <line - x1="463" - y1="270" - x2="473" - y2="270" - style="stroke:rgb(0,0,0);stroke-width:1;stroke-linecap:round" - id="line146" /> - <a - id="a4267" - title="that may contain zero or more times one of the following nodes"> - <g - id="g482"> - <path - d="M432 263 L455 263 L461 269 L461 277 L455 283 L432 283 L426 277 L426 269 Z" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1;stroke-dasharray:4,1" - id="path148" /> - <path - d="M429 260 L452 260 L458 266 L458 274 L452 280 L429 280 L423 274 L423 266 Z" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1;stroke-dasharray:4,1" - id="path150" /> - <line - x1="428" - y1="270" - x2="432" - y2="270" - style="stroke:rgb(0,0,0);stroke-width:1" - id="line152" /> - <line - x1="432" - y1="270" - x2="436" - y2="266" - style="stroke:rgb(0,0,0);stroke-width:1" - id="line154" /> - <line - x1="444" - y1="266" - x2="448" - y2="266" - style="stroke:rgb(0,0,0);stroke-width:1" - id="line156" /> - <line - x1="444" - y1="270" - x2="452" - y2="270" - style="stroke:rgb(0,0,0);stroke-width:1" - id="line158" /> - <line - x1="444" - y1="274" - x2="448" - y2="274" - style="stroke:rgb(0,0,0);stroke-width:1" - id="line160" /> - <line - x1="448" - y1="266" - x2="448" - y2="274" - style="stroke:rgb(0,0,0);stroke-width:1" - id="line162" /> - <ellipse - cx="440" - cy="266" - rx="2" - ry="2" - style="rgb(0,0,0)" - id="ellipse164" /> - <ellipse - cx="440" - cy="270" - rx="2" - ry="2" - style="rgb(0,0,0)" - id="ellipse166" /> - <ellipse - cx="440" - cy="274" - rx="2" - ry="2" - style="rgb(0,0,0)" - id="ellipse168" /> - <text - x="453" - y="290" - style="font-family:Arial;font-size:7.2pt;fill:rgb(0,0,0);text-anchor:end;dominant-baseline:central" - id="text170">0..∞</text> - <rect - x="453" - y="265" - width="10" - height="10" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1" - id="rect172" /> - <line - x1="455" - y1="270" - x2="461" - y2="270" - style="stroke:rgb(0,0,0);stroke-width:1" - id="line174" /> - </g> - </a> - <line - x1="403" - y1="270" - x2="423" - y2="270" - style="stroke:rgb(0,0,0);stroke-width:1;stroke-linecap:round" - id="line176" /> - <a - id="a4234" - title="zero or more <array> elements" - href="#array-tag"> - <g - id="g395"> - <rect - x="348" - y="262" - width="53" - height="22" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1;stroke-dasharray:4,1" - id="rect178" /> - <rect - x="345" - y="259" - width="53" - height="22" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1;stroke-dasharray:4,1" - id="rect180" /> - <text - x="371.5" - y="270" - style="font-family:Arial;font-size:8pt;fill:rgb(0,0,0);font-weight:bold;text-anchor:middle;dominant-baseline:central" - id="text182">array</text> - <text - x="393" - y="291" - style="font-family:Arial;font-size:7.2pt;fill:rgb(0,0,0);text-anchor:end;dominant-baseline:central" - id="text184">0..∞</text> - <rect - x="393" - y="265" - width="10" - height="10" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1" - id="rect186" /> - <line - x1="395" - y1="270" - x2="401" - y2="270" - style="stroke:rgb(0,0,0);stroke-width:1" - id="line188" /> - </g> - </a> - <a - id="a4243" - href="#header-tag" - title="zero or more <header> elements"> - <g - id="g400"> - <rect - x="348" - y="340" - width="57" - height="22" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1;stroke-dasharray:4,1" - id="rect190" /> - <rect - x="345" - y="337" - width="57" - height="22" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1;stroke-dasharray:4,1" - id="rect192" /> - <text - x="373.5" - y="348" - style="font-family:Arial;font-size:8pt;fill:rgb(0,0,0);font-weight:bold;text-anchor:middle;dominant-baseline:central" - id="text194">header</text> - </g> - </a> - <text - x="392" - y="369" - style="font-family:Arial;font-size:7.2pt;fill:rgb(0,0,0);text-anchor:end;dominant-baseline:central" - id="text196">0..∞</text> - <a - id="a4249" - title="zero or more <param> elements" - href="#param-tag"> - <g - id="g406"> - <rect - x="348" - y="392" - width="54" - height="22" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1;stroke-dasharray:4,1" - id="rect206" /> - <rect - x="345" - y="389" - width="54" - height="22" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1;stroke-dasharray:4,1" - id="rect208" /> - <text - x="372" - y="400" - style="font-family:Arial;font-size:8pt;fill:rgb(0,0,0);font-weight:bold;text-anchor:middle;dominant-baseline:central" - id="text210">param</text> - <text - x="389" - y="421" - style="font-family:Arial;font-size:7.2pt;fill:rgb(0,0,0);text-anchor:end;dominant-baseline:central" - id="text212">0..∞</text> - </g> - </a> - <a - id="a4216" - href="#object-tag" - title="zero or more <object> elements"> - <g - id="g381"> - <g - id="g346"> - <rect - id="rect86" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1;stroke-dasharray:4,1" - height="22" - width="58" - y="80" - x="348" /> - <rect - id="rect88" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1;stroke-dasharray:4,1" - height="22" - width="58" - y="77" - x="345" /> - <text - id="text90" - style="font-family:Arial;font-size:8pt;fill:rgb(0,0,0);font-weight:bold;text-anchor:middle;dominant-baseline:central" - y="88" - x="374">object</text> - <text - id="text92" - style="font-family:Arial;font-size:7.2pt;fill:rgb(0,0,0);text-anchor:end;dominant-baseline:central" - y="109" - x="398">0..∞</text> - <rect - id="rect94" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1" - height="10" - width="10" - y="83" - x="398" /> - <line - id="line96" - style="stroke:rgb(0,0,0);stroke-width:1" - y2="88" - x2="406" - y1="88" - x1="400" /> - </g> - <line - x1="335" - y1="88" - x2="345" - y2="88" - style="stroke:rgb(0,0,0);stroke-width:1;stroke-linecap:round" - id="line234" /> - </g> - </a> - <line - x1="335" - y1="192" - x2="345" - y2="192" - style="stroke:rgb(0,0,0);stroke-width:1;stroke-linecap:round" - id="line236" /> - <line - x1="335" - y1="270" - x2="345" - y2="270" - style="stroke:rgb(0,0,0);stroke-width:1;stroke-linecap:round" - id="line238" /> - <line - x1="335" - y1="348" - x2="345" - y2="348" - style="stroke:rgb(0,0,0);stroke-width:1;stroke-linecap:round" - id="line240" /> - <line - x1="335" - y1="400" - x2="345" - y2="400" - style="stroke:rgb(0,0,0);stroke-width:1;stroke-linecap:round" - id="line242" /> - <a - id="a4256" - title="one <contentType> element" - href="#contentType-tag"> - <g - id="g411"> - <rect - x="345" - y="441" - width="84" - height="22" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1" - id="rect222" /> - <text - x="387" - y="452" - style="font-family:Arial;font-size:8pt;fill:rgb(0,0,0);font-weight:bold;text-anchor:middle;dominant-baseline:central" - id="text224">contentType</text> - <line - x1="335" - y1="452" - x2="345" - y2="452" - style="stroke:rgb(0,0,0);stroke-width:1;stroke-linecap:round" - id="line244" /> - </g> - </a> - <line - x1="335" - y1="88" - x2="335" - y2="452" - style="stroke:rgb(0,0,0);stroke-width:1;stroke-linecap:round" - id="line246" /> - <line - x1="325" - y1="244" - x2="335" - y2="244" - style="stroke:rgb(0,0,0);stroke-width:1;stroke-linecap:round" - id="line248" /> - <a - id="a4199" - title="that may contain zero or more times one of the following nodes"> - <g - id="g371"> - <path - d="M294 237 L317 237 L323 243 L323 251 L317 257 L294 257 L288 251 L288 243 Z" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1;stroke-dasharray:4,1" - id="path250" /> - <path - d="M291 234 L314 234 L320 240 L320 248 L314 254 L291 254 L285 248 L285 240 Z" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1;stroke-dasharray:4,1" - id="path252" /> - <line - x1="290" - y1="244" - x2="294" - y2="244" - style="stroke:rgb(0,0,0);stroke-width:1" - id="line254" /> - <line - x1="294" - y1="244" - x2="298" - y2="240" - style="stroke:rgb(0,0,0);stroke-width:1" - id="line256" /> - <line - x1="306" - y1="240" - x2="310" - y2="240" - style="stroke:rgb(0,0,0);stroke-width:1" - id="line258" /> - <line - x1="306" - y1="244" - x2="314" - y2="244" - style="stroke:rgb(0,0,0);stroke-width:1" - id="line260" /> - <line - x1="306" - y1="248" - x2="310" - y2="248" - style="stroke:rgb(0,0,0);stroke-width:1" - id="line262" /> - <line - x1="310" - y1="240" - x2="310" - y2="248" - style="stroke:rgb(0,0,0);stroke-width:1" - id="line264" /> - <ellipse - cx="302" - cy="240" - rx="2" - ry="2" - style="rgb(0,0,0)" - id="ellipse266" /> - <ellipse - cx="302" - cy="244" - rx="2" - ry="2" - style="rgb(0,0,0)" - id="ellipse268" /> - <ellipse - cx="302" - cy="248" - rx="2" - ry="2" - style="rgb(0,0,0)" - id="ellipse270" /> - <text - x="315" - y="264" - style="font-family:Arial;font-size:7.2pt;fill:rgb(0,0,0);text-anchor:end;dominant-baseline:central" - id="text272">0..∞</text> - <rect - x="315" - y="239" - width="10" - height="10" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1" - id="rect274" /> - <line - x1="317" - y1="244" - x2="323" - y2="244" - style="stroke:rgb(0,0,0);stroke-width:1" - id="line276" /> - </g> - </a> - <line - x1="265" - y1="244" - x2="285" - y2="244" - style="stroke:rgb(0,0,0);stroke-width:1;stroke-linecap:round" - id="line278" /> - <a - id="a4190" - title="zero or more <operation> elements" - href="#operation-tag"> - <g - id="g338"> - <rect - x="188" - y="236" - width="75" - height="22" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1;stroke-dasharray:4,1" - id="rect280" /> - <rect - x="185" - y="233" - width="75" - height="22" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1;stroke-dasharray:4,1" - id="rect282" /> - <text - x="222.5" - y="244" - style="font-family:Arial;font-size:8pt;fill:rgb(0,0,0);font-weight:bold;text-anchor:middle;dominant-baseline:central" - id="text284">operation</text> - <text - x="255" - y="265" - style="font-family:Arial;font-size:7.2pt;fill:rgb(0,0,0);text-anchor:end;dominant-baseline:central" - id="text286">0..∞</text> - <rect - x="255" - y="239" - width="10" - height="10" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1" - id="rect288" /> - <line - x1="257" - y1="244" - x2="263" - y2="244" - style="stroke:rgb(0,0,0);stroke-width:1" - id="line290" /> - </g> - </a> - <line - x1="165" - y1="244" - x2="185" - y2="244" - style="stroke:rgb(0,0,0);stroke-width:1;stroke-linecap:round" - id="line292" /> - <a - id="a4180" - title="that contains a sequence of"> - <g - id="g355"> - <path - d="M131 234 L154 234 L160 240 L160 248 L154 254 L131 254 L125 248 L125 240 Z" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1" - id="path294" /> - <line - x1="128" - y1="244" - x2="157" - y2="244" - style="stroke:rgb(0,0,0);stroke-width:1" - id="line296" /> - <ellipse - cx="137" - cy="244" - rx="2" - ry="2" - style="rgb(0,0,0)" - id="ellipse298" /> - <ellipse - cx="142" - cy="244" - rx="2" - ry="2" - style="rgb(0,0,0)" - id="ellipse300" /> - <ellipse - cx="147" - cy="244" - rx="2" - ry="2" - style="rgb(0,0,0)" - id="ellipse302" /> - <rect - x="155" - y="239" - width="10" - height="10" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1" - id="rect304" /> - <line - x1="157" - y1="244" - x2="163" - y2="244" - style="stroke:rgb(0,0,0);stroke-width:1" - id="line306" /> - </g> - </a> - <line - x1="105" - y1="244" - x2="125" - y2="244" - style="stroke:rgb(0,0,0);stroke-width:1;stroke-linecap:round" - id="line308" /> - <a - id="a484" - href="#operations-tag" - xlink:arcrole="The <operations> element"> - <g - id="g330"> - <rect - x="20" - y="233" - width="80" - height="22" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1" - id="rect310" /> - <text - x="60" - y="244" - style="font-family:Arial;font-size:8pt;fill:rgb(0,0,0);font-weight:bold;text-anchor:middle;dominant-baseline:central" - id="text312">operations</text> - <rect - x="95" - y="239" - width="10" - height="10" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1" - id="rect314" /> - <line - x1="97" - y1="244" - x2="103" - y2="244" - style="stroke:rgb(0,0,0);stroke-width:1" - id="line316" /> - </g> - </a> -</svg> +<svg xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" id="svg318" width="561" height="478" version="1.1"><metadata id="metadata324"/><a id="a4301" href="#field-tag" title="zero or more <field> elements"><g id="g417"><rect id="rect2" width="44" height="22" x="491" y="28" style="fill:#fff;stroke:#000;stroke-width:1;stroke-dasharray:4,1"/><rect id="rect4" width="44" height="22" x="488" y="25" style="fill:#fff;stroke:#000;stroke-width:1;stroke-dasharray:4,1"/><text x="510" y="36" style="font-family:Arial;font-size:8pt;fill:#000;font-weight:700;text-anchor:middle;dominant-baseline:central" id="text6">field</text><text x="522" y="57" style="font-family:Arial;font-size:7.2pt;fill:#000;text-anchor:end;dominant-baseline:central" id="text8">0..∞</text></g></a><a id="a4308" href="#array-tag" title="zero or more <array> elements"><g id="g442"><rect id="rect18" width="53" height="22" x="491" y="80" style="fill:#fff;stroke:#000;stroke-width:1;stroke-dasharray:4,1"/><rect id="rect20" width="53" height="22" x="488" y="77" style="fill:#fff;stroke:#000;stroke-width:1;stroke-dasharray:4,1"/><text x="514.5" y="88" style="font-family:Arial;font-size:8pt;fill:#000;font-weight:700;text-anchor:middle;dominant-baseline:central" id="text22">array</text><text x="536" y="109" style="font-family:Arial;font-size:7.2pt;fill:#000;text-anchor:end;dominant-baseline:central" id="text24">0..∞</text><rect id="rect26" width="10" height="10" x="536" y="83" style="fill:#fff;stroke:#000;stroke-width:1"/><line id="line28" x1="538" x2="544" y1="88" y2="88" style="stroke:#000;stroke-width:1"/><line id="line30" x1="541" x2="541" y1="85" y2="91" style="stroke:#000;stroke-width:1"/></g></a><line id="line46" x1="478" x2="488" y1="36" y2="36" style="stroke:#000;stroke-width:1;stroke-linecap:round"/><line id="line48" x1="478" x2="488" y1="88" y2="88" style="stroke:#000;stroke-width:1;stroke-linecap:round"/><a id="a4318" href="#object-tag" title="zero or more <object> elements"><g id="g452"><rect id="rect32" width="58" height="22" x="491" y="132" style="fill:#fff;stroke:#000;stroke-width:1;stroke-dasharray:4,1"/><rect id="rect34" width="58" height="22" x="488" y="129" style="fill:#fff;stroke:#000;stroke-width:1;stroke-dasharray:4,1"/><text x="517" y="140" style="font-family:Arial;font-size:8pt;fill:#000;font-weight:700;text-anchor:middle;dominant-baseline:central" id="text36">object</text><text x="541" y="161" style="font-family:Arial;font-size:7.2pt;fill:#000;text-anchor:end;dominant-baseline:central" id="text38">0..∞</text><rect id="rect40" width="10" height="10" x="541" y="135" style="fill:#fff;stroke:#000;stroke-width:1"/><line id="line42" x1="543" x2="549" y1="140" y2="140" style="stroke:#000;stroke-width:1"/><line id="line44" x1="546" x2="546" y1="137" y2="143" style="stroke:#000;stroke-width:1"/><line id="line50" x1="478" x2="488" y1="140" y2="140" style="stroke:#000;stroke-width:1;stroke-linecap:round"/></g></a><line id="line52" x1="478" x2="478" y1="36" y2="140" style="stroke:#000;stroke-width:1;stroke-linecap:round"/><line id="line54" x1="468" x2="478" y1="88" y2="88" style="stroke:#000;stroke-width:1;stroke-linecap:round"/><a id="a4284" title="that may contain zero or more times one of the following nodes"><g id="g433"><path d="M437 81 L460 81 L466 87 L466 95 L460 101 L437 101 L431 95 L431 87 Z" style="fill:#fff;stroke:#000;stroke-width:1;stroke-dasharray:4,1" id="path56"/><path d="M434 78 L457 78 L463 84 L463 92 L457 98 L434 98 L428 92 L428 84 Z" style="fill:#fff;stroke:#000;stroke-width:1;stroke-dasharray:4,1" id="path58"/><line id="line60" x1="433" x2="437" y1="88" y2="88" style="stroke:#000;stroke-width:1"/><line id="line62" x1="437" x2="441" y1="88" y2="84" style="stroke:#000;stroke-width:1"/><line id="line64" x1="449" x2="453" y1="84" y2="84" style="stroke:#000;stroke-width:1"/><line id="line66" x1="449" x2="457" y1="88" y2="88" style="stroke:#000;stroke-width:1"/><line id="line68" x1="449" x2="453" y1="92" y2="92" style="stroke:#000;stroke-width:1"/><line id="line70" x1="453" x2="453" y1="84" y2="92" style="stroke:#000;stroke-width:1"/><ellipse id="ellipse72" cx="445" cy="84" rx="2" ry="2"/><ellipse id="ellipse74" cx="445" cy="88" rx="2" ry="2"/><ellipse id="ellipse76" cx="445" cy="92" rx="2" ry="2"/><text x="458" y="108" style="font-family:Arial;font-size:7.2pt;fill:#000;text-anchor:end;dominant-baseline:central" id="text78">0..∞</text><rect id="rect80" width="10" height="10" x="458" y="83" style="fill:#fff;stroke:#000;stroke-width:1"/><line id="line82" x1="460" x2="466" y1="88" y2="88" style="stroke:#000;stroke-width:1"/></g></a><line id="line84" x1="408" x2="428" y1="88" y2="88" style="stroke:#000;stroke-width:1;stroke-linecap:round"/><a id="a4227" href="#field-tag" title="zero or more <field> elements"><g id="g387"><rect id="rect98" width="44" height="22" x="348" y="184" style="fill:#fff;stroke:#000;stroke-width:1;stroke-dasharray:4,1"/><rect id="rect100" width="44" height="22" x="345" y="181" style="fill:#fff;stroke:#000;stroke-width:1;stroke-dasharray:4,1"/><text x="367" y="192" style="font-family:Arial;font-size:8pt;fill:#000;font-weight:700;text-anchor:middle;dominant-baseline:central" id="text102">field</text><text x="379" y="213" style="font-family:Arial;font-size:7.2pt;fill:#000;text-anchor:end;dominant-baseline:central" id="text104">0..∞</text></g></a><a id="a4262" href="#value-tag" title="zero or one <value> element"><g id="g466"><rect id="rect128" width="49" height="22" x="483" y="285" style="fill:#fff;stroke:#000;stroke-width:1;stroke-dasharray:4,1"/><text x="507.5" y="296" style="font-family:Arial;font-size:8pt;fill:#000;font-weight:700;text-anchor:middle;dominant-baseline:central" id="text130">value</text></g></a><a id="a4329" href="#object-tag" title="zero or more <object> elements"><g id="g462"><rect id="rect114" width="58" height="22" x="486" y="236" style="fill:#fff;stroke:#000;stroke-width:1;stroke-dasharray:4,1"/><rect id="rect116" width="58" height="22" x="483" y="233" style="fill:#fff;stroke:#000;stroke-width:1;stroke-dasharray:4,1"/><text x="512" y="244" style="font-family:Arial;font-size:8pt;fill:#000;font-weight:700;text-anchor:middle;dominant-baseline:central" id="text118">object</text><text x="536" y="265" style="font-family:Arial;font-size:7.2pt;fill:#000;text-anchor:end;dominant-baseline:central" id="text120">0..∞</text><rect id="rect122" width="10" height="10" x="536" y="239" style="fill:#fff;stroke:#000;stroke-width:1"/><line id="line124" x1="538" x2="544" y1="244" y2="244" style="stroke:#000;stroke-width:1"/><line id="line126" x1="541" x2="541" y1="241" y2="247" style="stroke:#000;stroke-width:1"/><line id="line140" x1="473" x2="483" y1="244" y2="244" style="stroke:#000;stroke-width:1;stroke-linecap:round"/></g></a><line id="line142" x1="473" x2="483" y1="296" y2="296" style="stroke:#000;stroke-width:1;stroke-linecap:round"/><line id="line144" x1="473" x2="473" y1="244" y2="296" style="stroke:#000;stroke-width:1;stroke-linecap:round"/><line id="line146" x1="463" x2="473" y1="270" y2="270" style="stroke:#000;stroke-width:1;stroke-linecap:round"/><a id="a4267" title="that may contain zero or more times one of the following nodes"><g id="g482"><path d="M432 263 L455 263 L461 269 L461 277 L455 283 L432 283 L426 277 L426 269 Z" style="fill:#fff;stroke:#000;stroke-width:1;stroke-dasharray:4,1" id="path148"/><path d="M429 260 L452 260 L458 266 L458 274 L452 280 L429 280 L423 274 L423 266 Z" style="fill:#fff;stroke:#000;stroke-width:1;stroke-dasharray:4,1" id="path150"/><line id="line152" x1="428" x2="432" y1="270" y2="270" style="stroke:#000;stroke-width:1"/><line id="line154" x1="432" x2="436" y1="270" y2="266" style="stroke:#000;stroke-width:1"/><line id="line156" x1="444" x2="448" y1="266" y2="266" style="stroke:#000;stroke-width:1"/><line id="line158" x1="444" x2="452" y1="270" y2="270" style="stroke:#000;stroke-width:1"/><line id="line160" x1="444" x2="448" y1="274" y2="274" style="stroke:#000;stroke-width:1"/><line id="line162" x1="448" x2="448" y1="266" y2="274" style="stroke:#000;stroke-width:1"/><ellipse id="ellipse164" cx="440" cy="266" rx="2" ry="2"/><ellipse id="ellipse166" cx="440" cy="270" rx="2" ry="2"/><ellipse id="ellipse168" cx="440" cy="274" rx="2" ry="2"/><text x="453" y="290" style="font-family:Arial;font-size:7.2pt;fill:#000;text-anchor:end;dominant-baseline:central" id="text170">0..∞</text><rect id="rect172" width="10" height="10" x="453" y="265" style="fill:#fff;stroke:#000;stroke-width:1"/><line id="line174" x1="455" x2="461" y1="270" y2="270" style="stroke:#000;stroke-width:1"/></g></a><line id="line176" x1="403" x2="423" y1="270" y2="270" style="stroke:#000;stroke-width:1;stroke-linecap:round"/><a id="a4234" href="#array-tag" title="zero or more <array> elements"><g id="g395"><rect id="rect178" width="53" height="22" x="348" y="262" style="fill:#fff;stroke:#000;stroke-width:1;stroke-dasharray:4,1"/><rect id="rect180" width="53" height="22" x="345" y="259" style="fill:#fff;stroke:#000;stroke-width:1;stroke-dasharray:4,1"/><text x="371.5" y="270" style="font-family:Arial;font-size:8pt;fill:#000;font-weight:700;text-anchor:middle;dominant-baseline:central" id="text182">array</text><text x="393" y="291" style="font-family:Arial;font-size:7.2pt;fill:#000;text-anchor:end;dominant-baseline:central" id="text184">0..∞</text><rect id="rect186" width="10" height="10" x="393" y="265" style="fill:#fff;stroke:#000;stroke-width:1"/><line id="line188" x1="395" x2="401" y1="270" y2="270" style="stroke:#000;stroke-width:1"/></g></a><a id="a4243" href="#header-tag" title="zero or more <header> elements"><g id="g400"><rect id="rect190" width="57" height="22" x="348" y="340" style="fill:#fff;stroke:#000;stroke-width:1;stroke-dasharray:4,1"/><rect id="rect192" width="57" height="22" x="345" y="337" style="fill:#fff;stroke:#000;stroke-width:1;stroke-dasharray:4,1"/><text x="373.5" y="348" style="font-family:Arial;font-size:8pt;fill:#000;font-weight:700;text-anchor:middle;dominant-baseline:central" id="text194">header</text></g></a><text x="392" y="369" style="font-family:Arial;font-size:7.2pt;fill:#000;text-anchor:end;dominant-baseline:central" id="text196">0..∞</text><a id="a4249" href="#param-tag" title="zero or more <param> elements"><g id="g406"><rect id="rect206" width="54" height="22" x="348" y="392" style="fill:#fff;stroke:#000;stroke-width:1;stroke-dasharray:4,1"/><rect id="rect208" width="54" height="22" x="345" y="389" style="fill:#fff;stroke:#000;stroke-width:1;stroke-dasharray:4,1"/><text x="372" y="400" style="font-family:Arial;font-size:8pt;fill:#000;font-weight:700;text-anchor:middle;dominant-baseline:central" id="text210">param</text><text x="389" y="421" style="font-family:Arial;font-size:7.2pt;fill:#000;text-anchor:end;dominant-baseline:central" id="text212">0..∞</text></g></a><a id="a4216" href="#object-tag" title="zero or more <object> elements"><g id="g381"><g id="g346"><rect id="rect86" style="fill:#fff;stroke:#000;stroke-width:1;stroke-dasharray:4,1" width="58" height="22" x="348" y="80"/><rect id="rect88" style="fill:#fff;stroke:#000;stroke-width:1;stroke-dasharray:4,1" width="58" height="22" x="345" y="77"/><text id="text90" style="font-family:Arial;font-size:8pt;fill:#000;font-weight:700;text-anchor:middle;dominant-baseline:central" x="374" y="88">object</text><text id="text92" style="font-family:Arial;font-size:7.2pt;fill:#000;text-anchor:end;dominant-baseline:central" x="398" y="109">0..∞</text><rect id="rect94" style="fill:#fff;stroke:#000;stroke-width:1" width="10" height="10" x="398" y="83"/><line id="line96" style="stroke:#000;stroke-width:1" x1="400" x2="406" y1="88" y2="88"/></g><line id="line234" x1="335" x2="345" y1="88" y2="88" style="stroke:#000;stroke-width:1;stroke-linecap:round"/></g></a><line id="line236" x1="335" x2="345" y1="192" y2="192" style="stroke:#000;stroke-width:1;stroke-linecap:round"/><line id="line238" x1="335" x2="345" y1="270" y2="270" style="stroke:#000;stroke-width:1;stroke-linecap:round"/><line id="line240" x1="335" x2="345" y1="348" y2="348" style="stroke:#000;stroke-width:1;stroke-linecap:round"/><line id="line242" x1="335" x2="345" y1="400" y2="400" style="stroke:#000;stroke-width:1;stroke-linecap:round"/><a id="a4256" href="#contentType-tag" title="one <contentType> element"><g id="g411"><rect id="rect222" width="84" height="22" x="345" y="441" style="fill:#fff;stroke:#000;stroke-width:1"/><text x="387" y="452" style="font-family:Arial;font-size:8pt;fill:#000;font-weight:700;text-anchor:middle;dominant-baseline:central" id="text224">contentType</text><line id="line244" x1="335" x2="345" y1="452" y2="452" style="stroke:#000;stroke-width:1;stroke-linecap:round"/></g></a><line id="line246" x1="335" x2="335" y1="88" y2="452" style="stroke:#000;stroke-width:1;stroke-linecap:round"/><line id="line248" x1="325" x2="335" y1="244" y2="244" style="stroke:#000;stroke-width:1;stroke-linecap:round"/><a id="a4199" title="that may contain zero or more times one of the following nodes"><g id="g371"><path d="M294 237 L317 237 L323 243 L323 251 L317 257 L294 257 L288 251 L288 243 Z" style="fill:#fff;stroke:#000;stroke-width:1;stroke-dasharray:4,1" id="path250"/><path d="M291 234 L314 234 L320 240 L320 248 L314 254 L291 254 L285 248 L285 240 Z" style="fill:#fff;stroke:#000;stroke-width:1;stroke-dasharray:4,1" id="path252"/><line id="line254" x1="290" x2="294" y1="244" y2="244" style="stroke:#000;stroke-width:1"/><line id="line256" x1="294" x2="298" y1="244" y2="240" style="stroke:#000;stroke-width:1"/><line id="line258" x1="306" x2="310" y1="240" y2="240" style="stroke:#000;stroke-width:1"/><line id="line260" x1="306" x2="314" y1="244" y2="244" style="stroke:#000;stroke-width:1"/><line id="line262" x1="306" x2="310" y1="248" y2="248" style="stroke:#000;stroke-width:1"/><line id="line264" x1="310" x2="310" y1="240" y2="248" style="stroke:#000;stroke-width:1"/><ellipse id="ellipse266" cx="302" cy="240" rx="2" ry="2"/><ellipse id="ellipse268" cx="302" cy="244" rx="2" ry="2"/><ellipse id="ellipse270" cx="302" cy="248" rx="2" ry="2"/><text x="315" y="264" style="font-family:Arial;font-size:7.2pt;fill:#000;text-anchor:end;dominant-baseline:central" id="text272">0..∞</text><rect id="rect274" width="10" height="10" x="315" y="239" style="fill:#fff;stroke:#000;stroke-width:1"/><line id="line276" x1="317" x2="323" y1="244" y2="244" style="stroke:#000;stroke-width:1"/></g></a><line id="line278" x1="265" x2="285" y1="244" y2="244" style="stroke:#000;stroke-width:1;stroke-linecap:round"/><a id="a4190" href="#operation-tag" title="zero or more <operation> elements"><g id="g338"><rect id="rect280" width="75" height="22" x="188" y="236" style="fill:#fff;stroke:#000;stroke-width:1;stroke-dasharray:4,1"/><rect id="rect282" width="75" height="22" x="185" y="233" style="fill:#fff;stroke:#000;stroke-width:1;stroke-dasharray:4,1"/><text x="222.5" y="244" style="font-family:Arial;font-size:8pt;fill:#000;font-weight:700;text-anchor:middle;dominant-baseline:central" id="text284">operation</text><text x="255" y="265" style="font-family:Arial;font-size:7.2pt;fill:#000;text-anchor:end;dominant-baseline:central" id="text286">0..∞</text><rect id="rect288" width="10" height="10" x="255" y="239" style="fill:#fff;stroke:#000;stroke-width:1"/><line id="line290" x1="257" x2="263" y1="244" y2="244" style="stroke:#000;stroke-width:1"/></g></a><line id="line292" x1="165" x2="185" y1="244" y2="244" style="stroke:#000;stroke-width:1;stroke-linecap:round"/><a id="a4180" title="that contains a sequence of"><g id="g355"><path d="M131 234 L154 234 L160 240 L160 248 L154 254 L131 254 L125 248 L125 240 Z" style="fill:#fff;stroke:#000;stroke-width:1" id="path294"/><line id="line296" x1="128" x2="157" y1="244" y2="244" style="stroke:#000;stroke-width:1"/><ellipse id="ellipse298" cx="137" cy="244" rx="2" ry="2"/><ellipse id="ellipse300" cx="142" cy="244" rx="2" ry="2"/><ellipse id="ellipse302" cx="147" cy="244" rx="2" ry="2"/><rect id="rect304" width="10" height="10" x="155" y="239" style="fill:#fff;stroke:#000;stroke-width:1"/><line id="line306" x1="157" x2="163" y1="244" y2="244" style="stroke:#000;stroke-width:1"/></g></a><line id="line308" x1="105" x2="125" y1="244" y2="244" style="stroke:#000;stroke-width:1;stroke-linecap:round"/><a id="a484" href="#operations-tag" xlink:arcrole="The <operations> element"><g id="g330"><rect id="rect310" width="80" height="22" x="20" y="233" style="fill:#fff;stroke:#000;stroke-width:1"/><text x="60" y="244" style="font-family:Arial;font-size:8pt;fill:#000;font-weight:700;text-anchor:middle;dominant-baseline:central" id="text312">operations</text><rect id="rect314" width="10" height="10" x="95" y="239" style="fill:#fff;stroke:#000;stroke-width:1"/><line id="line316" x1="97" x2="103" y1="244" y2="244" style="stroke:#000;stroke-width:1"/></g></a></svg> \ No newline at end of file diff --git a/docs/img/mftf-extending-precedence.png b/docs/img/mftf-extending-precedence.png new file mode 100644 index 000000000..deed5231f Binary files /dev/null and b/docs/img/mftf-extending-precedence.png differ diff --git a/docs/img/page-dia.svg b/docs/img/page-dia.svg index 668891db0..10f014328 100644 --- a/docs/img/page-dia.svg +++ b/docs/img/page-dia.svg @@ -1,290 +1 @@ -<?xml version="1.0" encoding="UTF-8" standalone="no"?> -<svg - xmlns:dc="http://purl.org/dc/elements/1.1/" - xmlns:cc="http://creativecommons.org/ns#" - xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" - xmlns:svg="http://www.w3.org/2000/svg" - xmlns="http://www.w3.org/2000/svg" - xmlns:xlink="http://www.w3.org/1999/xlink" - xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" - xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" - width="370" - height="62" - version="1.1" - id="svg78" - sodipodi:docname="page-dia.svg" - inkscape:version="0.92.2 (5c3e80d, 2017-08-06)"> - <metadata - id="metadata84"> - <rdf:RDF> - <cc:Work - rdf:about=""> - <dc:format>image/svg+xml</dc:format> - <dc:type - rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> - </cc:Work> - </rdf:RDF> - </metadata> - <defs - id="defs82" /> - <sodipodi:namedview - pagecolor="#ffffff" - bordercolor="#666666" - borderopacity="1" - objecttolerance="10" - gridtolerance="10" - guidetolerance="10" - inkscape:pageopacity="0" - inkscape:pageshadow="2" - inkscape:window-width="1920" - inkscape:window-height="1017" - id="namedview80" - showgrid="false" - inkscape:zoom="1.5351351" - inkscape:cx="91.410446" - inkscape:cy="18.181373" - inkscape:window-x="3278" - inkscape:window-y="-8" - inkscape:window-maximized="1" - inkscape:current-layer="svg78" /> - <rect - x="304" - y="28" - width="59" - height="22" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1;stroke-dasharray:4,1" - id="rect2" /> - <a - href="#section-tag" - id="a8" - title="zero or more <secion> elements"> - <rect - x="301" - y="25" - width="59" - height="22" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1;stroke-dasharray:4,1" - id="rect4" /> - <text - x="330.5" - y="36" - style="font-family:Arial;font-size:8pt;fill:rgb(0,0,0);font-weight:bold;text-anchor:middle;dominant-baseline:central" - id="text6">section</text> - </a> - <text - x="350" - y="57" - style="font-family:Arial;font-size:7.2pt;fill:rgb(0,0,0);text-anchor:end;dominant-baseline:central" - id="text10">0..∞</text> - <line - x1="281" - y1="36" - x2="301" - y2="36" - style="stroke:rgb(0,0,0);stroke-width:1;stroke-linecap:round" - id="line16" /> - <a - id="a3770" - title="that contains a sequence of"> - <g - id="g3768"> - <path - d="M247 26 L270 26 L276 32 L276 40 L270 46 L247 46 L241 40 L241 32 Z" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1" - id="path18" /> - <line - x1="244" - y1="36" - x2="273" - y2="36" - style="stroke:rgb(0,0,0);stroke-width:1" - id="line20" /> - <ellipse - cx="253" - cy="36" - rx="2" - ry="2" - style="rgb(0,0,0)" - id="ellipse22" /> - <ellipse - cx="258" - cy="36" - rx="2" - ry="2" - style="rgb(0,0,0)" - id="ellipse24" /> - <ellipse - cx="263" - cy="36" - rx="2" - ry="2" - style="rgb(0,0,0)" - id="ellipse26" /> - <rect - x="271" - y="31" - width="10" - height="10" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1" - id="rect28" /> - <line - x1="273" - y1="36" - x2="279" - y2="36" - style="stroke:rgb(0,0,0);stroke-width:1" - id="line30" /> - </g> - </a> - <line - x1="221" - y1="36" - x2="241" - y2="36" - style="stroke:rgb(0,0,0);stroke-width:1;stroke-linecap:round" - id="line32" /> - <rect - x="167" - y="28" - width="52" - height="22" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1" - id="rect34" /> - <a - href="#page-tag" - id="a40" - title="one or more <page> elements"> - <rect - x="164" - y="25" - width="52" - height="22" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1" - id="rect36" /> - <text - x="190" - y="36" - style="font-family:Arial;font-size:8pt;fill:rgb(0,0,0);font-weight:bold;text-anchor:middle;dominant-baseline:central" - id="text38">page</text> - </a> - <text - x="211" - y="57" - style="font-family:Arial;font-size:7.2pt;fill:rgb(0,0,0);text-anchor:end;dominant-baseline:central" - id="text42">1..∞</text> - <rect - x="211" - y="31" - width="10" - height="10" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1" - id="rect48" /> - <line - x1="213" - y1="36" - x2="219" - y2="36" - style="stroke:rgb(0,0,0);stroke-width:1" - id="line50" /> - <line - x1="144" - y1="36" - x2="164" - y2="36" - style="stroke:rgb(0,0,0);stroke-width:1;stroke-linecap:round" - id="line52" /> - <a - id="a3751" - title="that contains a sequence of"> - <g - id="g3749"> - <path - d="M110 26 L133 26 L139 32 L139 40 L133 46 L110 46 L104 40 L104 32 Z" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1" - id="path54" /> - <line - x1="107" - y1="36" - x2="136" - y2="36" - style="stroke:rgb(0,0,0);stroke-width:1" - id="line56" /> - <ellipse - cx="116" - cy="36" - rx="2" - ry="2" - style="rgb(0,0,0)" - id="ellipse58" /> - <ellipse - cx="121" - cy="36" - rx="2" - ry="2" - style="rgb(0,0,0)" - id="ellipse60" /> - <ellipse - cx="126" - cy="36" - rx="2" - ry="2" - style="rgb(0,0,0)" - id="ellipse62" /> - <rect - x="134" - y="31" - width="10" - height="10" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1" - id="rect64" /> - <line - x1="136" - y1="36" - x2="142" - y2="36" - style="stroke:rgb(0,0,0);stroke-width:1" - id="line66" /> - </g> - </a> - <line - x1="84" - y1="36" - x2="104" - y2="36" - style="stroke:rgb(0,0,0);stroke-width:1;stroke-linecap:round" - id="line68" /> - <a - id="a46" - href="#pages-tag" - title="Root element <pages>"> - <g - id="g44"> - <rect - x="20" - y="25" - width="59" - height="22" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1" - id="rect70" /> - <text - x="49.5" - y="36" - style="font-weight:bold;font-size:10.66666698px;font-family:Arial;dominant-baseline:central;text-anchor:middle;fill:#000000" - id="text72">pages</text> - <rect - x="74" - y="31" - width="10" - height="10" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1" - id="rect74" /> - <line - x1="76" - y1="36" - x2="82" - y2="36" - style="stroke:rgb(0,0,0);stroke-width:1" - id="line76" /> - </g> - </a> -</svg> +<svg xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" id="svg78" width="370" height="62" version="1.1"><metadata id="metadata84"/><rect id="rect2" width="59" height="22" x="304" y="28" style="fill:#fff;stroke:#000;stroke-width:1;stroke-dasharray:4,1"/><a id="a8" href="#section-tag" title="zero or more <secion> elements"><rect id="rect4" width="59" height="22" x="301" y="25" style="fill:#fff;stroke:#000;stroke-width:1;stroke-dasharray:4,1"/><text x="330.5" y="36" style="font-family:Arial;font-size:8pt;fill:#000;font-weight:700;text-anchor:middle;dominant-baseline:central" id="text6">section</text></a><text x="350" y="57" style="font-family:Arial;font-size:7.2pt;fill:#000;text-anchor:end;dominant-baseline:central" id="text10">0..∞</text><line id="line16" x1="281" x2="301" y1="36" y2="36" style="stroke:#000;stroke-width:1;stroke-linecap:round"/><a id="a3770" title="that contains a sequence of"><g id="g3768"><path d="M247 26 L270 26 L276 32 L276 40 L270 46 L247 46 L241 40 L241 32 Z" style="fill:#fff;stroke:#000;stroke-width:1" id="path18"/><line id="line20" x1="244" x2="273" y1="36" y2="36" style="stroke:#000;stroke-width:1"/><ellipse id="ellipse22" cx="253" cy="36" rx="2" ry="2"/><ellipse id="ellipse24" cx="258" cy="36" rx="2" ry="2"/><ellipse id="ellipse26" cx="263" cy="36" rx="2" ry="2"/><rect id="rect28" width="10" height="10" x="271" y="31" style="fill:#fff;stroke:#000;stroke-width:1"/><line id="line30" x1="273" x2="279" y1="36" y2="36" style="stroke:#000;stroke-width:1"/></g></a><line id="line32" x1="221" x2="241" y1="36" y2="36" style="stroke:#000;stroke-width:1;stroke-linecap:round"/><rect id="rect34" width="52" height="22" x="167" y="28" style="fill:#fff;stroke:#000;stroke-width:1"/><a id="a40" href="#page-tag" title="one or more <page> elements"><rect id="rect36" width="52" height="22" x="164" y="25" style="fill:#fff;stroke:#000;stroke-width:1"/><text x="190" y="36" style="font-family:Arial;font-size:8pt;fill:#000;font-weight:700;text-anchor:middle;dominant-baseline:central" id="text38">page</text></a><text x="211" y="57" style="font-family:Arial;font-size:7.2pt;fill:#000;text-anchor:end;dominant-baseline:central" id="text42">1..∞</text><rect id="rect48" width="10" height="10" x="211" y="31" style="fill:#fff;stroke:#000;stroke-width:1"/><line id="line50" x1="213" x2="219" y1="36" y2="36" style="stroke:#000;stroke-width:1"/><line id="line52" x1="144" x2="164" y1="36" y2="36" style="stroke:#000;stroke-width:1;stroke-linecap:round"/><a id="a3751" title="that contains a sequence of"><g id="g3749"><path d="M110 26 L133 26 L139 32 L139 40 L133 46 L110 46 L104 40 L104 32 Z" style="fill:#fff;stroke:#000;stroke-width:1" id="path54"/><line id="line56" x1="107" x2="136" y1="36" y2="36" style="stroke:#000;stroke-width:1"/><ellipse id="ellipse58" cx="116" cy="36" rx="2" ry="2"/><ellipse id="ellipse60" cx="121" cy="36" rx="2" ry="2"/><ellipse id="ellipse62" cx="126" cy="36" rx="2" ry="2"/><rect id="rect64" width="10" height="10" x="134" y="31" style="fill:#fff;stroke:#000;stroke-width:1"/><line id="line66" x1="136" x2="142" y1="36" y2="36" style="stroke:#000;stroke-width:1"/></g></a><line id="line68" x1="84" x2="104" y1="36" y2="36" style="stroke:#000;stroke-width:1;stroke-linecap:round"/><a id="a46" href="#pages-tag" title="Root element <pages>"><g id="g44"><rect id="rect70" width="59" height="22" x="20" y="25" style="fill:#fff;stroke:#000;stroke-width:1"/><text x="49.5" y="36" style="font-weight:700;font-size:10.66666698px;font-family:Arial;dominant-baseline:central;text-anchor:middle;fill:#000" id="text72">pages</text><rect id="rect74" width="10" height="10" x="74" y="31" style="fill:#fff;stroke:#000;stroke-width:1"/><line id="line76" x1="76" x2="82" y1="36" y2="36" style="stroke:#000;stroke-width:1"/></g></a></svg> \ No newline at end of file diff --git a/docs/img/pull-request.png b/docs/img/pull-request.png deleted file mode 100644 index c492350d8..000000000 Binary files a/docs/img/pull-request.png and /dev/null differ diff --git a/docs/img/section-dia.svg b/docs/img/section-dia.svg index 1ee7611af..5eb80bbb4 100644 --- a/docs/img/section-dia.svg +++ b/docs/img/section-dia.svg @@ -1,296 +1 @@ -<?xml version="1.0" encoding="UTF-8" standalone="no"?> -<svg - xmlns:dc="http://purl.org/dc/elements/1.1/" - xmlns:cc="http://creativecommons.org/ns#" - xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" - xmlns:svg="http://www.w3.org/2000/svg" - xmlns="http://www.w3.org/2000/svg" - xmlns:xlink="http://www.w3.org/1999/xlink" - xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" - xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" - width="385" - height="62" - version="1.1" - id="svg74" - sodipodi:docname="section-dia.svg" - inkscape:version="0.92.2 (5c3e80d, 2017-08-06)"> - <metadata - id="metadata80"> - <rdf:RDF> - <cc:Work - rdf:about=""> - <dc:format>image/svg+xml</dc:format> - <dc:type - rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> - </cc:Work> - </rdf:RDF> - </metadata> - <defs - id="defs78" /> - <sodipodi:namedview - pagecolor="#ffffff" - bordercolor="#666666" - borderopacity="1" - objecttolerance="10" - gridtolerance="10" - guidetolerance="10" - inkscape:pageopacity="0" - inkscape:pageshadow="2" - inkscape:window-width="1920" - inkscape:window-height="1017" - id="namedview76" - showgrid="false" - inkscape:zoom="2.0864242" - inkscape:cx="154.86083" - inkscape:cy="15.327771" - inkscape:window-x="3278" - inkscape:window-y="-8" - inkscape:window-maximized="1" - inkscape:current-layer="svg74" /> - <rect - x="316" - y="28" - width="62" - height="22" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1" - id="rect2" /> - <a - id="a3803" - href="#element-tag" - title="one or more <element> elements"> - <g - id="g88"> - <rect - x="313" - y="25" - width="62" - height="22" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1" - id="rect4" /> - <text - x="344" - y="36" - style="font-family:Arial;font-size:8pt;fill:rgb(0,0,0);font-weight:bold;text-anchor:middle;dominant-baseline:central" - id="text6">element</text> - </g> - </a> - <text - x="365" - y="57" - style="font-family:Arial;font-size:7.2pt;fill:rgb(0,0,0);text-anchor:end;dominant-baseline:central" - id="text8">1..∞</text> - <line - x1="293" - y1="36" - x2="313" - y2="36" - style="stroke:rgb(0,0,0);stroke-width:1;stroke-linecap:round" - id="line14" /> - <a - id="a3777" - title="that contains a sequence of"> - <g - id="g3775"> - <path - d="M259 26 L282 26 L288 32 L288 40 L282 46 L259 46 L253 40 L253 32 Z" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1" - id="path16" /> - <line - x1="256" - y1="36" - x2="285" - y2="36" - style="stroke:rgb(0,0,0);stroke-width:1" - id="line18" /> - <ellipse - cx="265" - cy="36" - rx="2" - ry="2" - style="rgb(0,0,0)" - id="ellipse20" /> - <ellipse - cx="270" - cy="36" - rx="2" - ry="2" - style="rgb(0,0,0)" - id="ellipse22" /> - <ellipse - cx="275" - cy="36" - rx="2" - ry="2" - style="rgb(0,0,0)" - id="ellipse24" /> - <rect - x="283" - y="31" - width="10" - height="10" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1" - id="rect26" /> - <line - x1="285" - y1="36" - x2="291" - y2="36" - style="stroke:rgb(0,0,0);stroke-width:1" - id="line28" /> - </g> - </a> - <line - x1="233" - y1="36" - x2="253" - y2="36" - style="stroke:rgb(0,0,0);stroke-width:1;stroke-linecap:round" - id="line30" /> - <rect - x="167" - y="28" - width="64" - height="22" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1" - id="rect32" /> - <a - id="a109" - href="#section-tag" - title="one or more <section> elements"> - <g - id="g84"> - <path - inkscape:connector-curvature="0" - id="rect34" - d="m 164,25 c 21.33333,0 42.66667,0 64,0 0,7.333333 0,14.666667 0,22 -21.33333,0 -42.66667,0 -64,0 0,-7.333333 0,-14.666667 0,-22 z" - style="fill:#ffffff;stroke:#000000;stroke-width:1" /> - <path - inkscape:connector-curvature="0" - id="text36" - d="m 177.5625,37.363255 c 1.52529,-0.935044 2.52281,1.779504 3.69792,0.04687 -1.41244,-0.537747 -5.23048,-1.866312 -2.60417,-3.849609 1.13374,-1.247589 5.42381,0.888062 2.93622,1.363663 -0.52908,-0.06221 -2.59658,-1.343691 -2.32164,0.136988 1.91588,0.03374 5.07512,1.94106 2.46713,3.697591 -1.42333,0.629818 -3.75169,0.397163 -4.17546,-1.395508 z m 9.65625,-0.182291 c 2.44842,-0.276007 0.71062,2.155886 -0.94792,1.885417 -2.98868,0.278515 -3.66937,-4.498438 -1.14974,-5.585938 1.95032,-1.411312 4.8677,2.271182 3.08252,3.117187 -1.03835,0 -2.07671,0 -3.11507,0 -0.18497,1.277388 1.81333,2.021528 2.13021,0.583334 z m 0.0833,-1.479167 c 0.38377,-2.44698 -3.53646,-0.483165 -1.6359,0 0.5453,0 1.0906,0 1.6359,0 z m 7.47396,-0.65625 c -1.52854,0.994728 -2.51116,-1.881488 -3.55989,0.273438 -0.96483,1.83752 1.70256,3.656455 2.33125,1.525753 2.64318,-0.225852 0.0544,2.909802 -1.55668,2.20968 -3.17146,-0.115705 -3.1548,-5.664414 0.0212,-5.757325 1.21388,-0.172487 2.50213,0.483367 2.76416,1.748454 z m 3.65104,-1.635417 c 0.4678,1.281947 -0.63769,0.990621 -1,1.442464 0.17471,0.96121 -0.40103,2.406694 0.40625,3.000245 1.95951,0.06826 -0.55728,2.393732 -1.625,0.739583 -0.45945,-1.271518 -0.16723,-2.680854 -0.25,-4.015625 -0.97874,0.476369 -0.99812,-1.649412 0,-1.166667 -0.31505,-1.053184 0.48134,-1.467007 1.31127,-1.861536 0.54105,-0.0085 -0.30244,1.868393 0.43328,1.861536 0.2414,0 0.4828,0 0.7242,0 z m 1.02604,-0.75 c -0.20685,-1.071939 0.0909,-1.639223 1.24109,-1.354166 0.51629,0.342671 0.34899,1.883179 -0.74283,1.354166 -0.16609,0 -0.33217,0 -0.49826,0 z m 0,6.281251 c 0,-1.843751 0,-3.687501 0,-5.531251 0.96324,-0.107657 1.81965,-0.108492 1.46355,1.103188 0,1.476021 0,2.952042 0,4.428063 -0.48785,0 -0.9757,0 -1.46355,0 z m 2.6198,-2.843751 c -0.18497,-2.633509 3.5807,-3.821658 5.09424,-1.784912 1.49262,1.743986 0.16168,4.91742 -2.23487,4.753663 -1.65062,0.06716 -2.99523,-1.308404 -2.85937,-2.968751 z m 1.5,0.07813 c -0.0314,3.170045 4.26045,0.891586 2.3125,-1.260416 -0.96403,-1.094803 -2.49485,0.0058 -2.3125,1.260416 z m 10.39062,2.765626 c -0.96323,0.107657 -1.81964,0.108491 -1.46354,-1.103189 -0.16753,-1.109612 0.46733,-2.707337 -0.63151,-3.396812 -1.96808,-0.190926 -1.40078,2.230498 -1.48307,3.511676 0.38258,1.199876 -0.60098,1.033596 -1.46354,0.988325 0,-1.843751 0,-3.687501 0,-5.531251 1.63308,-0.62997 1.077,1.516748 2.17708,0.109375 2.22452,-1.125947 3.22655,1.30944 2.86458,3.087564 0,0.778104 0,1.556208 0,2.334312 z" - style="font-weight:bold;font-size:10.66666698px;font-family:Arial;dominant-baseline:central;text-anchor:middle;fill:#000000" /> - </g> - </a> - <text - x="223" - y="57" - style="font-family:Arial;font-size:7.2pt;fill:rgb(0,0,0);text-anchor:end;dominant-baseline:central" - id="text38">1..∞</text> - <rect - x="223" - y="31" - width="10" - height="10" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1" - id="rect44" /> - <line - x1="225" - y1="36" - x2="231" - y2="36" - style="stroke:rgb(0,0,0);stroke-width:1" - id="line46" /> - <line - x1="144" - y1="36" - x2="164" - y2="36" - style="stroke:rgb(0,0,0);stroke-width:1;stroke-linecap:round" - id="line48" /> - <a - id="a3757" - title="that contains a sequence of"> - <g - id="g3755"> - <path - d="M110 26 L133 26 L139 32 L139 40 L133 46 L110 46 L104 40 L104 32 Z" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1" - id="path50" /> - <line - x1="107" - y1="36" - x2="136" - y2="36" - style="stroke:rgb(0,0,0);stroke-width:1" - id="line52" /> - <ellipse - cx="116" - cy="36" - rx="2" - ry="2" - style="rgb(0,0,0)" - id="ellipse54" /> - <ellipse - cx="121" - cy="36" - rx="2" - ry="2" - style="rgb(0,0,0)" - id="ellipse56" /> - <a - id="a3744"> - <ellipse - id="ellipse58" - style="rgb(0,0,0)" - ry="2" - rx="2" - cy="36" - cx="126" /> - </a> - <rect - x="134" - y="31" - width="10" - height="10" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1" - id="rect60" /> - <line - x1="136" - y1="36" - x2="142" - y2="36" - style="stroke:rgb(0,0,0);stroke-width:1" - id="line62" /> - </g> - </a> - <line - x1="84" - y1="36" - x2="104" - y2="36" - style="stroke:rgb(0,0,0);stroke-width:1;stroke-linecap:round" - id="line64" /> - <a - id="a48" - title="Root element <sections>"> - <g - id="g46"> - <rect - x="20" - y="25" - width="59" - height="22" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1" - id="rect66" /> - <text - x="49.5" - y="36" - style="font-family:Arial;font-size:8pt;fill:rgb(0,0,0);font-weight:bold;text-anchor:middle;dominant-baseline:central" - id="text68">sections</text> - <rect - x="74" - y="31" - width="10" - height="10" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1" - id="rect70" /> - <line - x1="76" - y1="36" - x2="82" - y2="36" - style="stroke:rgb(0,0,0);stroke-width:1" - id="line72" /> - </g> - </a> -</svg> +<svg xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" id="svg74" width="385" height="62" version="1.1"><metadata id="metadata80"/><rect id="rect2" width="62" height="22" x="316" y="28" style="fill:#fff;stroke:#000;stroke-width:1"/><a id="a3803" href="#element-tag" title="one or more <element> elements"><g id="g88"><rect id="rect4" width="62" height="22" x="313" y="25" style="fill:#fff;stroke:#000;stroke-width:1"/><text x="344" y="36" style="font-family:Arial;font-size:8pt;fill:#000;font-weight:700;text-anchor:middle;dominant-baseline:central" id="text6">element</text></g></a><text x="365" y="57" style="font-family:Arial;font-size:7.2pt;fill:#000;text-anchor:end;dominant-baseline:central" id="text8">1..∞</text><line id="line14" x1="293" x2="313" y1="36" y2="36" style="stroke:#000;stroke-width:1;stroke-linecap:round"/><a id="a3777" title="that contains a sequence of"><g id="g3775"><path d="M259 26 L282 26 L288 32 L288 40 L282 46 L259 46 L253 40 L253 32 Z" style="fill:#fff;stroke:#000;stroke-width:1" id="path16"/><line id="line18" x1="256" x2="285" y1="36" y2="36" style="stroke:#000;stroke-width:1"/><ellipse id="ellipse20" cx="265" cy="36" rx="2" ry="2"/><ellipse id="ellipse22" cx="270" cy="36" rx="2" ry="2"/><ellipse id="ellipse24" cx="275" cy="36" rx="2" ry="2"/><rect id="rect26" width="10" height="10" x="283" y="31" style="fill:#fff;stroke:#000;stroke-width:1"/><line id="line28" x1="285" x2="291" y1="36" y2="36" style="stroke:#000;stroke-width:1"/></g></a><line id="line30" x1="233" x2="253" y1="36" y2="36" style="stroke:#000;stroke-width:1;stroke-linecap:round"/><rect id="rect32" width="64" height="22" x="167" y="28" style="fill:#fff;stroke:#000;stroke-width:1"/><a id="a109" href="#section-tag" title="one or more <section> elements"><g id="g84"><path id="rect34" d="m 164,25 c 21.33333,0 42.66667,0 64,0 0,7.333333 0,14.666667 0,22 -21.33333,0 -42.66667,0 -64,0 0,-7.333333 0,-14.666667 0,-22 z" style="fill:#fff;stroke:#000;stroke-width:1"/><path id="text36" d="m 177.5625,37.363255 c 1.52529,-0.935044 2.52281,1.779504 3.69792,0.04687 -1.41244,-0.537747 -5.23048,-1.866312 -2.60417,-3.849609 1.13374,-1.247589 5.42381,0.888062 2.93622,1.363663 -0.52908,-0.06221 -2.59658,-1.343691 -2.32164,0.136988 1.91588,0.03374 5.07512,1.94106 2.46713,3.697591 -1.42333,0.629818 -3.75169,0.397163 -4.17546,-1.395508 z m 9.65625,-0.182291 c 2.44842,-0.276007 0.71062,2.155886 -0.94792,1.885417 -2.98868,0.278515 -3.66937,-4.498438 -1.14974,-5.585938 1.95032,-1.411312 4.8677,2.271182 3.08252,3.117187 -1.03835,0 -2.07671,0 -3.11507,0 -0.18497,1.277388 1.81333,2.021528 2.13021,0.583334 z m 0.0833,-1.479167 c 0.38377,-2.44698 -3.53646,-0.483165 -1.6359,0 0.5453,0 1.0906,0 1.6359,0 z m 7.47396,-0.65625 c -1.52854,0.994728 -2.51116,-1.881488 -3.55989,0.273438 -0.96483,1.83752 1.70256,3.656455 2.33125,1.525753 2.64318,-0.225852 0.0544,2.909802 -1.55668,2.20968 -3.17146,-0.115705 -3.1548,-5.664414 0.0212,-5.757325 1.21388,-0.172487 2.50213,0.483367 2.76416,1.748454 z m 3.65104,-1.635417 c 0.4678,1.281947 -0.63769,0.990621 -1,1.442464 0.17471,0.96121 -0.40103,2.406694 0.40625,3.000245 1.95951,0.06826 -0.55728,2.393732 -1.625,0.739583 -0.45945,-1.271518 -0.16723,-2.680854 -0.25,-4.015625 -0.97874,0.476369 -0.99812,-1.649412 0,-1.166667 -0.31505,-1.053184 0.48134,-1.467007 1.31127,-1.861536 0.54105,-0.0085 -0.30244,1.868393 0.43328,1.861536 0.2414,0 0.4828,0 0.7242,0 z m 1.02604,-0.75 c -0.20685,-1.071939 0.0909,-1.639223 1.24109,-1.354166 0.51629,0.342671 0.34899,1.883179 -0.74283,1.354166 -0.16609,0 -0.33217,0 -0.49826,0 z m 0,6.281251 c 0,-1.843751 0,-3.687501 0,-5.531251 0.96324,-0.107657 1.81965,-0.108492 1.46355,1.103188 0,1.476021 0,2.952042 0,4.428063 -0.48785,0 -0.9757,0 -1.46355,0 z m 2.6198,-2.843751 c -0.18497,-2.633509 3.5807,-3.821658 5.09424,-1.784912 1.49262,1.743986 0.16168,4.91742 -2.23487,4.753663 -1.65062,0.06716 -2.99523,-1.308404 -2.85937,-2.968751 z m 1.5,0.07813 c -0.0314,3.170045 4.26045,0.891586 2.3125,-1.260416 -0.96403,-1.094803 -2.49485,0.0058 -2.3125,1.260416 z m 10.39062,2.765626 c -0.96323,0.107657 -1.81964,0.108491 -1.46354,-1.103189 -0.16753,-1.109612 0.46733,-2.707337 -0.63151,-3.396812 -1.96808,-0.190926 -1.40078,2.230498 -1.48307,3.511676 0.38258,1.199876 -0.60098,1.033596 -1.46354,0.988325 0,-1.843751 0,-3.687501 0,-5.531251 1.63308,-0.62997 1.077,1.516748 2.17708,0.109375 2.22452,-1.125947 3.22655,1.30944 2.86458,3.087564 0,0.778104 0,1.556208 0,2.334312 z" style="font-weight:700;font-size:10.66666698px;font-family:Arial;dominant-baseline:central;text-anchor:middle;fill:#000"/></g></a><text x="223" y="57" style="font-family:Arial;font-size:7.2pt;fill:#000;text-anchor:end;dominant-baseline:central" id="text38">1..∞</text><rect id="rect44" width="10" height="10" x="223" y="31" style="fill:#fff;stroke:#000;stroke-width:1"/><line id="line46" x1="225" x2="231" y1="36" y2="36" style="stroke:#000;stroke-width:1"/><line id="line48" x1="144" x2="164" y1="36" y2="36" style="stroke:#000;stroke-width:1;stroke-linecap:round"/><a id="a3757" title="that contains a sequence of"><g id="g3755"><path d="M110 26 L133 26 L139 32 L139 40 L133 46 L110 46 L104 40 L104 32 Z" style="fill:#fff;stroke:#000;stroke-width:1" id="path50"/><line id="line52" x1="107" x2="136" y1="36" y2="36" style="stroke:#000;stroke-width:1"/><ellipse id="ellipse54" cx="116" cy="36" rx="2" ry="2"/><ellipse id="ellipse56" cx="121" cy="36" rx="2" ry="2"/><a id="a3744"><ellipse id="ellipse58" cx="126" cy="36" rx="2" ry="2"/></a><rect id="rect60" width="10" height="10" x="134" y="31" style="fill:#fff;stroke:#000;stroke-width:1"/><line id="line62" x1="136" x2="142" y1="36" y2="36" style="stroke:#000;stroke-width:1"/></g></a><line id="line64" x1="84" x2="104" y1="36" y2="36" style="stroke:#000;stroke-width:1;stroke-linecap:round"/><a id="a48" title="Root element <sections>"><g id="g46"><rect id="rect66" width="59" height="22" x="20" y="25" style="fill:#fff;stroke:#000;stroke-width:1"/><text x="49.5" y="36" style="font-family:Arial;font-size:8pt;fill:#000;font-weight:700;text-anchor:middle;dominant-baseline:central" id="text68">sections</text><rect id="rect70" width="10" height="10" x="74" y="31" style="fill:#fff;stroke:#000;stroke-width:1"/><line id="line72" x1="76" x2="82" y1="36" y2="36" style="stroke:#000;stroke-width:1"/></g></a></svg> \ No newline at end of file diff --git a/docs/img/switching-the-base.png b/docs/img/switching-the-base.png deleted file mode 100644 index 412ab1624..000000000 Binary files a/docs/img/switching-the-base.png and /dev/null differ diff --git a/docs/img/test-dia.svg b/docs/img/test-dia.svg index fcf2628da..47439dbb0 100644 --- a/docs/img/test-dia.svg +++ b/docs/img/test-dia.svg @@ -1,1228 +1 @@ -<?xml version="1.0" encoding="UTF-8" standalone="no"?> -<svg - xmlns:dc="http://purl.org/dc/elements/1.1/" - xmlns:cc="http://creativecommons.org/ns#" - xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" - xmlns:svg="http://www.w3.org/2000/svg" - xmlns="http://www.w3.org/2000/svg" - xmlns:xlink="http://www.w3.org/1999/xlink" - xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" - xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" - width="601" - height="322" - version="1.1" - id="svg330" - sodipodi:docname="test-dia.svg" - inkscape:version="0.92.2 (5c3e80d, 2017-08-06)"> - <metadata - id="metadata336"> - <rdf:RDF> - <cc:Work - rdf:about=""> - <dc:format>image/svg+xml</dc:format> - <dc:type - rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> - </cc:Work> - </rdf:RDF> - </metadata> - <defs - id="defs334" /> - <sodipodi:namedview - pagecolor="#ffffff" - bordercolor="#666666" - borderopacity="1" - objecttolerance="10" - gridtolerance="10" - guidetolerance="10" - inkscape:pageopacity="0" - inkscape:pageshadow="2" - inkscape:window-width="1920" - inkscape:window-height="1017" - id="namedview332" - showgrid="false" - inkscape:zoom="1.890183" - inkscape:cx="272.85453" - inkscape:cy="165.81687" - inkscape:window-x="3278" - inkscape:window-y="-8" - inkscape:window-maximized="1" - inkscape:current-layer="svg330" /> - <g - id="g398"> - <path - id="path2" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1" - d="M354 25 L436 25 L442 31 L442 41 L436 47 L354 47 L348 41 L348 31 Z" /> - <text - id="text4" - style="font-family:Arial;font-size:8pt;fill:rgb(0,0,0);font-weight:bold;text-anchor:middle;dominant-baseline:central" - y="36" - x="395">testTypeTags</text> - <line - id="line6" - style="stroke:rgb(0,0,0);stroke-width:2" - y2="41" - x2="354" - y1="44" - x1="351" /> - <path - id="path8" - style="fill:rgb(0,0,0)" - d="M354 41 L356 43 L357 38 L352 39 L354 41 Z" /> - <rect - id="rect10" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1" - height="10" - width="10" - y="31" - x="437" /> - <line - id="line12" - style="stroke:rgb(0,0,0);stroke-width:1" - y2="36" - x2="445" - y1="36" - x1="439" /> - <line - id="line14" - style="stroke:rgb(0,0,0);stroke-width:1" - y2="39" - x2="442" - y1="33" - x1="442" /> - </g> - <g - id="g429"> - <path - id="path16" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1" - d="M498 77 L580 77 L586 83 L586 93 L580 99 L498 99 L492 93 L492 83 Z" /> - <text - id="text18" - style="font-family:Arial;font-size:8pt;fill:rgb(0,0,0);font-weight:bold;text-anchor:middle;dominant-baseline:central" - y="88" - x="539">testTypeTags</text> - <line - id="line20" - style="stroke:rgb(0,0,0);stroke-width:2" - y2="93" - x2="498" - y1="96" - x1="495" /> - <path - id="path22" - style="fill:rgb(0,0,0)" - d="M498 93 L500 95 L501 90 L496 91 L498 93 Z" /> - <rect - id="rect24" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1" - height="10" - width="10" - y="83" - x="581" /> - <line - id="line26" - style="stroke:rgb(0,0,0);stroke-width:1" - y2="88" - x2="589" - y1="88" - x1="583" /> - <line - id="line28" - style="stroke:rgb(0,0,0);stroke-width:1" - y2="91" - x2="586" - y1="85" - x1="586" /> - </g> - <line - x1="472" - y1="88" - x2="492" - y2="88" - style="stroke:rgb(0,0,0);stroke-width:1;stroke-linecap:round" - id="line30" /> - <a - id="a4301" - title="that may contain any number of the following nodes in any order"> - <g - id="g420"> - <path - d="M441 81 L464 81 L470 87 L470 95 L464 101 L441 101 L435 95 L435 87 Z" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1;stroke-dasharray:4,1" - id="path32" /> - <path - d="M438 78 L461 78 L467 84 L467 92 L461 98 L438 98 L432 92 L432 84 Z" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1;stroke-dasharray:4,1" - id="path34" /> - <line - x1="437" - y1="88" - x2="441" - y2="88" - style="stroke:rgb(0,0,0);stroke-width:1" - id="line36" /> - <line - x1="441" - y1="88" - x2="445" - y2="84" - style="stroke:rgb(0,0,0);stroke-width:1" - id="line38" /> - <line - x1="453" - y1="84" - x2="457" - y2="84" - style="stroke:rgb(0,0,0);stroke-width:1" - id="line40" /> - <line - x1="453" - y1="88" - x2="461" - y2="88" - style="stroke:rgb(0,0,0);stroke-width:1" - id="line42" /> - <line - x1="453" - y1="92" - x2="457" - y2="92" - style="stroke:rgb(0,0,0);stroke-width:1" - id="line44" /> - <line - x1="457" - y1="84" - x2="457" - y2="92" - style="stroke:rgb(0,0,0);stroke-width:1" - id="line46" /> - <ellipse - cx="449" - cy="84" - rx="2" - ry="2" - style="rgb(0,0,0)" - id="ellipse48" /> - <ellipse - cx="449" - cy="88" - rx="2" - ry="2" - style="rgb(0,0,0)" - id="ellipse50" /> - <ellipse - cx="449" - cy="92" - rx="2" - ry="2" - style="rgb(0,0,0)" - id="ellipse52" /> - <text - x="462" - y="108" - style="font-family:Arial;font-size:7.2pt;fill:rgb(0,0,0);text-anchor:end;dominant-baseline:central" - id="text54">0..∞</text> - <rect - x="462" - y="83" - width="10" - height="10" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1" - id="rect56" /> - <line - x1="464" - y1="88" - x2="470" - y2="88" - style="stroke:rgb(0,0,0);stroke-width:1" - id="line58" /> - </g> - </a> - <line - x1="412" - y1="88" - x2="432" - y2="88" - style="stroke:rgb(0,0,0);stroke-width:1;stroke-linecap:round" - id="line60" /> - <a - id="a4270" - href="#before-tag" - title="zero or one <before> element"> - <g - id="g404"> - <rect - x="348" - y="77" - width="59" - height="22" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1;stroke-dasharray:4,1" - id="rect62" /> - <text - x="377.5" - y="88" - style="font-family:Arial;font-size:8pt;fill:rgb(0,0,0);font-weight:bold;text-anchor:middle;dominant-baseline:central" - id="text64">before</text> - <rect - x="402" - y="83" - width="10" - height="10" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1" - id="rect66" /> - <line - x1="404" - y1="88" - x2="410" - y2="88" - style="stroke:rgb(0,0,0);stroke-width:1" - id="line68" /> - </g> - </a> - <g - id="g438"> - <path - id="path70" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1" - d="M489 129 L571 129 L577 135 L577 145 L571 151 L489 151 L483 145 L483 135 Z" /> - <text - id="text72" - style="font-family:Arial;font-size:8pt;fill:rgb(0,0,0);font-weight:bold;text-anchor:middle;dominant-baseline:central" - y="140" - x="530">testTypeTags</text> - <line - id="line74" - style="stroke:rgb(0,0,0);stroke-width:2" - y2="145" - x2="489" - y1="148" - x1="486" /> - <path - id="path76" - style="fill:rgb(0,0,0)" - d="M489 145 L491 147 L492 142 L487 143 L489 145 Z" /> - <rect - id="rect78" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1" - height="10" - width="10" - y="135" - x="572" /> - <line - id="line80" - style="stroke:rgb(0,0,0);stroke-width:1" - y2="140" - x2="580" - y1="140" - x1="574" /> - <line - id="line82" - style="stroke:rgb(0,0,0);stroke-width:1" - y2="143" - x2="577" - y1="137" - x1="577" /> - </g> - <line - x1="463" - y1="140" - x2="483" - y2="140" - style="stroke:rgb(0,0,0);stroke-width:1;stroke-linecap:round" - id="line84" /> - <a - id="a4318" - title="that may contain any number of the following nodes in any order"> - <g - id="g460"> - <path - d="M432 133 L455 133 L461 139 L461 147 L455 153 L432 153 L426 147 L426 139 Z" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1;stroke-dasharray:4,1" - id="path86" /> - <path - d="M429 130 L452 130 L458 136 L458 144 L452 150 L429 150 L423 144 L423 136 Z" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1;stroke-dasharray:4,1" - id="path88" /> - <line - x1="428" - y1="140" - x2="432" - y2="140" - style="stroke:rgb(0,0,0);stroke-width:1" - id="line90" /> - <line - x1="432" - y1="140" - x2="436" - y2="136" - style="stroke:rgb(0,0,0);stroke-width:1" - id="line92" /> - <line - x1="444" - y1="136" - x2="448" - y2="136" - style="stroke:rgb(0,0,0);stroke-width:1" - id="line94" /> - <line - x1="444" - y1="140" - x2="452" - y2="140" - style="stroke:rgb(0,0,0);stroke-width:1" - id="line96" /> - <line - x1="444" - y1="144" - x2="448" - y2="144" - style="stroke:rgb(0,0,0);stroke-width:1" - id="line98" /> - <line - x1="448" - y1="136" - x2="448" - y2="144" - style="stroke:rgb(0,0,0);stroke-width:1" - id="line100" /> - <ellipse - cx="440" - cy="136" - rx="2" - ry="2" - style="rgb(0,0,0)" - id="ellipse102" /> - <ellipse - cx="440" - cy="140" - rx="2" - ry="2" - style="rgb(0,0,0)" - id="ellipse104" /> - <ellipse - cx="440" - cy="144" - rx="2" - ry="2" - style="rgb(0,0,0)" - id="ellipse106" /> - <text - x="453" - y="160" - style="font-family:Arial;font-size:7.2pt;fill:rgb(0,0,0);text-anchor:end;dominant-baseline:central" - id="text108">0..∞</text> - <rect - x="453" - y="135" - width="10" - height="10" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1" - id="rect110" /> - <line - x1="455" - y1="140" - x2="461" - y2="140" - style="stroke:rgb(0,0,0);stroke-width:1" - id="line112" /> - </g> - </a> - <line - x1="403" - y1="140" - x2="423" - y2="140" - style="stroke:rgb(0,0,0);stroke-width:1;stroke-linecap:round" - id="line114" /> - <a - id="a4277" - href="#after-tag" - title="zero or one <after> element"> - <g - id="g444"> - <rect - x="348" - y="129" - width="50" - height="22" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1;stroke-dasharray:4,1" - id="rect116" /> - <text - x="373" - y="140" - style="font-family:Arial;font-size:8pt;fill:rgb(0,0,0);font-weight:bold;text-anchor:middle;dominant-baseline:central" - id="text118">after</text> - <rect - x="393" - y="135" - width="10" - height="10" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1" - id="rect120" /> - <line - x1="395" - y1="140" - x2="401" - y2="140" - style="stroke:rgb(0,0,0);stroke-width:1" - id="line122" /> - </g> - </a> - <a - id="a4335" - title="zero or one <annotations> element" - href="#annotations-tag"> - <g - id="g467"> - <rect - x="348" - y="181" - width="86" - height="22" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1;stroke-dasharray:4,1" - id="rect124" /> - <text - x="391" - y="192" - style="font-family:Arial;font-size:8pt;fill:rgb(0,0,0);font-weight:bold;text-anchor:middle;dominant-baseline:central" - id="text126">annotations</text> - <rect - x="429" - y="187" - width="10" - height="10" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1" - id="rect128" /> - <line - x1="431" - y1="192" - x2="437" - y2="192" - style="stroke:rgb(0,0,0);stroke-width:1" - id="line130" /> - <line - x1="434" - y1="189" - x2="434" - y2="195" - style="stroke:rgb(0,0,0);stroke-width:1" - id="line132" /> - </g> - </a> - <line - x1="338" - y1="36" - x2="348" - y2="36" - style="stroke:rgb(0,0,0);stroke-width:1;stroke-linecap:round" - id="line134" /> - <line - x1="338" - y1="88" - x2="348" - y2="88" - style="stroke:rgb(0,0,0);stroke-width:1;stroke-linecap:round" - id="line136" /> - <line - x1="338" - y1="140" - x2="348" - y2="140" - style="stroke:rgb(0,0,0);stroke-width:1;stroke-linecap:round" - id="line138" /> - <line - x1="338" - y1="192" - x2="348" - y2="192" - style="stroke:rgb(0,0,0);stroke-width:1;stroke-linecap:round" - id="line140" /> - <line - x1="338" - y1="36" - x2="338" - y2="192" - style="stroke:rgb(0,0,0);stroke-width:1;stroke-linecap:round" - id="line142" /> - <line - x1="328" - y1="114" - x2="338" - y2="114" - style="stroke:rgb(0,0,0);stroke-width:1;stroke-linecap:round" - id="line144" /> - <a - id="a4284" - title="that may contain any number of the following nodes in any order"> - <g - id="g389"> - <path - d="M297 107 L320 107 L326 113 L326 121 L320 127 L297 127 L291 121 L291 113 Z" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1;stroke-dasharray:4,1" - id="path146" /> - <path - d="M294 104 L317 104 L323 110 L323 118 L317 124 L294 124 L288 118 L288 110 Z" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1;stroke-dasharray:4,1" - id="path148" /> - <line - x1="293" - y1="114" - x2="297" - y2="114" - style="stroke:rgb(0,0,0);stroke-width:1" - id="line150" /> - <line - x1="297" - y1="114" - x2="301" - y2="110" - style="stroke:rgb(0,0,0);stroke-width:1" - id="line152" /> - <line - x1="309" - y1="110" - x2="313" - y2="110" - style="stroke:rgb(0,0,0);stroke-width:1" - id="line154" /> - <line - x1="309" - y1="114" - x2="317" - y2="114" - style="stroke:rgb(0,0,0);stroke-width:1" - id="line156" /> - <line - x1="309" - y1="118" - x2="313" - y2="118" - style="stroke:rgb(0,0,0);stroke-width:1" - id="line158" /> - <line - x1="313" - y1="110" - x2="313" - y2="118" - style="stroke:rgb(0,0,0);stroke-width:1" - id="line160" /> - <ellipse - cx="305" - cy="110" - rx="2" - ry="2" - style="rgb(0,0,0)" - id="ellipse162" /> - <ellipse - cx="305" - cy="114" - rx="2" - ry="2" - style="rgb(0,0,0)" - id="ellipse164" /> - <ellipse - cx="305" - cy="118" - rx="2" - ry="2" - style="rgb(0,0,0)" - id="ellipse166" /> - <text - x="318" - y="134" - style="font-family:Arial;font-size:7.2pt;fill:rgb(0,0,0);text-anchor:end;dominant-baseline:central" - id="text168">0..∞</text> - <rect - x="318" - y="109" - width="10" - height="10" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1" - id="rect170" /> - <line - x1="320" - y1="114" - x2="326" - y2="114" - style="stroke:rgb(0,0,0);stroke-width:1" - id="line172" /> - </g> - </a> - <line - x1="268" - y1="114" - x2="288" - y2="114" - style="stroke:rgb(0,0,0);stroke-width:1;stroke-linecap:round" - id="line174" /> - <a - id="a4261" - title="one or more <test> elements" - href="#test-tag"> - <g - id="g373"> - <rect - x="220" - y="106" - width="46" - height="22" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1" - id="rect176" /> - <rect - x="217" - y="103" - width="46" - height="22" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1" - id="rect178" /> - <text - x="240" - y="114" - style="font-family:Arial;font-size:8pt;fill:rgb(0,0,0);font-weight:bold;text-anchor:middle;dominant-baseline:central" - id="text180">test</text> - <text - x="258" - y="135" - style="font-family:Arial;font-size:7.2pt;fill:rgb(0,0,0);text-anchor:end;dominant-baseline:central" - id="text182">1..∞</text> - <rect - x="258" - y="109" - width="10" - height="10" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1" - id="rect184" /> - <line - x1="260" - y1="114" - x2="266" - y2="114" - style="stroke:rgb(0,0,0);stroke-width:1" - id="line186" /> - </g> - </a> - <line - x1="197" - y1="114" - x2="217" - y2="114" - style="stroke:rgb(0,0,0);stroke-width:1;stroke-linecap:round" - id="line188" /> - <a - id="a4205" - title="that contains one of the following nodes"> - <g - id="g365"> - <path - d="M163 104 L186 104 L192 110 L192 118 L186 124 L163 124 L157 118 L157 110 Z" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1" - id="path190" /> - <line - x1="162" - y1="114" - x2="166" - y2="114" - style="stroke:rgb(0,0,0);stroke-width:1" - id="line192" /> - <line - x1="166" - y1="114" - x2="170" - y2="110" - style="stroke:rgb(0,0,0);stroke-width:1" - id="line194" /> - <line - x1="178" - y1="110" - x2="182" - y2="110" - style="stroke:rgb(0,0,0);stroke-width:1" - id="line196" /> - <line - x1="178" - y1="114" - x2="186" - y2="114" - style="stroke:rgb(0,0,0);stroke-width:1" - id="line198" /> - <line - x1="178" - y1="118" - x2="182" - y2="118" - style="stroke:rgb(0,0,0);stroke-width:1" - id="line200" /> - <line - x1="182" - y1="110" - x2="182" - y2="118" - style="stroke:rgb(0,0,0);stroke-width:1" - id="line202" /> - <ellipse - cx="174" - cy="110" - rx="2" - ry="2" - style="rgb(0,0,0)" - id="ellipse204" /> - <ellipse - cx="174" - cy="114" - rx="2" - ry="2" - style="rgb(0,0,0)" - id="ellipse206" /> - <ellipse - cx="174" - cy="118" - rx="2" - ry="2" - style="rgb(0,0,0)" - id="ellipse208" /> - <rect - x="187" - y="109" - width="10" - height="10" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1" - id="rect210" /> - <line - x1="189" - y1="114" - x2="195" - y2="114" - style="stroke:rgb(0,0,0);stroke-width:1" - id="line212" /> - </g> - </a> - <line - x1="137" - y1="114" - x2="157" - y2="114" - style="stroke:rgb(0,0,0);stroke-width:1;stroke-linecap:round" - id="line214" /> - <a - id="a4195" - title="that contains a sequence of the following nodes"> - <g - id="g351"> - <path - d="M103 104 L126 104 L132 110 L132 118 L126 124 L103 124 L97 118 L97 110 Z" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1" - id="path216" /> - <line - x1="100" - y1="114" - x2="129" - y2="114" - style="stroke:rgb(0,0,0);stroke-width:1" - id="line218" /> - <ellipse - cx="109" - cy="114" - rx="2" - ry="2" - style="rgb(0,0,0)" - id="ellipse220" /> - <ellipse - cx="114" - cy="114" - rx="2" - ry="2" - style="rgb(0,0,0)" - id="ellipse222" /> - <ellipse - cx="119" - cy="114" - rx="2" - ry="2" - style="rgb(0,0,0)" - id="ellipse224" /> - <rect - x="127" - y="109" - width="10" - height="10" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1" - id="rect226" /> - <line - x1="129" - y1="114" - x2="135" - y2="114" - style="stroke:rgb(0,0,0);stroke-width:1" - id="line228" /> - </g> - </a> - <line - x1="77" - y1="114" - x2="97" - y2="114" - style="stroke:rgb(0,0,0);stroke-width:1;stroke-linecap:round" - id="line230" /> - <a - id="a499" - href="#tests-tag" - title="Root element <tests>"> - <g - id="g342"> - <rect - x="20" - y="103" - width="52" - height="22" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1" - id="rect232" /> - <text - x="46" - y="114" - style="font-family:Arial;font-size:8pt;fill:rgb(0,0,0);font-weight:bold;text-anchor:middle;dominant-baseline:central" - id="text234">tests</text> - <rect - x="67" - y="109" - width="10" - height="10" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1" - id="rect236" /> - <line - x1="69" - y1="114" - x2="75" - y2="114" - style="stroke:rgb(0,0,0);stroke-width:1" - id="line238" /> - </g> - </a> - <a - href="test/actions.html" - title="one of <action> elements" - target="_blank" - id="a4355"> - <g - id="g497"> - <path - d="M205 233 L299 233 L305 239 L305 249 L299 255 L205 255 L199 249 L199 239 Z" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1" - id="path240" /> - <text - x="252" - y="244" - style="font-family:Arial;font-size:8pt;fill:rgb(0,0,0);font-weight:bold;text-anchor:middle;dominant-baseline:central" - id="text242">actionTypeTags</text> - <line - x1="202" - y1="252" - x2="205" - y2="249" - style="stroke:rgb(0,0,0);stroke-width:2" - id="line244" /> - <path - d="M205 249 L207 251 L208 246 L203 247 L205 249 Z" - style="fill:rgb(0,0,0)" - id="path246" /> - <rect - x="300" - y="239" - width="10" - height="10" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1" - id="rect248" /> - <line - x1="302" - y1="244" - x2="308" - y2="244" - style="stroke:rgb(0,0,0);stroke-width:1" - id="line250" /> - <line - x1="305" - y1="241" - x2="305" - y2="247" - style="stroke:rgb(0,0,0);stroke-width:1" - id="line252" /> - </g> - </a> - <a - id="a4350" - href="#argument-tag" - title="one <argument> element"> - <g - id="g471"> - <rect - x="373" - y="285" - width="70" - height="22" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1" - id="rect254" /> - <text - x="408" - y="296" - style="font-family:Arial;font-size:8pt;fill:rgb(0,0,0);font-weight:bold;text-anchor:middle;dominant-baseline:central" - id="text256">argument</text> - </g> - </a> - <line - x1="353" - y1="296" - x2="373" - y2="296" - style="stroke:rgb(0,0,0);stroke-width:1;stroke-linecap:round" - id="line258" /> - <a - id="a4220" - title="that may contain multiple sequences of the following node"> - <g - id="g482"> - <path - d="M322 289 L345 289 L351 295 L351 303 L345 309 L322 309 L316 303 L316 295 Z" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1;stroke-dasharray:4,1" - id="path260" /> - <path - d="M319 286 L342 286 L348 292 L348 300 L342 306 L319 306 L313 300 L313 292 Z" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1;stroke-dasharray:4,1" - id="path262" /> - <line - x1="316" - y1="296" - x2="345" - y2="296" - style="stroke:rgb(0,0,0);stroke-width:1" - id="line264" /> - <ellipse - cx="325" - cy="296" - rx="2" - ry="2" - style="rgb(0,0,0)" - id="ellipse266" /> - <ellipse - cx="330" - cy="296" - rx="2" - ry="2" - style="rgb(0,0,0)" - id="ellipse268" /> - <ellipse - cx="335" - cy="296" - rx="2" - ry="2" - style="rgb(0,0,0)" - id="ellipse270" /> - <text - x="343" - y="316" - style="font-family:Arial;font-size:7.2pt;fill:rgb(0,0,0);text-anchor:end;dominant-baseline:central" - id="text272">0..∞</text> - <rect - x="343" - y="291" - width="10" - height="10" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1" - id="rect274" /> - <line - x1="345" - y1="296" - x2="351" - y2="296" - style="stroke:rgb(0,0,0);stroke-width:1" - id="line276" /> - </g> - </a> - <line - x1="293" - y1="296" - x2="313" - y2="296" - style="stroke:rgb(0,0,0);stroke-width:1;stroke-linecap:round" - id="line278" /> - <a - id="a4343" - href="#actiongroup-tag" - target="" - title="zero or one <actionGroup> elements"> - <g - id="g488"> - <rect - x="199" - y="285" - width="89" - height="22" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1;stroke-dasharray:4,1" - id="rect280" /> - <text - x="243.5" - y="296" - style="font-family:Arial;font-size:8pt;fill:rgb(0,0,0);font-weight:bold;text-anchor:middle;dominant-baseline:central" - id="text282">actionGroup</text> - <rect - x="283" - y="291" - width="10" - height="10" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1" - id="rect284" /> - <line - x1="285" - y1="296" - x2="291" - y2="296" - style="stroke:rgb(0,0,0);stroke-width:1" - id="line286" /> - </g> - </a> - <line - x1="189" - y1="244" - x2="199" - y2="244" - style="stroke:rgb(0,0,0);stroke-width:1;stroke-linecap:round" - id="line288" /> - <line - x1="189" - y1="296" - x2="199" - y2="296" - style="stroke:rgb(0,0,0);stroke-width:1;stroke-linecap:round" - id="line290" /> - <line - x1="189" - y1="244" - x2="189" - y2="296" - style="stroke:rgb(0,0,0);stroke-width:1;stroke-linecap:round" - id="line292" /> - <line - x1="179" - y1="270" - x2="189" - y2="270" - style="stroke:rgb(0,0,0);stroke-width:1;stroke-linecap:round" - id="line294" /> - <a - id="a4246" - title="that contains one of the following nodes"> - <g - id="g4244"> - <path - d="M145 260 L168 260 L174 266 L174 274 L168 280 L145 280 L139 274 L139 266 Z" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1" - id="path296" /> - <line - x1="144" - y1="270" - x2="148" - y2="270" - style="stroke:rgb(0,0,0);stroke-width:1" - id="line298" /> - <line - x1="148" - y1="270" - x2="152" - y2="266" - style="stroke:rgb(0,0,0);stroke-width:1" - id="line300" /> - <line - x1="160" - y1="266" - x2="164" - y2="266" - style="stroke:rgb(0,0,0);stroke-width:1" - id="line302" /> - <line - x1="160" - y1="270" - x2="168" - y2="270" - style="stroke:rgb(0,0,0);stroke-width:1" - id="line304" /> - <line - x1="160" - y1="274" - x2="164" - y2="274" - style="stroke:rgb(0,0,0);stroke-width:1" - id="line306" /> - <line - x1="164" - y1="266" - x2="164" - y2="274" - style="stroke:rgb(0,0,0);stroke-width:1" - id="line308" /> - <ellipse - cx="156" - cy="266" - rx="2" - ry="2" - style="rgb(0,0,0)" - id="ellipse310" /> - <ellipse - cx="156" - cy="270" - rx="2" - ry="2" - style="rgb(0,0,0)" - id="ellipse312" /> - <ellipse - cx="156" - cy="274" - rx="2" - ry="2" - style="rgb(0,0,0)" - id="ellipse314" /> - <rect - x="169" - y="265" - width="10" - height="10" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1" - id="rect316" /> - <line - x1="171" - y1="270" - x2="177" - y2="270" - style="stroke:rgb(0,0,0);stroke-width:1" - id="line318" /> - </g> - </a> - <line - x1="119" - y1="270" - x2="139" - y2="270" - style="stroke:rgb(0,0,0);stroke-width:1;stroke-linecap:round" - id="line320" /> - <path - d="M26 259 L108 259 L114 265 L114 275 L108 281 L26 281 L20 275 L20 265 Z" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1" - id="path322" /> - <text - x="67" - y="270" - style="font-family:Arial;font-size:8pt;fill:rgb(0,0,0);font-weight:bold;text-anchor:middle;dominant-baseline:central" - id="text324">testTypeTags</text> - <rect - x="109" - y="265" - width="10" - height="10" - style="fill:rgb(255,255,255);stroke:rgb(0,0,0);stroke-width:1" - id="rect326" /> - <line - x1="111" - y1="270" - x2="117" - y2="270" - style="stroke:rgb(0,0,0);stroke-width:1" - id="line328" /> -</svg> +<svg xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" id="svg330" width="601" height="322" version="1.1"><metadata id="metadata336"/><g id="g398"><path id="path2" style="fill:#fff;stroke:#000;stroke-width:1" d="M354 25 L436 25 L442 31 L442 41 L436 47 L354 47 L348 41 L348 31 Z"/><text id="text4" style="font-family:Arial;font-size:8pt;fill:#000;font-weight:700;text-anchor:middle;dominant-baseline:central" x="395" y="36">testTypeTags</text><line id="line6" style="stroke:#000;stroke-width:2" x1="351" x2="354" y1="44" y2="41"/><path id="path8" style="fill:#000" d="M354 41 L356 43 L357 38 L352 39 L354 41 Z"/><rect id="rect10" style="fill:#fff;stroke:#000;stroke-width:1" width="10" height="10" x="437" y="31"/><line id="line12" style="stroke:#000;stroke-width:1" x1="439" x2="445" y1="36" y2="36"/><line id="line14" style="stroke:#000;stroke-width:1" x1="442" x2="442" y1="33" y2="39"/></g><g id="g429"><path id="path16" style="fill:#fff;stroke:#000;stroke-width:1" d="M498 77 L580 77 L586 83 L586 93 L580 99 L498 99 L492 93 L492 83 Z"/><text id="text18" style="font-family:Arial;font-size:8pt;fill:#000;font-weight:700;text-anchor:middle;dominant-baseline:central" x="539" y="88">testTypeTags</text><line id="line20" style="stroke:#000;stroke-width:2" x1="495" x2="498" y1="96" y2="93"/><path id="path22" style="fill:#000" d="M498 93 L500 95 L501 90 L496 91 L498 93 Z"/><rect id="rect24" style="fill:#fff;stroke:#000;stroke-width:1" width="10" height="10" x="581" y="83"/><line id="line26" style="stroke:#000;stroke-width:1" x1="583" x2="589" y1="88" y2="88"/><line id="line28" style="stroke:#000;stroke-width:1" x1="586" x2="586" y1="85" y2="91"/></g><line id="line30" x1="472" x2="492" y1="88" y2="88" style="stroke:#000;stroke-width:1;stroke-linecap:round"/><a id="a4301" title="that may contain any number of the following nodes in any order"><g id="g420"><path d="M441 81 L464 81 L470 87 L470 95 L464 101 L441 101 L435 95 L435 87 Z" style="fill:#fff;stroke:#000;stroke-width:1;stroke-dasharray:4,1" id="path32"/><path d="M438 78 L461 78 L467 84 L467 92 L461 98 L438 98 L432 92 L432 84 Z" style="fill:#fff;stroke:#000;stroke-width:1;stroke-dasharray:4,1" id="path34"/><line id="line36" x1="437" x2="441" y1="88" y2="88" style="stroke:#000;stroke-width:1"/><line id="line38" x1="441" x2="445" y1="88" y2="84" style="stroke:#000;stroke-width:1"/><line id="line40" x1="453" x2="457" y1="84" y2="84" style="stroke:#000;stroke-width:1"/><line id="line42" x1="453" x2="461" y1="88" y2="88" style="stroke:#000;stroke-width:1"/><line id="line44" x1="453" x2="457" y1="92" y2="92" style="stroke:#000;stroke-width:1"/><line id="line46" x1="457" x2="457" y1="84" y2="92" style="stroke:#000;stroke-width:1"/><ellipse id="ellipse48" cx="449" cy="84" rx="2" ry="2"/><ellipse id="ellipse50" cx="449" cy="88" rx="2" ry="2"/><ellipse id="ellipse52" cx="449" cy="92" rx="2" ry="2"/><text x="462" y="108" style="font-family:Arial;font-size:7.2pt;fill:#000;text-anchor:end;dominant-baseline:central" id="text54">0..∞</text><rect id="rect56" width="10" height="10" x="462" y="83" style="fill:#fff;stroke:#000;stroke-width:1"/><line id="line58" x1="464" x2="470" y1="88" y2="88" style="stroke:#000;stroke-width:1"/></g></a><line id="line60" x1="412" x2="432" y1="88" y2="88" style="stroke:#000;stroke-width:1;stroke-linecap:round"/><a id="a4270" href="#before-tag" title="zero or one <before> element"><g id="g404"><rect id="rect62" width="59" height="22" x="348" y="77" style="fill:#fff;stroke:#000;stroke-width:1;stroke-dasharray:4,1"/><text x="377.5" y="88" style="font-family:Arial;font-size:8pt;fill:#000;font-weight:700;text-anchor:middle;dominant-baseline:central" id="text64">before</text><rect id="rect66" width="10" height="10" x="402" y="83" style="fill:#fff;stroke:#000;stroke-width:1"/><line id="line68" x1="404" x2="410" y1="88" y2="88" style="stroke:#000;stroke-width:1"/></g></a><g id="g438"><path id="path70" style="fill:#fff;stroke:#000;stroke-width:1" d="M489 129 L571 129 L577 135 L577 145 L571 151 L489 151 L483 145 L483 135 Z"/><text id="text72" style="font-family:Arial;font-size:8pt;fill:#000;font-weight:700;text-anchor:middle;dominant-baseline:central" x="530" y="140">testTypeTags</text><line id="line74" style="stroke:#000;stroke-width:2" x1="486" x2="489" y1="148" y2="145"/><path id="path76" style="fill:#000" d="M489 145 L491 147 L492 142 L487 143 L489 145 Z"/><rect id="rect78" style="fill:#fff;stroke:#000;stroke-width:1" width="10" height="10" x="572" y="135"/><line id="line80" style="stroke:#000;stroke-width:1" x1="574" x2="580" y1="140" y2="140"/><line id="line82" style="stroke:#000;stroke-width:1" x1="577" x2="577" y1="137" y2="143"/></g><line id="line84" x1="463" x2="483" y1="140" y2="140" style="stroke:#000;stroke-width:1;stroke-linecap:round"/><a id="a4318" title="that may contain any number of the following nodes in any order"><g id="g460"><path d="M432 133 L455 133 L461 139 L461 147 L455 153 L432 153 L426 147 L426 139 Z" style="fill:#fff;stroke:#000;stroke-width:1;stroke-dasharray:4,1" id="path86"/><path d="M429 130 L452 130 L458 136 L458 144 L452 150 L429 150 L423 144 L423 136 Z" style="fill:#fff;stroke:#000;stroke-width:1;stroke-dasharray:4,1" id="path88"/><line id="line90" x1="428" x2="432" y1="140" y2="140" style="stroke:#000;stroke-width:1"/><line id="line92" x1="432" x2="436" y1="140" y2="136" style="stroke:#000;stroke-width:1"/><line id="line94" x1="444" x2="448" y1="136" y2="136" style="stroke:#000;stroke-width:1"/><line id="line96" x1="444" x2="452" y1="140" y2="140" style="stroke:#000;stroke-width:1"/><line id="line98" x1="444" x2="448" y1="144" y2="144" style="stroke:#000;stroke-width:1"/><line id="line100" x1="448" x2="448" y1="136" y2="144" style="stroke:#000;stroke-width:1"/><ellipse id="ellipse102" cx="440" cy="136" rx="2" ry="2"/><ellipse id="ellipse104" cx="440" cy="140" rx="2" ry="2"/><ellipse id="ellipse106" cx="440" cy="144" rx="2" ry="2"/><text x="453" y="160" style="font-family:Arial;font-size:7.2pt;fill:#000;text-anchor:end;dominant-baseline:central" id="text108">0..∞</text><rect id="rect110" width="10" height="10" x="453" y="135" style="fill:#fff;stroke:#000;stroke-width:1"/><line id="line112" x1="455" x2="461" y1="140" y2="140" style="stroke:#000;stroke-width:1"/></g></a><line id="line114" x1="403" x2="423" y1="140" y2="140" style="stroke:#000;stroke-width:1;stroke-linecap:round"/><a id="a4277" href="#after-tag" title="zero or one <after> element"><g id="g444"><rect id="rect116" width="50" height="22" x="348" y="129" style="fill:#fff;stroke:#000;stroke-width:1;stroke-dasharray:4,1"/><text x="373" y="140" style="font-family:Arial;font-size:8pt;fill:#000;font-weight:700;text-anchor:middle;dominant-baseline:central" id="text118">after</text><rect id="rect120" width="10" height="10" x="393" y="135" style="fill:#fff;stroke:#000;stroke-width:1"/><line id="line122" x1="395" x2="401" y1="140" y2="140" style="stroke:#000;stroke-width:1"/></g></a><a id="a4335" href="#annotations-tag" title="zero or one <annotations> element"><g id="g467"><rect id="rect124" width="86" height="22" x="348" y="181" style="fill:#fff;stroke:#000;stroke-width:1;stroke-dasharray:4,1"/><text x="391" y="192" style="font-family:Arial;font-size:8pt;fill:#000;font-weight:700;text-anchor:middle;dominant-baseline:central" id="text126">annotations</text><rect id="rect128" width="10" height="10" x="429" y="187" style="fill:#fff;stroke:#000;stroke-width:1"/><line id="line130" x1="431" x2="437" y1="192" y2="192" style="stroke:#000;stroke-width:1"/><line id="line132" x1="434" x2="434" y1="189" y2="195" style="stroke:#000;stroke-width:1"/></g></a><line id="line134" x1="338" x2="348" y1="36" y2="36" style="stroke:#000;stroke-width:1;stroke-linecap:round"/><line id="line136" x1="338" x2="348" y1="88" y2="88" style="stroke:#000;stroke-width:1;stroke-linecap:round"/><line id="line138" x1="338" x2="348" y1="140" y2="140" style="stroke:#000;stroke-width:1;stroke-linecap:round"/><line id="line140" x1="338" x2="348" y1="192" y2="192" style="stroke:#000;stroke-width:1;stroke-linecap:round"/><line id="line142" x1="338" x2="338" y1="36" y2="192" style="stroke:#000;stroke-width:1;stroke-linecap:round"/><line id="line144" x1="328" x2="338" y1="114" y2="114" style="stroke:#000;stroke-width:1;stroke-linecap:round"/><a id="a4284" title="that may contain any number of the following nodes in any order"><g id="g389"><path d="M297 107 L320 107 L326 113 L326 121 L320 127 L297 127 L291 121 L291 113 Z" style="fill:#fff;stroke:#000;stroke-width:1;stroke-dasharray:4,1" id="path146"/><path d="M294 104 L317 104 L323 110 L323 118 L317 124 L294 124 L288 118 L288 110 Z" style="fill:#fff;stroke:#000;stroke-width:1;stroke-dasharray:4,1" id="path148"/><line id="line150" x1="293" x2="297" y1="114" y2="114" style="stroke:#000;stroke-width:1"/><line id="line152" x1="297" x2="301" y1="114" y2="110" style="stroke:#000;stroke-width:1"/><line id="line154" x1="309" x2="313" y1="110" y2="110" style="stroke:#000;stroke-width:1"/><line id="line156" x1="309" x2="317" y1="114" y2="114" style="stroke:#000;stroke-width:1"/><line id="line158" x1="309" x2="313" y1="118" y2="118" style="stroke:#000;stroke-width:1"/><line id="line160" x1="313" x2="313" y1="110" y2="118" style="stroke:#000;stroke-width:1"/><ellipse id="ellipse162" cx="305" cy="110" rx="2" ry="2"/><ellipse id="ellipse164" cx="305" cy="114" rx="2" ry="2"/><ellipse id="ellipse166" cx="305" cy="118" rx="2" ry="2"/><text x="318" y="134" style="font-family:Arial;font-size:7.2pt;fill:#000;text-anchor:end;dominant-baseline:central" id="text168">0..∞</text><rect id="rect170" width="10" height="10" x="318" y="109" style="fill:#fff;stroke:#000;stroke-width:1"/><line id="line172" x1="320" x2="326" y1="114" y2="114" style="stroke:#000;stroke-width:1"/></g></a><line id="line174" x1="268" x2="288" y1="114" y2="114" style="stroke:#000;stroke-width:1;stroke-linecap:round"/><a id="a4261" href="#test-tag" title="one or more <test> elements"><g id="g373"><rect id="rect176" width="46" height="22" x="220" y="106" style="fill:#fff;stroke:#000;stroke-width:1"/><rect id="rect178" width="46" height="22" x="217" y="103" style="fill:#fff;stroke:#000;stroke-width:1"/><text x="240" y="114" style="font-family:Arial;font-size:8pt;fill:#000;font-weight:700;text-anchor:middle;dominant-baseline:central" id="text180">test</text><text x="258" y="135" style="font-family:Arial;font-size:7.2pt;fill:#000;text-anchor:end;dominant-baseline:central" id="text182">1..∞</text><rect id="rect184" width="10" height="10" x="258" y="109" style="fill:#fff;stroke:#000;stroke-width:1"/><line id="line186" x1="260" x2="266" y1="114" y2="114" style="stroke:#000;stroke-width:1"/></g></a><line id="line188" x1="197" x2="217" y1="114" y2="114" style="stroke:#000;stroke-width:1;stroke-linecap:round"/><a id="a4205" title="that contains one of the following nodes"><g id="g365"><path d="M163 104 L186 104 L192 110 L192 118 L186 124 L163 124 L157 118 L157 110 Z" style="fill:#fff;stroke:#000;stroke-width:1" id="path190"/><line id="line192" x1="162" x2="166" y1="114" y2="114" style="stroke:#000;stroke-width:1"/><line id="line194" x1="166" x2="170" y1="114" y2="110" style="stroke:#000;stroke-width:1"/><line id="line196" x1="178" x2="182" y1="110" y2="110" style="stroke:#000;stroke-width:1"/><line id="line198" x1="178" x2="186" y1="114" y2="114" style="stroke:#000;stroke-width:1"/><line id="line200" x1="178" x2="182" y1="118" y2="118" style="stroke:#000;stroke-width:1"/><line id="line202" x1="182" x2="182" y1="110" y2="118" style="stroke:#000;stroke-width:1"/><ellipse id="ellipse204" cx="174" cy="110" rx="2" ry="2"/><ellipse id="ellipse206" cx="174" cy="114" rx="2" ry="2"/><ellipse id="ellipse208" cx="174" cy="118" rx="2" ry="2"/><rect id="rect210" width="10" height="10" x="187" y="109" style="fill:#fff;stroke:#000;stroke-width:1"/><line id="line212" x1="189" x2="195" y1="114" y2="114" style="stroke:#000;stroke-width:1"/></g></a><line id="line214" x1="137" x2="157" y1="114" y2="114" style="stroke:#000;stroke-width:1;stroke-linecap:round"/><a id="a4195" title="that contains a sequence of the following nodes"><g id="g351"><path d="M103 104 L126 104 L132 110 L132 118 L126 124 L103 124 L97 118 L97 110 Z" style="fill:#fff;stroke:#000;stroke-width:1" id="path216"/><line id="line218" x1="100" x2="129" y1="114" y2="114" style="stroke:#000;stroke-width:1"/><ellipse id="ellipse220" cx="109" cy="114" rx="2" ry="2"/><ellipse id="ellipse222" cx="114" cy="114" rx="2" ry="2"/><ellipse id="ellipse224" cx="119" cy="114" rx="2" ry="2"/><rect id="rect226" width="10" height="10" x="127" y="109" style="fill:#fff;stroke:#000;stroke-width:1"/><line id="line228" x1="129" x2="135" y1="114" y2="114" style="stroke:#000;stroke-width:1"/></g></a><line id="line230" x1="77" x2="97" y1="114" y2="114" style="stroke:#000;stroke-width:1;stroke-linecap:round"/><a id="a499" href="#tests-tag" title="Root element <tests>"><g id="g342"><rect id="rect232" width="52" height="22" x="20" y="103" style="fill:#fff;stroke:#000;stroke-width:1"/><text x="46" y="114" style="font-family:Arial;font-size:8pt;fill:#000;font-weight:700;text-anchor:middle;dominant-baseline:central" id="text234">tests</text><rect id="rect236" width="10" height="10" x="67" y="109" style="fill:#fff;stroke:#000;stroke-width:1"/><line id="line238" x1="69" x2="75" y1="114" y2="114" style="stroke:#000;stroke-width:1"/></g></a><a id="a4355" href="test/actions.html" target="_blank" title="one of <action> elements"><g id="g497"><path d="M205 233 L299 233 L305 239 L305 249 L299 255 L205 255 L199 249 L199 239 Z" style="fill:#fff;stroke:#000;stroke-width:1" id="path240"/><text x="252" y="244" style="font-family:Arial;font-size:8pt;fill:#000;font-weight:700;text-anchor:middle;dominant-baseline:central" id="text242">actionTypeTags</text><line id="line244" x1="202" x2="205" y1="252" y2="249" style="stroke:#000;stroke-width:2"/><path d="M205 249 L207 251 L208 246 L203 247 L205 249 Z" style="fill:#000" id="path246"/><rect id="rect248" width="10" height="10" x="300" y="239" style="fill:#fff;stroke:#000;stroke-width:1"/><line id="line250" x1="302" x2="308" y1="244" y2="244" style="stroke:#000;stroke-width:1"/><line id="line252" x1="305" x2="305" y1="241" y2="247" style="stroke:#000;stroke-width:1"/></g></a><a id="a4350" href="#argument-tag" title="one <argument> element"><g id="g471"><rect id="rect254" width="70" height="22" x="373" y="285" style="fill:#fff;stroke:#000;stroke-width:1"/><text x="408" y="296" style="font-family:Arial;font-size:8pt;fill:#000;font-weight:700;text-anchor:middle;dominant-baseline:central" id="text256">argument</text></g></a><line id="line258" x1="353" x2="373" y1="296" y2="296" style="stroke:#000;stroke-width:1;stroke-linecap:round"/><a id="a4220" title="that may contain multiple sequences of the following node"><g id="g482"><path d="M322 289 L345 289 L351 295 L351 303 L345 309 L322 309 L316 303 L316 295 Z" style="fill:#fff;stroke:#000;stroke-width:1;stroke-dasharray:4,1" id="path260"/><path d="M319 286 L342 286 L348 292 L348 300 L342 306 L319 306 L313 300 L313 292 Z" style="fill:#fff;stroke:#000;stroke-width:1;stroke-dasharray:4,1" id="path262"/><line id="line264" x1="316" x2="345" y1="296" y2="296" style="stroke:#000;stroke-width:1"/><ellipse id="ellipse266" cx="325" cy="296" rx="2" ry="2"/><ellipse id="ellipse268" cx="330" cy="296" rx="2" ry="2"/><ellipse id="ellipse270" cx="335" cy="296" rx="2" ry="2"/><text x="343" y="316" style="font-family:Arial;font-size:7.2pt;fill:#000;text-anchor:end;dominant-baseline:central" id="text272">0..∞</text><rect id="rect274" width="10" height="10" x="343" y="291" style="fill:#fff;stroke:#000;stroke-width:1"/><line id="line276" x1="345" x2="351" y1="296" y2="296" style="stroke:#000;stroke-width:1"/></g></a><line id="line278" x1="293" x2="313" y1="296" y2="296" style="stroke:#000;stroke-width:1;stroke-linecap:round"/><a id="a4343" href="#actiongroup-tag" title="zero or one <actionGroup> elements"><g id="g488"><rect id="rect280" width="89" height="22" x="199" y="285" style="fill:#fff;stroke:#000;stroke-width:1;stroke-dasharray:4,1"/><text x="243.5" y="296" style="font-family:Arial;font-size:8pt;fill:#000;font-weight:700;text-anchor:middle;dominant-baseline:central" id="text282">actionGroup</text><rect id="rect284" width="10" height="10" x="283" y="291" style="fill:#fff;stroke:#000;stroke-width:1"/><line id="line286" x1="285" x2="291" y1="296" y2="296" style="stroke:#000;stroke-width:1"/></g></a><line id="line288" x1="189" x2="199" y1="244" y2="244" style="stroke:#000;stroke-width:1;stroke-linecap:round"/><line id="line290" x1="189" x2="199" y1="296" y2="296" style="stroke:#000;stroke-width:1;stroke-linecap:round"/><line id="line292" x1="189" x2="189" y1="244" y2="296" style="stroke:#000;stroke-width:1;stroke-linecap:round"/><line id="line294" x1="179" x2="189" y1="270" y2="270" style="stroke:#000;stroke-width:1;stroke-linecap:round"/><a id="a4246" title="that contains one of the following nodes"><g id="g4244"><path d="M145 260 L168 260 L174 266 L174 274 L168 280 L145 280 L139 274 L139 266 Z" style="fill:#fff;stroke:#000;stroke-width:1" id="path296"/><line id="line298" x1="144" x2="148" y1="270" y2="270" style="stroke:#000;stroke-width:1"/><line id="line300" x1="148" x2="152" y1="270" y2="266" style="stroke:#000;stroke-width:1"/><line id="line302" x1="160" x2="164" y1="266" y2="266" style="stroke:#000;stroke-width:1"/><line id="line304" x1="160" x2="168" y1="270" y2="270" style="stroke:#000;stroke-width:1"/><line id="line306" x1="160" x2="164" y1="274" y2="274" style="stroke:#000;stroke-width:1"/><line id="line308" x1="164" x2="164" y1="266" y2="274" style="stroke:#000;stroke-width:1"/><ellipse id="ellipse310" cx="156" cy="266" rx="2" ry="2"/><ellipse id="ellipse312" cx="156" cy="270" rx="2" ry="2"/><ellipse id="ellipse314" cx="156" cy="274" rx="2" ry="2"/><rect id="rect316" width="10" height="10" x="169" y="265" style="fill:#fff;stroke:#000;stroke-width:1"/><line id="line318" x1="171" x2="177" y1="270" y2="270" style="stroke:#000;stroke-width:1"/></g></a><line id="line320" x1="119" x2="139" y1="270" y2="270" style="stroke:#000;stroke-width:1;stroke-linecap:round"/><path d="M26 259 L108 259 L114 265 L114 275 L108 281 L26 281 L20 275 L20 265 Z" style="fill:#fff;stroke:#000;stroke-width:1" id="path322"/><text x="67" y="270" style="font-family:Arial;font-size:8pt;fill:#000;font-weight:700;text-anchor:middle;dominant-baseline:central" id="text324">testTypeTags</text><rect id="rect326" width="10" height="10" x="109" y="265" style="fill:#fff;stroke:#000;stroke-width:1"/><line id="line328" x1="111" x2="117" y1="270" y2="270" style="stroke:#000;stroke-width:1"/></svg> \ No newline at end of file diff --git a/docs/img/trouble-chrome232.png b/docs/img/trouble-chrome232.png deleted file mode 100644 index 1dc44f6a4..000000000 Binary files a/docs/img/trouble-chrome232.png and /dev/null differ diff --git a/docs/interactive-pause.md b/docs/interactive-pause.md new file mode 100644 index 000000000..19a8e5681 --- /dev/null +++ b/docs/interactive-pause.md @@ -0,0 +1,80 @@ +--- +title: Interactive pause +redirect_to: https://developer.adobe.com/commerce/testing/functional-testing-framework/interactive-pause/ +status: migrated +--- + +# Interactive Pause + +It can be difficut to write a successful test on the first attempt. You will need to try different commands, with different arguments, before you find the correct path. + +Since Codeception 3.0, you can pause execution in any point and enter an interactive shell where you will be able to try commands in action. + +Now this `Interactive Pause` feature is available in MFTF. All you need to do is to set `ENABLE_PAUSE` to `true` in `.env`. + +Check [pause on codeception.com][] for documentation and a video to see `Interactive Pause` in action. + +In short, when a test gets to `$I->pause()` step, it stops and shows a console where you can try all available commands with auto-completion, stash commands, save screenshots, etc. + +## How to Configure Interactive Pause + +To be able to use Interactive console you need to install `hoa/console` library by running `composer require hoa/console` command in your project. This will allow `<pause />` action to work. +MFTF supports `Interactive Pause` when `ENABLE_PAUSE` is set to `true` in `<project_root>/dev/tests/acceptance/.env` file. + +## MFTF Run Commands + +```bash +vendor/bin/mftf run:group +``` + +```bash +vendor/bin/mftf run:test +``` + +```bash +vendor/bin/mftf run:manifest +``` + +```bash +vendor/bin/mftf run:failed +``` + +### Use `Interactive Pause` During Test Development + +Here is a typical work flow for this use case: + +- Set `ENABLE_PAUSE` to `true` under `.env` +- Add <pause> action in a test where you want to pause execution for debugging +- Run test +- Execution should pause at <pause> action and invoke interactive console +- Try out commands in interactive console +- Resume test execution by pressing `ENTER` + +### Use `Pause` On Test Failure + +When `ENABLE_PAUSE` is set to `true`, MFTF automatically generates `pause()` action in `_failed()` hook for tests and in `_failed()` function in `MagentoWebDriver`. +This allows you to use `pause` to debug test failure for a long running test. The work flow might look like: + +- Set `ENABLE_PAUSE` to `true` under `.env` +- Run test +- Execution pauses and invokes interactive console right after test fails +- Examine and debug on the spot of failure + +## MFTF Codecept Run Command + +You can also use MFTF's wrapper command to run Codeception directly and activate `Interactive Pause` by passing `--debug` option. +You do not need to set `ENABLE_PAUSE` to `true` for this command if you don't want to pause on test failure. + +```bash +vendor/bin/mftf codecept:run --debug +``` + +<div class="bs-callout-warning"> +<p> +Note: MFTF command "--debug" option has different meaning than Codeception command "--debug" mode option. +</p> +</div> + +<!-- Link definitions --> + +[pause on codeception.com]: https://codeception.com/docs/02-GettingStarted#Interactive-Pause diff --git a/docs/introduction.md b/docs/introduction.md index 61c1eaef3..c92083afe 100644 --- a/docs/introduction.md +++ b/docs/introduction.md @@ -1,9 +1,28 @@ +--- +title: Introduction to the Magento Functional Testing Framework +redirect_to: https://developer.adobe.com/commerce/testing/functional-testing-framework/ +status: migrated +--- + # Introduction to the Magento Functional Testing Framework -[Find your MFTF version][] of the MFTF. +<div class="bs-callout bs-callout-info" markdown="1"> +This documentation is for MFTF 3.0, which was release in conjunction with Magento 2.4. +MFTF 3.0 is a major update and introduces many new changes and fixes. +MFTF 2 docs can be found [here][]. +</div> -The Magento Functional Testing Framework (MFTF) aims to replace the [Functional Testing Framework] in future releases. -MFTF improves: +[Find your version] of MFTF. + +The Magento Functional Testing Framework (MFTF) is a framework used to perform automated end-to-end functional testing. + +## Goals + +- To facilitate functional testing and minimize the effort it takes to perform regression testing. +- Enable extension developers to provide functional tests for their extensions. +- Ensure a common standard of quality between Magento, extension developers and system integrators. + +MFTF also focuses on - **Traceability** for clear logging and reporting capabilities. - **Modularity** to run tests based on installed modules and extensions. @@ -11,39 +30,58 @@ MFTF improves: - **Readability** using clear and declarative XML test steps. - **Maintainability** based on simple test creation and overall structure. -Because MFTF tests are written in XML, you no longer need to learn PHP to write tests. - -<div class="bs-callout bs-callout-info" markdown="1"> -We are actively developing functional tests. -Refer to `<magento_root>/app/code/<vendor_name>/<module_name>/Test/Mftf/` for examples. -Check out the [MFTF Test Migration][] repo. -</div> - ## Audience -This MFTF guide is intended for Magento developers and software engineers, such as QA specialists, PHP developers, and system integrators. +- **Contributors**: Tests build confidence about the results of changes introduced to the platform. +- **Extension Developers**: Can adjust expected behaviour according to their customizations. +- **System Integrators**: MFTF coverage provided out-of-the-box with Magento is solid base for Acceptance / Regression Tests. -## Goals +## MFTF tests + +MFTF supports two different locations for storing the tests and test artifacts: -The purpose of MFTF is to: +- `<magento_root>/app/code/<vendor_name>/<module_name>/Test/Mftf/` is the location of local, customized tests. +- `<magento_root>/vendor/<vendor_name>/<module_name>/Test/Mftf/` is location of tests provided by Magento and vendors. -- Facilitate functional testing and minimize the effort it takes to perform regression testing. -- Make it easier to support the extension and customization of tests via XML merging. +If you installed Magento with Composer, please refer to `vendor/magento/<module_dir>/Test/Mftf/` for examples. -## Scope +### Directory Structure -MFTF will enable you to: +The file structure under both cases is the same: -- Test user interactions with web applications in testing. -- Write functional tests located in `<magento_root>/app/code/<vendor_name>/<module_name>/Test/Mftf/`. -- Cover basic functionality using out-of-the-box tests. You can test extended functionality using custom tests. -- Automate regression testing. +```tree +Test +└── Mftf + ├── ActionGroup + │   └── ... + ├── Data + │   └── ... + ├── Metadata + │   └── ... + ├── Page + │   └── ... + ├── Section + │   └── ... + └── Test + └── ... +``` ## Use cases -As a Magento developer, test changes, such as extended search functionality, a new form attribute, or new product tags. +- Contributor: changes the core behaviour, fixing the annoying bug. + He wants to have automated "supervisor" which is going to verify his work continuously across the stages of bug fixing. Finally, when fix is done - Functional Test is also proof of work done. +- Extension Developer: offers extension that changes core behaviour. + He can easily write new tests to make sure that after enabling the feature, Magento behaves properly. Everything with just extending existing tests. As a result he don't need to write coverage from scratch. +- Integration Agency: maintains Client's e-commerce. + They are able to customize tests delivered with Magento core to follow customizations implemented to Magento. After each upgrade they can just run the MFTF tests to know that no regression was introduced. -As a software engineer, perform regression testing before release to ensure that Magento works as expected with new functionality. +## MFTF output + +- Generated PHP Codeception tests +- Codeception results and console logs +- Screenshots and HTML failure report +- Allure formatted XML results +- Allure report dashboard of results ## Find your MFTF version @@ -52,11 +90,9 @@ There are two options to find out your MFTF version: - using the MFTF CLI - using the Composer CLI -### MFTF CLI +All the Command Line commands needs to be executed from `<magento_root>` -```bash -cd <magento_root>/ -``` +### MFTF CLI ```bash vendor/bin/mftf --version @@ -64,10 +100,6 @@ vendor/bin/mftf --version ### Composer CLI -```bash -cd <magento_root>/ -``` - ```bash composer show magento/magento2-functional-testing-framework ``` @@ -99,14 +131,14 @@ codeception.dist.yml // Codeception configuration (generated while ru ## MFTF tests -The MFTF supports two different locations for storing the tests and test artifacts: - +MFTF supports three different locations for storing the tests and test artifacts: - `<magento_root>/app/code/<vendor_name>/<module_name>/Test/Mftf/` is the directory to create new tests. - `<magento_root>/vendor/<vendor_name>/<module_name>/Test/Mftf/` is the directory with the out of the box tests (fetched by the Composer). +- `<magento_root>/dev/tests/acceptance/tests/functional/<vendor_name>/<module_name>/` is used to store tests that depend on multiple modules. All tests and test data from these locations are merged in the order indicated in the above list. -The file structure under the both path cases is the same: +Directories immediately following the above paths will use the same format, and sub-directories under each category are supported. ```tree <Path> @@ -120,6 +152,8 @@ The file structure under the both path cases is the same: │   └── ... ├── Section │   └── ... +├── Suite +│   └── ... └── Test └── ... ``` @@ -130,7 +164,6 @@ Follow the [MFTF project] and [contribute on Github]. <!-- Link definitions --> [contribute on Github]: https://github.com/magento/magento2-functional-testing-framework/blob/master/.github/CONTRIBUTING.md -[Functional Testing Framework]: https://devdocs.magento.com/guides/v2.3/mtf/mtf_introduction.html [MFTF project]: https://github.com/magento/magento2-functional-testing-framework -[Find your MFTF version]: #find-your-mftf-version -[MFTF Test Migration]: https://github.com/magento/magento-functional-tests-migration +[Find your version]: #find-your-mftf-version +[here]: ../v2/docs/introduction.html diff --git a/docs/merge_points/extend-action-groups.md b/docs/merge_points/extend-action-groups.md index 95d6cff90..c8fefade4 100644 --- a/docs/merge_points/extend-action-groups.md +++ b/docs/merge_points/extend-action-groups.md @@ -1,3 +1,9 @@ +--- +title: Extend action groups +redirect_to: https://developer.adobe.com/commerce/testing/functional-testing-framework/merge-points/extend-action-groups/ +status: migrated +--- + # Extend action groups Extending an action group doesn't affect the existing action group. @@ -9,7 +15,7 @@ In this example we add a `<click>` command to check the checkbox that our extens <!-- {% raw %} --> ```xml -<actionGroup name="FillAdminSimpleProductForm"> +<actionGroup name="AdminFillSimpleProductFormActionGroup"> <arguments> <argument name="category"/> <argument name="simpleProduct"/> @@ -34,10 +40,10 @@ In this example we add a `<click>` command to check the checkbox that our extens </actionGroup> ``` -## File to merge +## Extend file ```xml -<actionGroup name="FillAdminSimpleProductFormWithMyExtension" extends="FillAdminSimpleProductForm"> +<actionGroup name="AdminFillSimpleProductFormWithMyExtensionActionGroup" extends="AdminFillSimpleProductFormActionGroup"> <!-- This will be added after the step "fillQuantity" on line 12 in the above test. --> <click selector="{{MyExtensionSection.myCheckbox}}" stepKey="clickMyCheckbox" after="fillQuantity"/> </actionGroup> @@ -48,7 +54,7 @@ In this example we add a `<click>` command to check the checkbox that our extens Note that there are now two action groups below. ```xml -<actionGroup name="FillAdminSimpleProductForm"> +<actionGroup name="AdminFillSimpleProductFormActionGroup"> <arguments> <argument name="category"/> <argument name="simpleProduct"/> @@ -71,7 +77,7 @@ Note that there are now two action groups below. <click selector="{{AdminProductSEOSection.sectionHeader}}" stepKey="openSeoSectionAssert"/> <seeInField userInput="{{simpleProduct.urlKey}}" selector="{{AdminProductSEOSection.urlKeyInput}}" stepKey="assertFieldUrlKey"/> </actionGroup> -<actionGroup name="FillAdminSimpleProductFormWithMyExtension"> +<actionGroup name="AdminFillSimpleProductFormWithMyExtensionActionGroup"> <arguments> <argument name="category"/> <argument name="simpleProduct"/> diff --git a/docs/merge_points/extend-data.md b/docs/merge_points/extend-data.md index 344d11935..d2150fc61 100644 --- a/docs/merge_points/extend-data.md +++ b/docs/merge_points/extend-data.md @@ -1,8 +1,14 @@ +--- +title: Extend data entities +redirect_to: https://developer.adobe.com/commerce/testing/functional-testing-framework/merge-points/extend-data/ +status: migrated +--- + # Extend data entities -Extending an action group doesn't affect the existing action group. +Extending a data entity does not affect the existing data entity. -In this example we add a `<click>` command to check the checkbox that our extension added with a new action group for the simple product creation form. +In this example we update the quantity to 1001 and add a new piece of data relevant to our extension. Unlike merging, this will _not_ affect any other tests that use this data entity. ## Starting entity @@ -23,7 +29,7 @@ In this example we add a `<click>` command to check the checkbox that our extens </entity> ``` -## File to merge +## Extend file ```xml <entity name="ExtensionProduct" type="product" extends="SimpleProduct"> @@ -67,4 +73,4 @@ Note that there are now two data entities below. <requiredEntity type="custom_attribute_array">CustomAttributeCategoryIds</requiredEntity> <data key="myExtensionData">dataHere</data> </entity> -``` \ No newline at end of file +``` diff --git a/docs/merge_points/extend-tests.md b/docs/merge_points/extend-tests.md index 7398e273d..90e2c8b95 100644 --- a/docs/merge_points/extend-tests.md +++ b/docs/merge_points/extend-tests.md @@ -1,6 +1,12 @@ +--- +title: Extend tests +redirect_to: https://developer.adobe.com/commerce/testing/functional-testing-framework/merge-points/extend-tests/ +status: migrated +--- + # Extend tests -Data objects can be merged to cover the needs of your extension. +Tests can be extended to cover the needs of your extension. In this example, we add an action group to a new copy of the original test for our extension. @@ -25,8 +31,8 @@ In this example, we add an action group to a new copy of the original test for o <deleteData createDataKey="createPreReqCategory" stepKey="deletePreReqCategory"/> </after> - <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin1"/> - <actionGroup ref="FillAdminSimpleProductForm" stepKey="fillProductFieldsInAdmin"> + <actionGroup ref="AdminLoginActionGroup" stepKey="AdminLoginActionGroup1"/> + <actionGroup ref="AdminFillSimpleProductFormActionGroup" stepKey="fillProductFieldsInAdmin"> <argument name="category" value="$$createPreReqCategory$$"/> <argument name="simpleProduct" value="_defaultProduct"/> </actionGroup> @@ -40,7 +46,7 @@ In this example, we add an action group to a new copy of the original test for o </test> ``` -## File to merge +## Extend file ```xml <test name="AdminCreateSimpleProductExtensionTest" extends="AdminCreateSimpleProductTest"> @@ -89,8 +95,8 @@ Note that there are now two tests below. <deleteData createDataKey="createPreReqCategory" stepKey="deletePreReqCategory"/> </after> - <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin1"/> - <actionGroup ref="FillAdminSimpleProductForm" stepKey="fillProductFieldsInAdmin"> + <actionGroup ref="AdminLoginActionGroup" stepKey="AdminLoginActionGroup1"/> + <actionGroup ref="AdminFillSimpleProductFormActionGroup" stepKey="fillProductFieldsInAdmin"> <argument name="category" value="$$createPreReqCategory$$"/> <argument name="simpleProduct" value="_defaultProduct"/> </actionGroup> @@ -120,8 +126,8 @@ Note that there are now two tests below. <deleteData createDataKey="createPreReqCategory" stepKey="deletePreReqCategory"/> </after> - <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin1"/> - <actionGroup ref="FillAdminSimpleProductForm" stepKey="fillProductFieldsInAdmin"> + <actionGroup ref="AdminLoginActionGroup" stepKey="AdminLoginActionGroup1"/> + <actionGroup ref="AdminFillSimpleProductFormActionGroup" stepKey="fillProductFieldsInAdmin"> <argument name="category" value="$$createPreReqCategory$$"/> <argument name="simpleProduct" value="_defaultProduct"/> </actionGroup> @@ -142,4 +148,4 @@ Note that there are now two tests below. <argument name="extensionData" value="_myData"/> </actionGroup> </test> -``` \ No newline at end of file +``` diff --git a/docs/merge_points/introduction.md b/docs/merge_points/introduction.md index af03dade6..d0c75f784 100644 --- a/docs/merge_points/introduction.md +++ b/docs/merge_points/introduction.md @@ -1,3 +1,9 @@ +--- +title: Merge points for testing extensions in MFTF +redirect_to: https://developer.adobe.com/commerce/testing/functional-testing-framework/merge-points/ +status: migrated +--- + # Merge Points for testing extensions in MFTF The Magento Functional Testing Framework (MFTF) allows great flexibility when writing XML tests for extensions. diff --git a/docs/merge_points/merge-action-groups.md b/docs/merge_points/merge-action-groups.md index 3a4f70ab1..b4e2c4273 100644 --- a/docs/merge_points/merge-action-groups.md +++ b/docs/merge_points/merge-action-groups.md @@ -1,7 +1,13 @@ +--- +title: Merge action groups +redirect_to: https://developer.adobe.com/commerce/testing/functional-testing-framework/merge-points/merge-action-groups/ +status: migrated +--- + # Merge action groups An action group is a set of individual actions working together as a group. -These action groups can be shared between tests and they also be modified to your needs. +These action groups can be shared between tests and they also can be modified to your needs. In this example we add a `<click>` command to check the checkbox that our extension adds to the simple product creation form. @@ -10,7 +16,7 @@ In this example we add a `<click>` command to check the checkbox that our extens <!-- {% raw %} --> ```xml -<actionGroup name="FillAdminSimpleProductForm"> +<actionGroup name="AdminFillSimpleProductFormActionGroup"> <arguments> <argument name="category"/> <argument name="simpleProduct"/> @@ -38,7 +44,7 @@ In this example we add a `<click>` command to check the checkbox that our extens ## File to merge ```xml -<actionGroup name="FillAdminSimpleProductForm"> +<actionGroup name="AdminFillSimpleProductFormActionGroup"> <!-- This will be added after the step "fillQuantity" in the above test. --> <click selector="{{MyExtensionSection.myCheckbox}}" stepKey="clickMyCheckbox" after="fillQuantity"/> </actionGroup> @@ -47,7 +53,7 @@ In this example we add a `<click>` command to check the checkbox that our extens ## Resultant test ```xml -<actionGroup name="FillAdminSimpleProductForm"> +<actionGroup name="AdminFillSimpleProductFormActionGroup"> <arguments> <argument name="category"/> <argument name="simpleProduct"/> diff --git a/docs/merge_points/merge-data.md b/docs/merge_points/merge-data.md index b3342cc46..9bb64c692 100644 --- a/docs/merge_points/merge-data.md +++ b/docs/merge_points/merge-data.md @@ -1,3 +1,9 @@ +--- +title: Merge data +redirect_to: https://developer.adobe.com/commerce/testing/functional-testing-framework/merge-points/merge-data/ +status: migrated +--- + # Merge data Data objects can be merged to cover the needs of your extension. diff --git a/docs/merge_points/merge-pages.md b/docs/merge_points/merge-pages.md index c7a3e8fa8..2c4248744 100644 --- a/docs/merge_points/merge-pages.md +++ b/docs/merge_points/merge-pages.md @@ -1,3 +1,9 @@ +--- +title: Merge pages +redirect_to: https://developer.adobe.com/commerce/testing/functional-testing-framework/merge-points/merge-pages/ +status: migrated +--- + # Merge pages Sections can be merged into pages to cover your extension. diff --git a/docs/merge_points/merge-sections.md b/docs/merge_points/merge-sections.md index dbd6e3828..3061eb265 100644 --- a/docs/merge_points/merge-sections.md +++ b/docs/merge_points/merge-sections.md @@ -1,3 +1,9 @@ +--- +title: Merge sections +redirect_to: https://developer.adobe.com/commerce/testing/functional-testing-framework/merge-points/merge-sections/ +status: migrated +--- + # Merge sections Sections can be merged together to cover your extension. @@ -9,7 +15,7 @@ In this example we add another selector to the section on the products page sect <!-- {% raw %} --> ```xml -<section name="ProductsPageSection"> +<section name="AdminProductsPageSection"> <element name="addProductButton" type="button" selector="//button[@id='add_new_product-button']"/> <element name="checkboxForProduct" type="button" selector="//*[contains(text(),'{{args}}')]/parent::td/preceding-sibling::td/label[@class='data-grid-checkbox-cell-inner']" parameterized="true"/> <element name="actions" type="button" selector="//div[@class='col-xs-2']/div[@class='action-select-wrap']/button[@class='action-select']"/> @@ -22,7 +28,7 @@ In this example we add another selector to the section on the products page sect ## File to merge ```xml -<section name="ProductsPageSection"> +<section name="AdminProductsPageSection"> <!-- myExtensionElement will simply be added to the page --> <element name="myExtensionElement" type="button" selector="input.myExtension"/> </section> @@ -31,7 +37,7 @@ In this example we add another selector to the section on the products page sect ## Resultant section ```xml -<section name="ProductsPageSection"> +<section name="AdminProductsPageSection"> <element name="addProductButton" type="button" selector="//button[@id='add_new_product-button']"/> <element name="checkboxForProduct" type="button" selector="//*[contains(text(),'{{args}}')]/parent::td/preceding-sibling::td/label[@class='data-grid-checkbox-cell-inner']" parameterized="true"/> <element name="actions" type="button" selector="//div[@class='col-xs-2']/div[@class='action-select-wrap']/button[@class='action-select']"/> @@ -41,7 +47,6 @@ In this example we add another selector to the section on the products page sect <!-- New element merged --> <element name="myExtensionElement" type="button" selector="input.myExtension"/> </section> -</page> ``` <!-- {% endraw %} --> \ No newline at end of file diff --git a/docs/merge_points/merge-tests.md b/docs/merge_points/merge-tests.md index 3958410e9..6051d6808 100644 --- a/docs/merge_points/merge-tests.md +++ b/docs/merge_points/merge-tests.md @@ -1,3 +1,9 @@ +--- +title: Merge tests +redirect_to: https://developer.adobe.com/commerce/testing/functional-testing-framework/merge-points/merge-tests/ +status: migrated +--- + # Merge tests Tests can be merged to create a new test that covers new extension capabilities. @@ -25,8 +31,8 @@ In this example we add an action group that modifies the original test to intera <deleteData createDataKey="createPreReqCategory" stepKey="deletePreReqCategory"/> </after> - <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin1"/> - <actionGroup ref="FillAdminSimpleProductForm" stepKey="fillProductFieldsInAdmin"> + <actionGroup ref="AdminLoginActionGroup" stepKey="adminLoginActionGroup1"/> + <actionGroup ref="AdminFillSimpleProductFormActionGroup" stepKey="fillProductFieldsInAdmin"> <argument name="category" value="$$createPreReqCategory$$"/> <argument name="simpleProduct" value="_defaultProduct"/> </actionGroup> @@ -77,8 +83,8 @@ In this example we add an action group that modifies the original test to intera <deleteData createDataKey="createPreReqCategory" stepKey="deletePreReqCategory"/> </after> - <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin1"/> - <actionGroup ref="FillAdminSimpleProductForm" stepKey="fillProductFieldsInAdmin"> + <actionGroup ref="AdminLoginActionGroup" stepKey="AdminLoginActionGroup1"/> + <actionGroup ref="AdminFillSimpleProductFormActionGroup" stepKey="fillProductFieldsInAdmin"> <argument name="category" value="$$createPreReqCategory$$"/> <argument name="simpleProduct" value="_defaultProduct"/> </actionGroup> diff --git a/docs/merging.md b/docs/merging.md index 99df18464..01d6b6163 100644 --- a/docs/merging.md +++ b/docs/merging.md @@ -1,12 +1,18 @@ +--- +title: Merging +redirect_to: https://developer.adobe.com/commerce/testing/functional-testing-framework/merging/ +status: migrated +--- + # Merging -The MFTF allows you to merge test components defined in XML files, such as: +MFTF allows you to merge test components defined in XML files, such as: - [`<tests>`][] - [`<pages>`][] - [`<sections>`][] - [`<data>`][] -- `<action groups>` +- [`<action groups>`][] You can create, delete, or update the component. It is useful for supporting rapid test creation for extensions and customizations. @@ -20,59 +26,65 @@ For example: - All tests with `<test name="SampleTest>` will be merged into one. - All pages with `<page name="SamplePage>` will be merged into one. -- All sections with `<section name="SampleAction">` will be merged into one. +- All sections with `<section name="SampleSection">` will be merged into one. - All data entities with `<entity name="sampleData" type="sample">` will be merged into one. -- All action groups with `<actionGroup name="selectNotLoggedInCustomerGroup">` will be merged into one. +- All action groups with `<actionGroup name="SelectNotLoggedInCustomerGroupActionGroup">` will be merged into one. Although a file name does not influence merging, we recommend using the same file names in merging updates. This makes it easier to search later on. +## Merging precedence + +**Magento Functional Testing Framework** uses Module's `<sequence>` to merge all XML configurations into Codeception instructions. If there's no Sequence specified, MFTF would use: + +1. Vendor modules (Magento & Vendors) located in `vendor/` +1. Tests located in `app/code/*/*/Test/Mftf` + +![Usual precedence for merging MFTF Tests and resources][mftfExtendingPrecedence image] + ## Add a test You cannot add another [`<test>`][tests] using merging functionality. -To add a `<test>`, create a new `*Test.xml` file or add a `<test>` node to an existing `*Test.xml` file. +To add a `<test>`, create a new `*Test.xml` file. ## Remove a test Tests cannot be removed while merging. -If a [`<test>`][tests] must be skipped due to a module completely invalidating a function, you can add the test to the `skip` group. +If a [`<test>`][tests] must be skipped due to a module completely invalidating a function, you can use `skip` annotation to skip the test. Learn more about running tests with different options using [`mftf`] or [`codecept`] commands. ### Example -Skip the `AdminLoginTest` test in the `.../Backend/Test/AdminLoginTest.xml` file while merging with the `.../Foo/Test/AdminLoginTest.xml` file: +Skip the `AdminLoginSuccessfulTest` test in the `.../Backend/Test/AdminLoginSuccessfulTest.xml` file while merging with the `.../Foo/Test/AdminLoginSuccessfulTest.xml` file: <!-- {% raw %} --> ```xml <tests ...> - <test name="AdminLoginTest"> + <test name="AdminLoginSuccessfulTest"> <annotations> - <features value="Admin Login"/> + <features value="Backend"/> <stories value="Login on the Admin Login page"/> - <title value="You should be able to log into the Magento Admin backend."/> - <description value="You should be able to log into the Magento Admin backend."/> + <title value="Admin should be able to log into the Magento Admin backend successfully"/> + <description value="Admin should be able to log into the Magento Admin backend successfully"/> <severity value="CRITICAL"/> <testCaseId value="MAGETWO-71572"/> <group value="example"/> <group value="login"/> </annotations> - <amOnPage url="{{AdminLoginPage.url}}" stepKey="amOnAdminLoginPage"/> - <fillField selector="{{AdminLoginFormSection.username}}" userInput="{{_ENV.MAGENTO_ADMIN_USERNAME}}" stepKey="fillUsername"/> - <fillField selector="{{AdminLoginFormSection.password}}" userInput="{{_ENV.MAGENTO_ADMIN_PASSWORD}}" stepKey="fillPassword"/> - <click selector="{{AdminLoginFormSection.signIn}}" stepKey="clickOnSignIn"/> - <closeAdminNotification stepKey="closeAdminNotification"/> - <seeInCurrentUrl url="{{AdminLoginPage.url}}" stepKey="seeAdminLoginUrl"/> + <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> + <actionGroup ref="AssertAdminSuccessLoginActionGroup" stepKey="assertLoggedIn"/> + <actionGroup ref="AdminLogoutActionGroup" stepKey="logoutFromAdmin"/> </test> </tests> ``` -Create the `.../Foo/Test/AdminLoginTest.xml` file: +Create the `.../Foo/Test/AdminLoginSuccessfulTest.xml` file: ```xml <tests ...> - <test name="AdminLoginTest"> + <test name="AdminLoginSuccessfulTest"> <annotations> <skip> <issueId value="Issue#"/> @@ -82,29 +94,26 @@ Create the `.../Foo/Test/AdminLoginTest.xml` file: </tests> ``` -The `AdminLoginTest` result corresponds to: +The `AdminLoginSuccessfulTest` result corresponds to: ```xml -<test name="AdminLoginTest"> +<test name="AdminLoginSuccessfulTest"> <annotations> - <features value="Admin Login"/> + <features value="Backend"/> <stories value="Login on the Admin Login page"/> - <title value="You should be able to log into the Magento Admin backend."/> - <description value="You should be able to log into the Magento Admin backend."/> + <title value="Admin should be able to log into the Magento Admin backend successfully"/> + <description value="Admin should be able to log into the Magento Admin backend successfully"/> <severity value="CRITICAL"/> <testCaseId value="MAGETWO-71572"/> <group value="example"/> <group value="login"/> - <skip> - <issueId value="Issue#"/> - </skip> + <skip> + <issueId value="Issue#"/> + </skip> </annotations> - <amOnPage url="{{AdminLoginPage.url}}" stepKey="amOnAdminLoginPage"/> - <fillField selector="{{AdminLoginFormSection.username}}" userInput="{{_ENV.MAGENTO_ADMIN_USERNAME}}" stepKey="fillUsername"/> - <fillField selector="{{AdminLoginFormSection.password}}" userInput="{{_ENV.MAGENTO_ADMIN_PASSWORD}}" stepKey="fillPassword"/> - <click selector="{{AdminLoginFormSection.signIn}}" stepKey="clickOnSignIn"/> - <closeAdminNotification stepKey="closeAdminNotification"/> - <seeInCurrentUrl url="{{AdminLoginPage.url}}" stepKey="seeAdminLoginUrl"/> + <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> + <actionGroup ref="AssertAdminSuccessLoginActionGroup" stepKey="assertLoggedIn"/> + <actionGroup ref="AdminLogoutActionGroup" stepKey="logoutFromAdmin"/> </test> ``` @@ -117,159 +126,152 @@ See the previous examples. ### Add a test step -**Use case**: Add `checkOption` before `click` (`stepKey="clickLogin"`) and add `seeInCurrentUrl` after the `click` in the `LogInAsAdminTest` test (in the `.../Backend/Test/LogInAsAdminTest.xml` file) while merging with the `.../Foo/Test/LogInAsAdminTest.xml` file: +**Use case**: Add `AdminCheckOptionSalesActionGroup` before `AssertAdminSuccessLoginActionGroup` (`stepKey="assertLoggedIn"`) and add `AdminAssertSalesOnDashboardActionGroup` after the `AssertAdminSuccessLoginActionGroup` in the `AdminLoginSuccessfulTest` test (in the `.../Backend/Test/AdminLoginSuccessfulTest.xml` file) while merging with the `.../Foo/Test/AdminLoginSuccessfulTest.xml` file: ```xml <tests ...> - <test name="LogInAsAdminTest"> - <amOnPage url="{{AdminLoginPage}}" stepKey="navigateToAdmin"/> - <fillField selector="{{AdminLoginFormSection.username}}" userInput="admin" stepKey="fillUsername"/> - <fillField selector="{{AdminLoginFormSection.password}}" userInput="password" stepKey="fillPassword"/> - <click selector="{{AdminLoginFormSection.signIn}}" stepKey="clickLogin"/> - <see userInput="Lifetime Sales" stepKey="seeLifetimeSales"/> + <test name="AdminLoginSuccessfulTest"> + <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> + <actionGroup ref="AssertAdminSuccessLoginActionGroup" stepKey="assertLoggedIn"/> + <actionGroup ref="AdminLogoutActionGroup" stepKey="logoutFromAdmin"/> </test> </tests> ``` -Create the `.../Foo/Test/LogInAsAdminTest.xml` file: +Create the `.../Foo/Test/AdminLoginSuccessfulTest.xml` file: ```xml <tests ...> - <test name="LogInAsAdminTest"> - <checkOption selector="{{AdminLoginFormSection.rememberMe}}" stepKey="checkRememberMe" before="clickLogin"/> - <seeInCurrentUrl url="admin/admin/dashboard/" stepKey="seeAdminUrl" after="clickLogin"/> + <test name="AdminLoginSuccessfulTest"> + <actionGroup ref="AdminCheckOptionSalesActionGroup" stepKey="checkOptionSales" before="assertLoggedIn"/> + <actionGroup ref="AdminAssertSalesOnDashboardActionGroup" stepKey="assertSalesOnDashboard" after="assertLoggedIn"/> </test> </tests> ``` -The `LogInAsAdminTest` result corresponds to: +The `AdminLoginSuccessfulTest` result corresponds to: ```xml -<test name="LogInAsAdminTest"> - <amOnPage url="{{AdminLoginPage}}" stepKey="navigateToAdmin"/> - <fillField selector="{{AdminLoginFormSection.username}}" userInput="admin" stepKey="fillUsername"/> - <fillField selector="{{AdminLoginFormSection.password}}" userInput="password" stepKey="fillPassword"/> - <checkOption selector="{{AdminLoginFormSection.rememberMe}}" stepKey="checkRememberMe"/> - <click selector="{{AdminLoginFormSection.signIn}}" stepKey="clickLogin"/> - <seeInCurrentUrl url="admin/admin/dashboard/" stepKey="seeAdminUrl"/> - <see userInput="Lifetime Sales" stepKey="seeLifetimeSales"/> +<test name="AdminLoginSuccessfulTest"> + <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> + <actionGroup ref="AdminCheckOptionSalesActionGroup" stepKey="checkOptionSales" before="assertLoggedIn"/> + <actionGroup ref="AssertAdminSuccessLoginActionGroup" stepKey="assertLoggedIn"/> + <actionGroup ref="AdminAssertSalesOnDashboardActionGroup" stepKey="assertSalesOnDashboard" after="assertLoggedIn"/> + <actionGroup ref="AdminLogoutActionGroup" stepKey="logoutFromAdmin"/> </test> ``` ### Remove a test step -**Use case**: Remove `see` (`stepKey="seeLifetimeSales"`) from the `LogInAsAdminTest` test (in the `.../Backend/Test/LogInAsAdminTest.xml` file) while merging with the `.../Foo/Test/LogInAsAdminTest.xml` file: +**Use case**: Remove `AdminCheckOptionSalesActionGroup` (`stepKey="checkOptionSales"`) from the `LogInAsAdminTest` test (in the `.../Backend/Test/AdminLoginSuccessfulTest.xml` file) while merging with the `.../Foo/Test/AdminLoginSuccessfulTest.xml` file: ```xml <tests ...> - <test name="LogInAsAdminTest"> - <amOnPage url="{{AdminLoginPage}}" stepKey="navigateToAdmin"/> - <fillField selector="{{AdminLoginFormSection.username}}" userInput="admin" stepKey="fillUsername"/> - <fillField selector="{{AdminLoginFormSection.password}}" userInput="password" stepKey="fillPassword"/> - <click selector="{{AdminLoginFormSection.signIn}}" stepKey="clickLogin"/> - <see userInput="Lifetime Sales" stepKey="seeLifetimeSales"/> + <test name="AdminLoginSuccessfulTest"> + <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> + <actionGroup ref="AdminCheckOptionSalesActionGroup" stepKey="checkOptionSales"/> + <actionGroup ref="AssertAdminSuccessLoginActionGroup" stepKey="assertLoggedIn"/> + <actionGroup ref="AdminLogoutActionGroup" stepKey="logoutFromAdmin"/> </test> </tests> ``` -Create the `.../Foo/Test/LogInAsAdminTest.xml` file: +Create the `.../Foo/Test/AdminLoginSuccessfulTest.xml` file: ```xml <tests ...> - <test name="LogInAsAdminTest"> - <remove keyForRemoval="seeLifetimeSales"/> + <test name="AdminLoginSuccessfulTest"> + <remove keyForRemoval="checkOptionSales"/> </test> </tests> ``` -The `LogInAsAdminTest` result corresponds to: +The `AdminLoginSuccessfulTest` result corresponds to: ```xml -<test name="LogInAsAdminTest"> - <amOnPage url="{{AdminLoginPage}}" stepKey="navigateToAdmin"/> - <fillField selector="{{AdminLoginFormSection.username}}" userInput="admin" stepKey="fillUsername"/> - <fillField selector="{{AdminLoginFormSection.password}}" userInput="password" stepKey="fillPassword"/> - <click selector="{{AdminLoginFormSection.signIn}}" stepKey="clickLogin"/> +<test name="AdminLoginSuccessfulTest"> + <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> + <actionGroup ref="AssertAdminSuccessLoginActionGroup" stepKey="assertLoggedIn"/> + <actionGroup ref="AdminLogoutActionGroup" stepKey="logoutFromAdmin"/> </test> ``` ### Update a test step -**Use case**: Change selector in `fillField` (`stepKey="fillPassword"`) of the `LogInAsAdminTest` test (in the `.../Backend/Test/LogInAsAdminTest.xml` file) while merging with the `.../Foo/Test/LogInAsAdminTest.xml` file: +**Use case**: Change argument in `AdminCheckOptionSalesActionGroup` (`stepKey="checkOptionSales"`) of the `AdminLoginSuccessfulTest` test (in the `.../Backend/Test/AdminLoginSuccessfulTest.xml` file) while merging with the `.../Foo/Test/AdminLoginSuccessfulTest.xml` file: ```xml <tests ...> - <test name="LogInAsAdminTest"> - <amOnPage url="{{AdminLoginPage}}" stepKey="navigateToAdmin"/> - <fillField selector="{{AdminLoginFormSection.username}}" userInput="admin" stepKey="fillUsername"/> - <fillField selector="{{AdminLoginFormSection.password}}" userInput="password" stepKey="fillPassword"/> - <click selector="{{AdminLoginFormSection.signIn}}" stepKey="clickLogin"/> - <see userInput="Lifetime Sales" stepKey="seeLifetimeSales"/> + <test name="AdminLoginSuccessfulTest"> + <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> + <actionGroup ref="AdminCheckOptionSalesActionGroup" stepKey="checkOptionSales"> + <argument name="salesOption" value="{{AdminSalesData.lifeTimeSales}}"/> + </actionGroup> + <actionGroup ref="AssertAdminSuccessLoginActionGroup" stepKey="assertLoggedIn"/> + <actionGroup ref="AdminLogoutActionGroup" stepKey="logoutFromAdmin"/> </test> </tests> ``` -Create the `.../Foo/Test/LogInAsAdminTest.xml` file: +Create the `.../Foo/Test/AdminLoginSuccessfulTest.xml` file: ```xml -<tests ...> - <test name="LogInAsAdminTest"> - <fillField selector="{{AdminLoginFormSection.wrong-password}}" userInput="password" stepKey="fillPassword"/> - </test> -</tests> +<test name="AdminLoginSuccessfulTest"> + <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> + <actionGroup ref="AdminCheckOptionSalesActionGroup" stepKey="checkOptionSales"> + <argument name="salesOption" value="{{AdminSalesData.annualSales}}"/> + </actionGroup> + <actionGroup ref="AssertAdminSuccessLoginActionGroup" stepKey="assertLoggedIn"/> + <actionGroup ref="AdminLogoutActionGroup" stepKey="logoutFromAdmin"/> +</test> ``` -The `LogInAsAdminTest` result corresponds to: +The `AdminLoginSuccessfulTest` result corresponds to: ```xml -<test name="LogInAsAdminTest"> - <amOnPage url="{{AdminLoginPage}}" stepKey="navigateToAdmin"/> - <fillField selector="{{AdminLoginFormSection.username}}" userInput="admin" stepKey="fillUsername"/> - <fillField selector="{{AdminLoginFormSection.wrong-password}}" userInput="password" stepKey="fillPassword"/> - <click selector="{{AdminLoginFormSection.signIn}}" stepKey="clickLogin"/> - <see userInput="Lifetime Sales" stepKey="seeLifetimeSales"/> +<test name="AdminLoginSuccessfulTest"> + <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> + <actionGroup ref="AdminCheckOptionSalesActionGroup" stepKey="checkOptionSales"> + <argument name="salesOption" value="{{AdminSalesData.annualSales}}"/> + </actionGroup> + <actionGroup ref="AssertAdminSuccessLoginActionGroup" stepKey="assertLoggedIn"/> + <actionGroup ref="AdminLogoutActionGroup" stepKey="logoutFromAdmin"/> </test> ``` ### Add several test steps {#insert-after} -**Use case**: Add several actions after the `click` (`stepKey="clickLogin"`) in the `LogInAsAdminTest` test (in the `.../Backend/Test/LogInAsAdminTest.xml` file) while merging with the `.../Foo/Test/LogInAsAdminTest.xml` file: +**Use case**: Add several action groups after the `AdminLoginActionGroup` (`stepKey="loginAsAdmin"`) in the `AdminLoginSuccessfulTest` test (in the `.../Backend/Test/AdminLoginSuccessfulTest.xml` file) while merging with the `.../Foo/Test/LogInAsAdminTest.xml` file: ```xml -<tests ...> - <test name="LogInAsAdminTest"> - <amOnPage url="{{AdminLoginPage}}" stepKey="navigateToAdmin"/> - <fillField selector="{{AdminLoginFormSection.username}}" userInput="admin" stepKey="fillUsername"/> - <fillField selector="{{AdminLoginFormSection.password}}" userInput="password" stepKey="fillPassword"/> - <click selector="{{AdminLoginFormSection.signIn}}" stepKey="clickLogin"/> - <see userInput="Lifetime Sales" stepKey="seeLifetimeSales"/> - </test> -</tests> +<test name="AdminLoginSuccessfulTest"> + <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> +</test> ``` -Create the `.../Foo/Test/LogInAsAdminTest.xml` file: +Create the `.../Foo/Test/AdminLoginSuccessfulTest.xml` file: ```xml <tests ...> - <test name="LogInAsAdminTest" insertAfter="clickLogin"> - <checkOption selector="{{AdminLoginFormSection.rememberMe}}" stepKey="checkRememberMe"/> - <seeInCurrentUrl url="admin/admin/dashboard/" stepKey="seeAdminUrl"/> + <test name="AdminLoginSuccessfulTest" insertAfter="loginAsAdmin"> + <actionGroup ref="AdminCheckOptionSalesActionGroup" stepKey="checkOptionSales"/> + <actionGroup ref="AssertAdminSuccessLoginActionGroup" stepKey="assertLoggedIn"/> + <actionGroup ref="AdminLogoutActionGroup" stepKey="logoutFromAdmin"/> </test> </tests> ``` -The `LogInAsAdminTest` result corresponds to: +The `AdminLoginSuccessfulTest` result corresponds to: ```xml -<test name="LogInAsAdminTest"> - <amOnPage url="{{AdminLoginPage}}" stepKey="navigateToAdmin"/> - <fillField selector="{{AdminLoginFormSection.username}}" userInput="admin" stepKey="fillUsername"/> - <fillField selector="{{AdminLoginFormSection.password}}" userInput="password" stepKey="fillPassword"/> - <click selector="{{AdminLoginFormSection.signIn}}" stepKey="clickLogin"/> - <checkOption selector="{{AdminLoginFormSection.rememberMe}}" stepKey="checkRememberMe"/> - <seeInCurrentUrl url="admin/admin/dashboard/" stepKey="seeAdminUrl"/> - <see userInput="Lifetime Sales" stepKey="seeLifetimeSales"/> -</test> +<tests ...> + <test name="AdminLoginSuccessfulTest"> + <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> + <actionGroup ref="AdminCheckOptionSalesActionGroup" stepKey="checkOptionSales"/> + <actionGroup ref="AssertAdminSuccessLoginActionGroup" stepKey="assertLoggedIn"/> + <actionGroup ref="AdminLogoutActionGroup" stepKey="logoutFromAdmin"/> + </test> +</tests> ``` ## Merge action groups @@ -284,7 +286,7 @@ The controls change drastically in the B2B version, so it was abstracted to an a > Action group for selecting `customerGroup` in the `Cart Price Rules` section: ```xml -<actionGroup name="selectNotLoggedInCustomerGroup"> +<actionGroup name="SelectNotLoggedInCustomerGroupActionGroup"> <selectOption selector="{{AdminCartPriceRulesFormSection.customerGroups}}" userInput="NOT LOGGED IN" stepKey="selectCustomerGroup"/> </actionGroup> ``` @@ -293,7 +295,7 @@ The controls change drastically in the B2B version, so it was abstracted to an a ```xml <!-- name matches --> -<actionGroup name="selectNotLoggedInCustomerGroup"> +<actionGroup name="SelectNotLoggedInCustomerGroupActionGroup"> <!-- removes the original action --> <remove keyForRemoval="selectCustomerGroup"/> <!-- adds in sequence of actions to be performed instead--> @@ -314,62 +316,62 @@ To merge [pages][page], the `page name` must be the same as in the base module. ### Add a section **Use case**: The `FooBackend` module extends the `Backend` module and requires use of two new sections that must be covered with tests. -Add `BaseBackendSection` and `AnotherBackendSection` to the `BaseBackendPage` (`.../Backend/Page/BaseBackendPage.xml` file): +Add `AdminBaseBackendSection` and `AdminAnotherBackendSection` to the `AdminBaseBackendPage` (`.../Backend/Page/AdminBaseBackendPage.xml` file): ```xml <pages ...> - <page name="BaseBackendPage" url="admin" area="admin" module="Magento_Backend"> - <section name="BaseBackendSection"/> - <section name="AnotherBackendSection"/> + <page name="AdminBaseBackendPage" url="admin" area="admin" module="Magento_Backend"> + <section name="AdminBaseBackendSection"/> + <section name="AdminAnotherBackendSection"/> </page> </pages> ``` -Create the `.../FooBackend/Page/BaseBackendPage.xml` file: +Create the `.../FooBackend/Page/AdminBaseBackendPage.xml` file: ```xml <pages ...> - <page name="BaseBackendPage" url="admin" area="admin" module="Magento_Backend"> - <section name="NewExtensionSection"/> + <page name="AdminBaseBackendPage" url="admin" area="admin" module="Magento_Backend"> + <section name="AdminNewExtensionSection"/> </page> </pages> ``` -The `BaseBackendPage` result corresponds to: +The `AdminBaseBackendPage` result corresponds to: ```xml -<page name="BaseBackendPage" url="admin" area="admin" module="Magento_Backend"> +<page name="AdminBaseBackendPage" url="admin" area="admin" module="Magento_Backend"> - <section name="BaseBackendSection"/> - <section name="AnotherBackendSection"/> - <section name="NewExtensionSection"/> + <section name="AdminBaseBackendSection"/> + <section name="AdminAnotherBackendSection"/> + <section name="AdminNewExtensionSection"/> </page> ``` ### Remove a section -**Use case**: The `FooBackend` module extends the `Backend` module and requires deletion of the `AnotherBackendSection` section (the `.../Backend/Page/BaseBackendPage.xml` file): +**Use case**: The `FooBackend` module extends the `Backend` module and requires deletion of the `AdminAnotherBackendSection` section (the `.../Backend/Page/AdminBaseBackendPage.xml` file): ```xml -<page name="BaseBackendPage" url="admin" area="admin" module="Magento_Backend"> - <section name="BaseBackendSection"/> - <section name="AnotherBackendSection"/> +<page name="AdminBaseBackendPage" url="admin" area="admin" module="Magento_Backend"> + <section name="AdminBaseBackendSection"/> + <section name="AdminAnotherBackendSection"/> </page> ``` -Create the `.../FooBackend/Page/BaseBackendPage.xml` file: +Create the `.../FooBackend/Page/AdminBaseBackendPage.xml` file: ```xml -<page name="BaseBackendPage" url="admin" area="admin" module="Magento_Backend"> - <section name="AnotherBackendSection" remove="true"/> +<page name="AdminBaseBackendPage" url="admin" area="admin" module="Magento_Backend"> + <section name="AdminAnotherBackendSection" remove="true"/> </page> ``` -The `BaseBackendPage` result corresponds to: +The `AdminBaseBackendPage` result corresponds to: ```xml -<page name="BaseBackendPage" url="admin" area="admin" module="Magento_Backend"> - <section name="BaseBackendSection"/> +<page name="AdminBaseBackendPage" url="admin" area="admin" module="Magento_Backend"> + <section name="AdminBaseBackendSection"/> </page> ``` @@ -569,3 +571,5 @@ The `_defaultSample` results corresponds to: [`<pages>`]: ./page.md [`<sections>`]: ./section.md [`<tests>`]: ./test.md +[`<action groups>`]: ./test/action-groups.md +[mftfExtendingPrecedence image]: img/mftf-extending-precedence.png diff --git a/docs/metadata.md b/docs/metadata.md index 795c8a038..2bae0c116 100644 --- a/docs/metadata.md +++ b/docs/metadata.md @@ -1,6 +1,12 @@ +--- +title: Metadata +redirect_to: https://developer.adobe.com/commerce/testing/functional-testing-framework/metadata/ +status: migrated +--- + # Metadata -In this topic we talk about handling entities that you need in your tests (such as categories, products, wish lists, and similar) using the MFTF. +In this topic we talk about handling entities that you need in your tests (such as categories, products, wish lists, and similar) using MFTF. Using data handling actions like [`createData`], [`deleteData`], [`updateData`], and [`getData`], you are able to create, delete, update, and read entities for your tests. The framework enables you to send HTTP requests with these statically defined data entities: @@ -29,7 +35,7 @@ Each [operation] includes: - The type of body content encoding in [contentType]. - The body of the request represented as a tree of objects, arrays, and fields. -When a test step requires handling the specified data entity, the MFTF performs the following steps: +When a test step requires handling the specified data entity, MFTF performs the following steps: - Reads input data (`<data/>`) and the type (the `type` attribute) of the specified [entity]. - Searches the metadata operation for the `dataType` that matches the entity's `type`. For example, `<entity type="product">` matches `<operation dataType="product"`. @@ -61,13 +67,14 @@ The following diagram demonstrates the XML structure of a metadata file: </array> </object> </operation> +</operations> ``` ## Principles {#principles} 1. A `dataType` value must match the `type` value of the corresponding entity. -2. A file name should contain data type split with `_` and must end with `-meta`. - Example: `product_attribute-meta.xml`. +2. A file name should be PascalCase and end with `Meta.xml`. + Example: `ProductAttributeMeta.xml`. 3. A metadata file may contain different types of operations (`type`) with the same data entity (`dataType`). Example: @@ -93,8 +100,8 @@ Example: ### Sending a REST API request -The MFTF allows you to handle basic CRUD operations with an object using [Magento REST API][api reference] requests. -To convert a request to the MFTF format, wrap the corresponding REST API request into XML tags according to the [Reference documentation][reference]. +MFTF allows you to handle basic CRUD operations with an object using [Magento REST API][api reference] requests. +To convert a request to MFTF format, wrap the corresponding REST API request into XML tags according to the [Reference documentation][reference]. - GET is used for retrieving data from objects. - POST is used for creating new objects. @@ -122,7 +129,7 @@ Let's see what happens when you create a category: <createData entity="_defaultCategory" stepKey="createPreReqCategory"/> ``` -The MFTF searches in the _Data_ directory an entity with `<entity name="_defaultCategory">` and reads `type` of the entity. +MFTF searches in the _Data_ directory an entity with `<entity name="_defaultCategory">` and reads `type` of the entity. If there are more than one entity with the same name, all of the entities are merged. _Catalog/Data/CategoryData.xml_: @@ -135,10 +142,10 @@ _Catalog/Data/CategoryData.xml_: </entity> ``` -Here, `type` is equal to `"category"`, which instructs the MFTF to search an operation with `dataType="category"`. -Since the action is __to create__ a category, the MFTF will also search for operation with `type="create"` in _Metadata_ for `dataType="category"`. +Here, `type` is equal to `"category"`, which instructs MFTF to search an operation with `dataType="category"`. +Since the action is __to create__ a category, MFTF will also search for operation with `type="create"` in _Metadata_ for `dataType="category"`. -_Catalog/Metadata/category-meta.xml_: +_Catalog/Metadata/CategoryMeta.xml_: ```xml <operation name="CreateCategory" dataType="category" type="create" auth="adminOauth" url="/V1/categories" method="POST"> @@ -186,10 +193,10 @@ Comments in the example below are used to demonstrate relation between JSON requ JSON does not support comments. </div> -Model schema for _catalogCategoryRepositoryV1SavePostBody_ with XML representation of _Catalog/Metadata/category-meta.xml_ in comments: +Model schema for _catalogCategoryRepositoryV1SavePostBody_ with XML representation of _Catalog/Metadata/CategoryMeta.xml_ in comments: ```json -{ // XML representation in the MFTF metadata format (see 'Catalog/Metadata/category-meta.xml') +{ // XML representation in the MFTF metadata format (see 'Catalog/Metadata/CategoryMeta.xml') "category": { // <object key="category" dataType="category"> "id": 0, // Skipped, because Category ID is not available on UI when you create a new category. "parent_id": 0, // <field key="parent_id">integer</field> @@ -205,9 +212,9 @@ Model schema for _catalogCategoryRepositoryV1SavePostBody_ with XML representati "string" // <value>string</value> ], // </array> "include_in_menu": true, // <field key="include_in_menu">boolean</field> - "extension_attributes": {}, // <field key="extension_attributes">empty_extension_attribute</field>, where 'empty_extension_attribute' is a reference to operation with 'dataType="empty_extension_attribute"' (see 'Catalog/Metadata/empty_extension_attribute-meta.xml') + "extension_attributes": {}, // <field key="extension_attributes">empty_extension_attribute</field>, where 'empty_extension_attribute' is a reference to operation with 'dataType="empty_extension_attribute"' (see 'Catalog/Metadata/EmptyExtensionAttributeMeta.xml') "custom_attributes": [ // <array key="custom_attributes"> - { // <value>custom_attribute</value>, where 'custom_attribute' is a reference to operation with 'dataType="custom_attribute"' (see 'Catalog/Metadata/custom_attribute-meta.xml') + { // <value>custom_attribute</value>, where 'custom_attribute' is a reference to operation with 'dataType="custom_attribute"' (see 'Catalog/Metadata/CustomAttributeMeta.xml') "attribute_code": "string", "value": "string" } @@ -235,26 +242,26 @@ The corresponding test step is: <createData entity="guestCart" stepKey="createGuestCart"/> ``` -The MFTF searches in the _Data_ directory an entity with `<entity name="guestCart">` and reads `type`. +MFTF searches in the _Data_ directory an entity with `<entity name="GuestCart">` and reads `type`. _Quote/Data/GuestCartData.xml_: ```xml -<entity name="guestCart" type="guestCart"> +<entity name="GuestCart" type="GuestCart"> </entity> ``` -`type="guestCart"` points to the operation with `dataType=guestCart"` and `type="create"` in the _Metadata_ directory. +`type="guestCart"` points to the operation with `dataType=GuestCart"` and `type="create"` in the _Metadata_ directory. _Catalog/Data/CategoryData.xml_: ```xml -<operation name="CreateGuestCart" dataType="guestCart" type="create" auth="anonymous" url="/V1/guest-carts" method="POST"> +<operation name="CreateGuestCart" dataType="GuestCart" type="create" auth="anonymous" url="/V1/guest-carts" method="POST"> <contentType>application/json</contentType> </operation> ``` -As a result, the MFTF sends an unauthorized POST request with an empty body to the `https://example.com/rest/V1/guest-carts` and stores the single string response that the endpoint returns. +As a result, MFTF sends an unauthorized POST request with an empty body to the `https://example.com/rest/V1/guest-carts` and stores the single string response that the endpoint returns. ### Handling a REST API response {#rest-response} @@ -266,7 +273,7 @@ Let's see how to handle data after you created a category with custom attributes <createData entity="customizedCategory" stepKey="createPreReqCategory"/> ``` -The MFTF receives the corresponding JSON response and enables you to reference its data using a variable of format: +MFTF receives the corresponding JSON response and enables you to reference its data using a variable of format: **$** _stepKey_ **.** _JsonKey_ **$** @@ -342,7 +349,7 @@ You are able to create assurances with `successRegex`, and, optionally, return v The `CreateStoreGroup` operation is used to persist a store group: -Source file is _Store/Metadata/store_group-meta.xml_: +Source file is _Store/Metadata/StoreGroupMeta.xml_: ```xml <operation name="CreateStoreGroup" dataType="group" type="create" auth="adminFormKey" url="/admin/system_store/save" method="POST" successRegex="/messages-message-success/" > @@ -376,9 +383,9 @@ The operation enables you to assign the following form fields: ### Create an object in storefront {#create-object-as-customerFormKey} -The MFTF uses the `CreateWishlist` operation to create a wish list on storefront: +MFTF uses the `CreateWishlist` operation to create a wish list on storefront: -Source file is _Wishlist/Metadata/wishlist-meta.xml_ +Source file is _Wishlist/Metadata/WishlistMeta.xml_ ```xml <operation name="CreateWishlist" dataType="wishlist" type="create" auth="customerFormKey" url="/wishlist/index/add/" method="POST" successRegex="" returnRegex="~\/wishlist_id\/(\d*?)\/~" > @@ -421,6 +428,7 @@ Root element that points to the corresponding XML Schema. | `returnIndex` | string | optional | Specifies index at which the value will be returned when `returnRegex` matches multiple values | | `removeBackend` | boolean | optional | Removes backend name from requested URL. Applicable when `auth="adminFormKey"`. | | `filename` | string | optional | | +|`deprecated`|string|optional|Used to warn about the future deprecation of the test. String will appear in Allure reports and console output at runtime.| - \*`url` - full URL is a concatenation of _ENV.baseUrl_ + `/rest/` + _url_. To reuse data of a required entity or returned response use a field key wrapped in curly braces such as `{sku}`. @@ -556,7 +564,7 @@ Example: <!-- LINK DEFINITIONS --> [actions]: test/actions.md -[api reference]: https://devdocs.magento.com/guides/v2.3/get-started/bk-get-started-api.html +[api reference]: https://devdocs.magento.com/guides/v2.4/get-started/bk-get-started-api.html [application/x-www-form-urlencoded]: https://www.w3.org/TR/html401/interact/forms.html#h-17.13.4.1 {:target="_blank"} [catalogCategoryRepositoryV1 image]: img/catalogCategoryRepository-operations.png @@ -571,7 +579,7 @@ Example: [getData]: test/actions.md#getdata [HTML forms]: https://www.w3.org/TR/html401/interact/forms.html {:target="\_blank"} -[oauth]: https://devdocs.magento.com/guides/v2.3/get-started/authentication/gs-authentication-oauth.html +[oauth]: https://devdocs.magento.com/guides/v2.4/get-started/authentication/gs-authentication-oauth.html {:target="\_blank"} [operation]: #operation-tag [reference]: #reference diff --git a/docs/mftf-tests-packaging.md b/docs/mftf-tests-packaging.md new file mode 100644 index 000000000..961e1425c --- /dev/null +++ b/docs/mftf-tests-packaging.md @@ -0,0 +1,64 @@ +--- +title: MFTF functional test modules and packaging +redirect_to: https://developer.adobe.com/commerce/testing/functional-testing-framework/mftf-tests-packaging/ +status: migrated +--- + +<style> +.mftf-dl { + margin-bottom: 2.5em; +} +dl dt{ + font-weight:400; +} +</style> + +# MFTF functional test modules and packaging + +## MFTF predefined test module paths +The Magento Functional Testing Framework can run tests from predefined paths and custom paths. The predefined paths are: +``` +app/code/<Vendor>/<Module>/Test/Mftf +dev/tests/acceptance/tests/functional/<Vendor>/<TestModule> +vendor/<Vendor>/<Module>/Test/Mftf +vendor/<Vendor>/<TestModule> +``` + +To support future service isolation, Test module in `dev/tests/acceptance/tests/functional/<Vendor>/<TestModule>` and +`vendor/<Vendor>/<TestModule>` must define the module type as `magento2-functional-test-module` in its `composer.json` file. +No `composer.json` file is required for tests in `app/code/<Vendor>/<Module>/Test/Mftf` and `vendor/<Vendor>/<Module>/Test/Mftf` +as they are part of the Magento modules. + +Test module for a specific Magento module can only be in one of the paths. + +## Test module composer.json format + +Test module `composer.json` file should use type `magento2-functional-test-module`. + +Test module `composer.json` file should define Magento module dependencies in suggests block. +MFTF will recognize the dependency if the suggest message of a module specifies `type` using `magento2-module` and `name` +using module name registered with Magento. + +Here is an example `composer.json` file for the test module `dev/tests/acceptance/tests/functional/Magento/ConfigurableProductCatalogSearch`: + +```json +{ + "name": "magento/module-configurable-product-catalog-search-functional-test", + "description": "MFTF test module for Magento_ConfigurableProduct and Magento_CatalogSearch", + "type": "magento2-functional-test-module", + "config": { + "sort-packages": true + }, + "require": { + "magento/magento2-functional-testing-framework": ">=2.5" + }, + "suggest": { + "magento/module-configurable-product": "type: magento2-module, name: Magento_ConfigurableProduct, version: *", + "magento/module-catalog-search": "type: magento2-module, name: Magento_CatalogSearch, version: *" + }, + "license": [ + "OSL-3.0", + "AFL-3.0" + ] +} +``` \ No newline at end of file diff --git a/docs/mftf-tests.md b/docs/mftf-tests.md deleted file mode 100644 index b9d94baa9..000000000 --- a/docs/mftf-tests.md +++ /dev/null @@ -1,31 +0,0 @@ -<style> -.mftf-dl { - margin-bottom: 2.5em; -} -dl dt{ - font-weight:400; -} - -</style> - -# MFTF functional test reference - -The Magento Functional Testing Framework runs tests on every Module within Magento. These files are stored within each Module folder in the Magento repo. -This page lists all those tests so that developers can have a good sense of what is covered. - -{% include mftf/mftf_data.md %} - -{% for item in mftf %} - -### {{ item.name }} -{% for file in item.items %} -#### [{{ file.filename }}]({{file.repo}}) -{: .mftf-test-link} - -{% for test in file.tests %} -{{test.testname}} - : {{test.description}} -{: .mftf-dl} -{% endfor %} -{% endfor %} -{% endfor %} diff --git a/docs/page.md b/docs/page.md index 705a94294..89f50b634 100644 --- a/docs/page.md +++ b/docs/page.md @@ -1,6 +1,12 @@ +--- +title: Page structure +redirect_to: https://developer.adobe.com/commerce/testing/functional-testing-framework/page/ +status: migrated +--- + # Page structure -The MFTF uses a modified concept of [PageObjects], which models the testing areas of your page as objects within the code. +MFTF uses a modified concept of [PageObjects], which models the testing areas of your page as objects within the code. This reduces occurrences of duplicated code and enables you to fix things quickly, in one place, when things change. You define the contents of a page, for reference in a [`<test>`], at both the [`<page>`] and [`<section>`] level. @@ -45,7 +51,8 @@ The following conventions apply to MFTF pages: - `<page>` name must be alphanumeric. - `*Page.xml` is stored in the _Page_ directory of a module. - The name format is `{Admin|Storefront}{PageDescription}Page.xml`. - +- One `<page>` tag is allowed per page XML file. + The `.url` attribute is required when using the page for [actions] that require the URL argument. ## Page examples @@ -60,7 +67,7 @@ Example (_Catalog/Page/AdminCategoryPage.xml_ file): <?xml version="1.0" encoding="UTF-8"?> <pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../..dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/PageObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> <page name="AdminCategoryPage" url="catalog/category/" module="Magento_Catalog" area="admin"> <section name="AdminCategorySidebarActionSection"/> <section name="AdminCategorySidebarTreeSection"/> @@ -96,7 +103,7 @@ Example (_Catalog/Page/StorefrontCategoryPage.xml_ file): <?xml version="1.0" encoding="UTF-8"?> <pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../..dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/PageObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> <page name="StorefrontCategoryPage" url="/{{var1}}.html" module="Magento_Catalog" parameterized="true" area="storefront"> <section name="StorefrontCategoryMainSection"/> </page> @@ -106,15 +113,15 @@ Example (_Catalog/Page/StorefrontCategoryPage.xml_ file): This example shows the page with the name `StorefrontCategoryPage`. It will be merged with the other `StorefrontCategoryPage` pages from other modules. -The following is an example of a call in test: +The following is an example of a call in a test: ```xml -<amOnPage url="{{StorefrontCategoryPage.url($$createPreReqCategory.name$$)}}" stepKey="navigateToCategoryPage"/> +<amOnPage url="{{StorefrontCategoryPage.url($createPreReqCategory.name$)}}" stepKey="navigateToCategoryPage"/> ``` The `StorefrontCategoryPage` page is declared as parameterized, where the `url` contains a `{{var1}}` parameter. -The corresponding web page is generated by the Magento Catalog module and is called by the `baseUrl`+`/$$createPreReqCategory.name$$.html` URl. +The corresponding web page is generated by the Magento Catalog module and is called by the `baseUrl`+`/$createPreReqCategory.name$.html` URl. `{{var1}}` is substituted with the `name` of the previously created category in the `createPreReqCategory` action. @@ -131,7 +138,7 @@ There are several XML elements that are used in `<page>` in the MFTF. ### pages {#pages-tag} `<pages>` are elements that point to the corresponding XML Schema location. -It contains one or more `<page>` elements. +It contains only one `<page>` element. ### page {#page-tag} @@ -145,6 +152,7 @@ Attributes|Type|Use|Description `area`|string|required|The area where this page lives. Three possible values: `admin` prepends `BACKEND_NAME` to `url`, `storefront` does not prepend anything to `url`, `external` flags the page for use with `amOnUrl`. The `url` provided must be a full URL, such as `http://myFullUrl.com/`, instead of the URL for a Magento page. `parameterized`|boolean |optional|Include and set to `"true"` if the `url` for this page has parameters that need to be replaced for proper use. `remove`|boolean|optional|The default value is `"false"`. Set to `"true"` to remove this element during parsing. +`deprecated`|string|optional|Used to warn about the future deprecation of the data entity. String will appear in Allure reports and console output at runtime. `<page>` may contain several [`<section>`] elements. diff --git a/docs/reporting.md b/docs/reporting.md index 59a8617ba..a70835309 100644 --- a/docs/reporting.md +++ b/docs/reporting.md @@ -1,3 +1,9 @@ +--- +title: Reporting +redirect_to: https://developer.adobe.com/commerce/testing/functional-testing-framework/reporting/ +status: migrated +--- + # Reporting The Magento Functional Testing Framework provides two types of reporting: @@ -57,42 +63,49 @@ The general information can be useful for MFTF contributors, but can be ignored Let's consider the general part of the following test execution report: ```terminal -==== Redirecting to Composer-installed version in vendor/codeception ==== -Codeception PHP Testing Framework v2.3.9 -Powered by PHPUnit 6.5.13 by Sebastian Bergmann and contributors. +Generate Tests Command Run +Codeception PHP Testing Framework v4.1.4 +Powered by PHPUnit 9.1.3 by Sebastian Bergmann and contributors. +Running with seed: Magento\FunctionalTestingFramework.functional Tests (2) ------------------------ -Modules: \Magento\FunctionalTestingFramework\Module\MagentoWebDriver, \Magento\FunctionalTestingFramework\Helper\Acceptance, \Magento\FunctionalTestingFramework\Helper\MagentoFakerData, \Magento\FunctionalTestingFramework\Module\MagentoRestDriver, PhpBrowser, \Magento\FunctionalTestingFramework\Module\MagentoSequence, \Magento\FunctionalTes +Modules: \Magento\FunctionalTestingFramework\Module\MagentoWebDriver, \Magento\FunctionalTestingFramework\Module\MagentoSequence, \Magento\FunctionalTestingFramework\Module\MagentoAssert, \Magento\FunctionalTestingFramework\Module\MagentoActionProxies, Asserts, \Magento\FunctionalTestingFramework\Helper\HelperContainer ``` -After the test generation command (mentioned in the previous section), MFTF delegates control to the `vendor/codeception` tool, which is the `Codeception PHP Testing Framework` of version `2.3.9` that uses `PHPUnit` of version `6.5.13`. +After the test generation command (mentioned in the previous section), MFTF delegates control to the `vendor/codeception` tool, which is the `Codeception PHP Testing Framework` of version `4.1.4` that uses `PHPUnit` of version `9.1.3`. The tool runs `2 Tests` using the configuration defined in the `functional` suite under the `Magento\FunctionalTestingFramework` namespace. The corresponding configuration file is `acceptance/tests/functional.suite.yml`. -It enables `Modules: \Magento\FunctionalTestingFramework\Module\MagentoWebDriver, \Magento\FunctionalTestingFramework\Helper\Acceptance, \Magento\FunctionalTestingFramework\Helper\MagentoFakerData, \Magento\FunctionalTestingFramework\Module\MagentoRestDriver, PhpBrowser, \Magento\FunctionalTestingFramework\Module\MagentoSequence, ...` +It enables `Modules: \Magento\FunctionalTestingFramework\Module\MagentoWebDriver, \Magento\FunctionalTestingFramework\Module\MagentoSequence, \Magento\FunctionalTestingFramework\Module\MagentoAssert, \Magento\FunctionalTestingFramework\Module\MagentoActionProxies, Asserts, \Magento\FunctionalTestingFramework\Helper\HelperContainer,..` #### Passed tests The next chunk of the log reports about test execution of the first test: ```terminal -AdminLoginTestCest: Admin login test -Signature: Magento\AcceptanceTest\_default\Backend\AdminLoginTestCest:AdminLoginTest -Test: tests/functional/Magento/FunctionalTest/_generated/default/AdminLoginTestCest.php:AdminLoginTest +AdminLoginSuccessfulTestCest: Admin login successful test +Signature: Magento\AcceptanceTest\_default\Backend\AdminLoginSuccessfulTestCest:AdminLoginSuccessfulTest +Test: tests/functional/Magento/_generated/default/AdminLoginSuccessfulTestCest.php:AdminLoginSuccessfulTest Scenario -- -I am on page "/admin/admin" -I fill field "#username","admin" -I fill field "#login","123123q" -I click ".actions .action-primary" -I wait for page load 30 -I close admin notification -I see in current url "/admin/admin" -PASSED +[loginAsAdmin] AdminLoginActionGroup + [navigateToAdmin] am on page "/admin/admin" + [fillUsername] fill field "#username","admin" + [fillPassword] fill field "#login","123123q" + [clickLogin] click ".actions .action-primary" + [clickLoginWaitForPageLoad] wait for page load 30 + [clickDontAllowButtonIfVisible] conditional click ".modal-popup .action-secondary",".modal-popup .action-secondary",true + [closeAdminNotification] close admin notification +[assertLoggedIn] AssertAdminSuccessLoginActionGroup + [waitForAdminAccountTextVisible] wait for element visible ".page-header .admin-user-account-text",60 + [assertAdminAccountTextElement] see element ".page-header .admin-user-account-text" +[logoutFromAdmin] AdminLogoutActionGroup + [amOnLogoutPage] am on page "/admin/admin/auth/logout/" + PASSED ``` -The running test is `AdminLoginTestCest`, which is `Admin login test` (this text is generated from the test name but with the `Cest` part excluded). -Its test signature is `Magento\AcceptanceTest\_default\Backend\AdminLoginTestCest:AdminLoginTest` that matches a `className:methodName` format using namespaces. -A path to the corresponding `Test` is `tests/functional/Magento/FunctionalTest/_generated/default/AdminLoginTestCest.php:AdminLoginTest` (relative to the `acceptance/` directory). +The running test is `AdminLoginSuccessfulTestCest`, which is `Admin login successful test` (this text is generated from the test name but with the `Cest` part excluded). +Its test signature is `Magento\AcceptanceTest\_default\Backend\AdminLoginSuccessfulTestCest:AdminLoginSuccessfulTest` that matches a `className:methodName` format using namespaces. +A path to the corresponding `Test` is `tests/functional/Magento/_generated/default/AdminLoginSuccessfulTestCest.php:AdminLoginSuccessfulTest` (relative to the `acceptance/` directory). `Scenario` lists the tests steps as they run during test execution, ending with the successful test verdict `PASSED`. It means that all test steps were processed as expected. @@ -104,51 +117,54 @@ The second test fails with the following report: ```terminal AdminMenuNavigationWithSecretKeysTestCest: Admin menu navigation with secret keys test Signature: Magento\AcceptanceTest\_default\Backend\AdminMenuNavigationWithSecretKeysTestCest:AdminMenuNavigationWithSecretKeysTest -Test: tests/functional/Magento/FunctionalTest/_generated/default/AdminMenuNavigationWithSecretKeysTestCest.php:AdminMenuNavigationWithSecretKeysTest +Test: tests/functional/Magento/_generated/default/AdminMenuNavigationWithSecretKeysTestCest.php:AdminMenuNavigationWithSecretKeysTest Scenario -- -I magento cli "config:set admin/security/use_form_key 1" +[enableUrlSecretKeys] magento cli "config:set admin/security/use_form_key 1",60 Value was saved. -I magento cli "cache:clean config full_page" + +[cleanInvalidatedCaches1] magento cli "cache:clean config full_page",60 Cleaned cache types: config full_page -I am on page "/admin/admin" -I wait for page load -I fill field "#username","admin" -I fill field "#login","123123q" -I click ".actions .action-primary" -I wait for page load 30 -I close admin notification -I click "//li[@id='menu-magento-backend-stores']" -I wait for loading mask to disappear -I click "#nav li[data-ui-id='menu-magento-config-system-config']" -I wait for page load -I see current url matches "~\/admin\/system_config\/~" -I see "#something" -I save screenshot -FAIL - -I magento cli "config:set admin/security/use_form_key 0" + +[loginAsAdmin] AdminLoginActionGroup + [navigateToAdmin] am on page "/admin/admin" + [fillUsername] fill field "#username","admin" + [fillPassword] fill field "#login","123123q" + [clickLogin] click ".actions .action-primary" + [clickLoginWaitForPageLoad] wait for page load 30 + [clickDontAllowButtonIfVisible] conditional click ".modal-popup .action-secondary",".modal-popup .action-secondary",true + [closeAdminNotification] close admin notification +[clickStoresMenuOption1] click "#menu-magento-backend-stores" +[waitForStoresMenu1] wait for loading mask to disappear +[clickStoresConfigurationMenuOption1] click "#nav li[data-ui-id='menu-magento-config-system-config']" +[waitForConfigurationPageLoad1] wait for page load 60 +[seeCurrentUrlMatchesConfigPath1] see current url matches "~\/admin\/system_config\/~" +[clickCatalogMenuOption] click "#something" +[saveScreenshot] save screenshot +[disableUrlSecretKeys] magento cli "config:set admin/security/use_form_key 0",60 Value was saved. -I magento cli "cache:clean config full_page" + +[cleanInvalidatedCaches2] magento cli "cache:clean config full_page",60 Cleaned cache types: config full_page -I am on page "/admin/admin/auth/logout/" + +[logout] AdminLogoutActionGroup + [amOnPage] am on page "/admin/admin/auth/logout/" + FAIL -------------------------------------------------------------------------------- ``` The general test details and scenario has the same format as in the Passed test. -The interesting part starts near the `FAIL` line. ```terminal -I see "#something" -I save screenshot -FAIL +[clickCatalogMenuOption] click "#something" +[saveScreenshot] save screenshot ``` When a test step fails, MFTF always saves a screenshot of the web page with the failing state immediately after the failure occurs. -`I save screenshot` follows the failing test step `I see "#something"` in our case. +`[saveScreenshot] save screenshot` follows the failing test step `[clickCatalogMenuOption] click "#something"` in our case. A screenshot of the fail goes at the `acceptance/tests/_output` directory in both PNG and HTML formats: @@ -165,27 +181,19 @@ The file name encodes: - with the `AdminMenuNavigationWithSecretKeysTest` test name - and execution status `fail` -Actions after `FAIL` are run as a part of the [`after`][] hook of the test. +Actions after `saveScreenshot` are run as a part of the [`after`][] hook of the test. ### Test result report After MFTF completed test execution, it generates a general report about test results along with detailed information about each fail. ```terminal --------------------------------------------------------------------------------- -DEPRECATION: Calling the "Symfony\Component\BrowserKit\Client::getInternalResponse()" method before the "request()" one is deprecated since Symfony 4.1 and will throw an exception in 5.0. /Users/.../magento2ce/vendor/symfony/browser-kit/Client.php:208 - -Time: 52.43 seconds, Memory: 16.00MB +Time: 02:07.534, Memory: 150.50 MB There was 1 failure: --------- ``` - -First you see warnings and deprecations. -The `DEPRECATION` here is thrown by an MFTF dependency (Symfony) that is out of the scope for test writers and should be considered by MFTF contributors. -If you encounter this type of reporting, [report an issue][]. - -Then, MFTF reports that the test run took 52.43 seconds using 16 MB of system RAM. +MFTF reports that the test run took 02:07.534 using 150.50 MB of system RAM. And, finally, that there was `1 failure`. Next, the report provides details about the test failure. @@ -193,75 +201,38 @@ Next, the report provides details about the test failure. ```terminal --------- 1) AdminMenuNavigationWithSecretKeysTestCest: Admin menu navigation with secret keys test -Test tests/functional/Magento/FunctionalTest/_generated/default/AdminMenuNavigationWithSecretKeysTestCest.php:AdminMenuNavigationWithSecretKeysTest -Step See "#something" -Fail Failed asserting that on page /admin/admin/system_config/index/key/678b7ba922c.../ ---> DASHBOARD -SALES -CATALOG -CUSTOMERS -MARKETING -CONTENT -REPORTS -STORES -SYSTEM -FIND PARTNERS & EXTENSIONS -Configuration -admin -1 -Store View: Default Config -What is this? -Save Config -Country Options -State Options -Locale Options -Store Information -Store Name -Store Phone Number -Store Hours of Operation -Countr -[Content too long to display. See complete response in '/Users/dmytroshevtsov/Projects/vagrant/vagrant-magento/magento2ce/dev/tests/acceptance/tests/_output/' directory] ---> contains "#something". + Test tests/functional/Magento/_generated/default/AdminMenuNavigationWithSecretKeysTestCest.php:AdminMenuNavigationWithSecretKeysTest + Step Click "#something" + Fail CSS or XPath element with '#something' was not found. Scenario Steps: -23. $I->amOnPage("/admin/admin/auth/logout/") at tests/functional/Magento/FunctionalTest/_generated/default/AdminMenuNavigationWithSecretKeysTestCest.php:54 -22. // Cleaned cache types: + 27. // Exiting Action Group [logout] AdminLogoutActionGroup + 26. $I->amOnPage("/admin/admin/auth/logout/") at tests/functional/Magento/_generated/default/AdminMenuNavigationWithSecretKeysTestCest.php:55 + 25. // Entering Action Group [logout] AdminLogoutActionGroup + 24. // Cleaned cache types: config full_page -21. $I->magentoCLI("cache:clean config full_page") at tests/functional/Magento/FunctionalTest/_generated/default/AdminMenuNavigationWithSecretKeysTestCest.php:52 -20. // Value was saved. -19. $I->magentoCLI("config:set admin/security/use_form_key 0") at tests/functional/Magento/FunctionalTest/_generated/default/AdminMenuNavigationWithSecretKeysTestCest.php:50 -18. $I->saveScreenshot() at tests/functional/Magento/FunctionalTest/_generated/default/AdminMenuNavigationWithSecretKeysTestCest.php:63 + + 23. $I->magentoCLI("cache:clean config full_page",60) at tests/functional/Magento/_generated/default/AdminMenuNavigationWithSecretKeysTestCest.php:52 + 22. // Value was saved. ``` - `1) AdminMenuNavigationWithSecretKeysTestCest: Admin menu navigation with secret keys test` - the failed Codeception test is *AdminMenuNavigationWithSecretKeysTestCest*. It references to the PHP class that implemented the failed test. -- `Test tests/functional/Magento/FunctionalTest/_generated/default/AdminMenuNavigationWithSecretKeysTestCest.php:AdminMenuNavigationWithSecretKeysTest` - the test is implemented in the *AdminMenuNavigationWithSecretKeysTest* test method of the *tests/functional/Magento/FunctionalTest/_generated/default/AdminMenuNavigationWithSecretKeysTestCest.php* file under `<magento root>/dev/tests/acceptance/`. +- `Test tests/functional/Magento/_generated/default/AdminMenuNavigationWithSecretKeysTestCest.php:AdminMenuNavigationWithSecretKeysTest` - the test is implemented in the *AdminMenuNavigationWithSecretKeysTest* test method of the *tests/functional/Magento/FunctionalTest/_generated/default/AdminMenuNavigationWithSecretKeysTestCest.php* file under `<magento root>/dev/tests/acceptance/`. It matches the corresponding test defined in XML that is *AdminMenuNavigationWithSecretKeysTest* defined in `<test name="AdminMenuNavigationWithSecretKeysTest">...</test>` -- `Step See "#something"` - the failing test step is the *see* action with the *#something* selector. It would correspond the `<see selector="#something" ... />` test step in the XML defined tests. - -- `Fail Failed asserting that on page /admin/admin/system_config/index/key/678b7ba922c.../` - the fail occurred on the web page `<MAGENTO_BASE_URL>/admin/admin/system_config/index/key/678b7ba922c.../`. - -```terminal ---> ... -[Content too long to display. See complete response in '/../../magento2/dev/tests/acceptance/tests/_output/' directory] ---> contains "#something". -``` - -The web page is too long to be reported in the CLI, and it is stored at *'/../../magento2/dev/tests/acceptance/tests/_output/'*. -Search the web page by test name *AdminMenuNavigationWithSecretKeysTest*. -The failing test assertion is that the web page contains *contains* a CSS locator *#something*. +- `Step Click "#something"` - the failing test step is the *click* action with the *#something* selector. It would correspond the `<click selector="#something" ... />` test step in the XML defined tests. Finally, the report finishes with fairly self-descriptive lines. ```terminal FAILURES! -Tests: 2, Assertions: 3, Failures: 1. +Tests: 2, Assertions: 2, Failures: 1. ``` -MFTF encountered failures due to the last test run, that included *2* tests with *3* assertions. +MFTF encountered failures due to the last test run, that included *2* tests with *2* assertions. *1* assertion fails. ## Allure @@ -324,8 +295,7 @@ And if you run the `open` command with no arguments while you are in the same di allure open ``` -Allure would attempt to open a generated report at the `magento2/allure-report/` directory.' -%} +Allure would attempt to open a generated report at the `magento2/allure-report/` directory. To clean up existing reports before generation (for example after getting new results), use the `--clean` flag: @@ -341,7 +311,7 @@ Refer to the [Reporting section][] for more Allure CLI details. [`run:group`]: commands/mftf.md#rungroup [`run:test`]: commands/mftf.md#runtest [Allure Framework]: https://docs.qameta.io/allure/ -[Allure Test Report]: http://allure.qatools.ru/ +[Allure Test Report]: https://github.com/allure-framework [codecept]: commands/codeception.md [codeception]: https://codeception.com/docs/reference/Commands [mftf]: commands/mftf.md diff --git a/docs/section.md b/docs/section.md index 98221ec44..7e8645d7e 100644 --- a/docs/section.md +++ b/docs/section.md @@ -1,3 +1,9 @@ +--- +title: Section structure +redirect_to: https://developer.adobe.com/commerce/testing/functional-testing-framework/section/ +status: migrated +--- + # Section structure A `<section>` is a reusable part of a [`<page>`](./page.html) and is the standard file for defining UI elements on a page used in a test. @@ -48,7 +54,7 @@ The following conventions apply to MFTF sections: - `*Section.xml` is stored in the _Section_ directory of a module. - The name format is `{Admin|Storefront}{SectionDescription}Section.xml`. - Camel case is used for `<section>` elements. - They describe the function of the element rather than attempting to describe the selector used. +- One `<section>` tag is allowed per section XML file. ## Example @@ -58,11 +64,11 @@ Example (`.../Catalog/Section/AdminCategorySidebarActionSection.xml` file): <?xml version="1.0" encoding="utf-8"?> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> - <section name="AdminCategorySidebarActionSection"> - <element name="addRootCategoryButton" type="button" selector="#add_root_category_button" timeout="30"/> - <element name="addSubcategoryButton" type="button" selector="#add_subcategory_button" timeout="30"/> - </section> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="AdminCategorySidebarActionSection"> + <element name="AddRootCategoryButton" type="button" selector="#add_root_category_button" timeout="30"/> + <element name="AddSubcategoryButton" type="button" selector="#add_subcategory_button" timeout="30"/> + </section> </sections> ``` @@ -70,14 +76,14 @@ This example uses a `AdminCategorySidebarActionSection` section. All sections wi The `AdminCategorySidebarActionSection` section declares two buttons: -- `addRootCategoryButton` - button with a `#add_root_category_button` locator on the parent web page -- `addSubcategoryButton` - button with a `#add_subcategory_button` locator on the parent web page +- `AddRootCategoryButton` - button with a `#add_root_category_button` locator on the parent web page +- `AddSubcategoryButton` - button with a `#add_subcategory_button` locator on the parent web page The following is an example of a call in test: ```xml <!-- Click on the button with locator "#add_subcategory_button" on the web page--> -<click selector="{{AdminCategorySidebarActionSection.addSubcategoryButton}}" stepKey="clickOnAddSubCategory"/> +<click selector="{{AdminCategorySidebarActionSection.AddSubcategoryButton}}" stepKey="clickOnAddSubCategory"/> ``` ## Elements reference @@ -89,6 +95,7 @@ The following is an example of a call in test: Attributes|Type|Use|Description ---|---|---|--- `name`|string|required|Unique section name identifier. +`deprecated`|string|optional|Used to warn about the future deprecation of the section. String will appear in Allure reports and console output at runtime. `remove`|boolean|optional|The default is `false`. Set to `true` to remove this element during parsing. ### element {#element-tag} @@ -103,6 +110,7 @@ Attributes|Type|Use|Description `locatorFunction`|string|optional|[Locator function][] declaration to be used in lieu of a selector. `timeout`|string|optional|The timeout after interaction with the element (in seconds). The default is _none_. `parameterized`|boolean|optional|Include and set to `true` if the `selector` for this element has parameters that need to be replaced for proper use. Learn more in [Parameterized selectors][]. +`deprecated`|string|optional|Used to warn about the future deprecation of the element. String will appear in Allure reports and console output at runtime. `remove`|boolean|optional|The default is `false`. Set to `true` to remove this element during parsing. #### `timeout` attribute {#timeout-attribute} @@ -114,21 +122,21 @@ The most usual use case is a test step with a button click action. The section element code declaration containing the timeout attribute: -> StorefrontSigninSection.xml +> StorefrontCustomerSignInPopupFormSection.xml ```xml ... -<element name="signIn" type="button" selector="#signIn" timeout="30"/> +<element name="signIn" type="button" selector="#send2" timeout="30"/> ... ``` The test step that covers the use case: -> StorefrontSigninTest.xml +> CaptchaWithDisabledGuestCheckoutTest.xml ```xml ... -<click selector="{{StorefrontSigninSection.signIn}}" ../> +<click selector="{{StorefrontCustomerSignInPopupFormSection.signIn}}" stepKey="clickSignIn"/> ... ``` diff --git a/docs/section/locator-functions.md b/docs/section/locator-functions.md index d52c2fc72..8a9424aac 100644 --- a/docs/section/locator-functions.md +++ b/docs/section/locator-functions.md @@ -1,5 +1,10 @@ -# Locator functions +--- +title: Locator functions +redirect_to: https://developer.adobe.com/commerce/testing/functional-testing-framework/section/locator-functions/ +status: migrated +--- +# Locator functions ## Define Locator::functions in elements @@ -32,7 +37,7 @@ An element cannot, however, have both a `selector` and a `locatorFunction`. Given the above element definitions, you call the elements in a test just like any other element. No special reference is required, as you are still just referring to an `element` inside a `section`: ```xml -<test name="LocatorFuctionTest"> +<test name="LocatorFunctionTest"> <click selector="{{LocatorFunctionSection.simpleLocator}}" stepKey="SimpleLocator"/> <click selector="{{LocatorFunctionSection.simpleLocatorTwoParam('string1', 'string2')}}" stepKey="TwoParamLiteral"/> </test> @@ -41,6 +46,6 @@ Given the above element definitions, you call the elements in a test just like a <!-- {% endraw %} --> <!-- Link Definitions --> -[Locator functions]: http://codeception.com/docs/reference/Locator +[Locator functions]: https://codeception.com/docs/reference/Locator [section]: ../section.md [parameterized selectors]: ./parameterized-selectors.md \ No newline at end of file diff --git a/docs/section/parameterized-selectors.md b/docs/section/parameterized-selectors.md index 58227948f..99c1f7c98 100644 --- a/docs/section/parameterized-selectors.md +++ b/docs/section/parameterized-selectors.md @@ -1,3 +1,9 @@ +--- +title: Parameterized selectors +redirect_to: https://developer.adobe.com/commerce/testing/functional-testing-framework/section/parameterized-selectors/ +status: migrated +--- + # Parameterized selectors Use the following examples to create and use parameterized selectors in the MFTF. diff --git a/docs/selectors.md b/docs/selectors.md index 376140819..4a28bdb31 100644 --- a/docs/selectors.md +++ b/docs/selectors.md @@ -1,3 +1,9 @@ +--- +title: Selectors +redirect_to: https://developer.adobe.com/commerce/testing/functional-testing-framework/test-writing/selectors/ +status: migrated +--- + ## Selectors These guidelines should help you to write high quality selectors. diff --git a/docs/suite.md b/docs/suite.md index e49dd02ab..97f2c9603 100644 --- a/docs/suite.md +++ b/docs/suite.md @@ -1,12 +1,19 @@ +--- +title: Suites +redirect_to: https://developer.adobe.com/commerce/testing/functional-testing-framework/suite/ +status: migrated +--- + # Suites -Suites are essentially groups of tests that run in the specific conditions (preconditions and postconditions). -They enable you including, excluding, and grouping tests for a customized test run when you need it. +Suites are essentially groups of tests that run in specific conditions (preconditions and postconditions). +They enable including, excluding, and grouping tests for a customized test run. You can form suites using separate tests, groups, and modules. -Each suite must be defined in the `<magento 2 root>/dev/tests/acceptance/tests/_suite/suite.xml` file. -The generated tests for each suite go into a separate directory under `<magento 2 root>/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/_generated/`. -By default, all generated tests are stored in the _default_ suite under `.../Magento/FunctionalTest/_generated/default/` +Each suite must be defined in the `<VendorName>/<ModuleName>/Test/Mftf/Suite` directory. + +The tests for each suite are generated in a separate directory under `<magento 2 root>/dev/tests/acceptance/tests/functional/Magento/_generated/`. +All tests that are not within a suite are generated in the _default_ suite at `<magento 2 root>/dev/tests/acceptance/tests/functional/Magento/_generated/default`. <div class="bs-callout bs-callout-info"> If a test is generated into at least one custom suite, it will not appear in the _default_ suite. @@ -18,7 +25,6 @@ The format of a suite: ```xml <?xml version="1.0" encoding="UTF-8"?> - <suites xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Suite/etc/suiteSchema.xsd"> <suite name=""> <before> @@ -42,14 +48,17 @@ The format of a suite: ## Principles - A suite name: - - must not match any existing group value. + + - must not match any existing group value. For example, the suite `<suite name="ExampleTest">` will fail during test run if any test contains in annotations `<group value="ExampleTest">`. - - must not be `default` or `skip`. Tests that are not in any suite are generated under the `default` suite. - The suite name `skip` is synonymous to including a test in the `<group value="skip"/>`, which will be deprecated in MFTF 3.0.0. - - can contain letters, numbers, and underscores. - - should be upper camel case. + - must not be `default` or `skip`. Tests that are not in any suite are generated under the `default` suite. + The suite name `skip` is synonymous to including a test in the `<group value="skip"/>`. + - can contain letters, numbers, and underscores. + - should be upper camel case. + - A suite must contain at least one `<include>`, or one `<exclude>`, or both. - Using `<before>` in a suite, you must add the corresponding `<after>` to restore the initial state of your testing instance. +- One `<suite>` tag is allowed per suite XML file. ## Conditions @@ -58,9 +67,7 @@ The code lives in one place and executes once per suite. - Set up preconditions and postconditions using [actions] in [`<before>`] and [`<after>`] correspondingly, just similar to use in a [test]. - Clean up after suites just like after tests. - The MFTF enforces the presence of both `<before>` and `<after>` if either is present. -- Do not reference in the subsequent tests to data that was persisted in the preconditions. - Referencing to `$stepKey.field$` of these actions is not valid. +MFTF enforces the presence of both `<before>` and `<after>` if either is present. ## Test writing @@ -134,14 +141,14 @@ It performs the following steps: *After* the testing, the suite returns the Magento instance to the initial state disabling WYSIWYG: 1. Log back in. -2. Disable **WYSIWYG** so that +2. Disable **WYSIWYG** in the Magento instance. This suite includes all tests that contain the `<group value="WYSIWYG"/>` annotation. ### Execute Magento CLI commands in suite conditions ```xml -<suites xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Suite/etc/suiteSchema.xsd"> +<suites xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Suite/etc/suiteSchema.xsd"> <suite name="Cache"> <before> <magentoCLI stepKey="disableCache" command="cache:disable"/> @@ -169,7 +176,7 @@ The suite includes a specific test `SomeCacheRelatedTest` and every `<test>` tha ### Change Magento configurations in suite conditions ```xml -<suites xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Suite/etc/suiteSchema.xsd"> +<suites xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Suite/etc/suiteSchema.xsd"> <suite name="PaypalConfiguration"> <before> <createData entity="SamplePaypalConfig" stepKey="createSamplePaypalConfig"/> @@ -243,11 +250,11 @@ The element can contain [`<test>`], [`<group>`], and [`<module>`]. A set of filters that you can use to specify which tests to exclude in the test suite. -There two types of behavior: +There are two types of behavior: 1. Applying filters to the included tests when the suite contains [`<include>`] filters. The MFTF will exclude tests from the previously included set and generate the remaining tests in the suite. -2. Applying filter to all tests when the suite does not contain [`<include>`] filters. +2. Applying filters to all tests when the suite does not contain [`<include>`] filters. The MFTF will generate all existing tests except the excluded. In this case, the custom suite will contain all generated tests except excluded, and the _default_ suite will contain the excluded tests only. diff --git a/docs/test-prep.md b/docs/test-prep.md index b344fcf9f..8a54c3682 100644 --- a/docs/test-prep.md +++ b/docs/test-prep.md @@ -1,3 +1,9 @@ +--- +title: Preparing a test for MFTF +redirect_to: https://developer.adobe.com/commerce/testing/functional-testing-framework/test-writing/test-prep/ +status: migrated +--- + # Preparing a test for MFTF This tutorial demonstrates the process of converting a raw functional test into a properly abstracted test file, ready for publishing. @@ -47,7 +53,7 @@ For our example, we have a test that creates a simple product. Note the hardcode <?xml version="1.0" encoding="UTF-8"?> <!-- /** - * Copyright © Magento, Inc. All rights reserved. + * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ --> @@ -112,7 +118,7 @@ In this example `AdminProductFormSection` refers to the `<section>` in the XML f <?xml version="1.0" encoding="UTF-8"?> <!-- /** - * Copyright © Magento, Inc. All rights reserved. + * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ --> @@ -176,7 +182,7 @@ Here we are interested in `<section name="AdminProductFormSection">`, where we a <?xml version="1.0" encoding="UTF-8"?> <!-- /** - * Copyright © Magento, Inc. All rights reserved. + * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ --> @@ -211,7 +217,7 @@ We replace the hardcoded values with variables and the MFTF will do the variable <?xml version="1.0" encoding="UTF-8"?> <!-- /** - * Copyright © Magento, Inc. All rights reserved. + * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ --> @@ -316,7 +322,7 @@ To create an action group, take the steps and put them within an `<actionGroup>` <?xml version="1.0" encoding="UTF-8"?> <!-- /** - * Copyright © Magento, Inc. All rights reserved. + * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ --> @@ -347,7 +353,7 @@ Now we can reference this action group within our test (and any other test). <?xml version="1.0" encoding="UTF-8"?> <!-- /** - * Copyright © Magento, Inc. All rights reserved. + * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ --> diff --git a/docs/test.md b/docs/test.md index dcda0ba50..f759432cd 100644 --- a/docs/test.md +++ b/docs/test.md @@ -1,9 +1,15 @@ +--- +title: Test +redirect_to: https://developer.adobe.com/commerce/testing/functional-testing-framework/test/ +status: migrated +--- + # Test Test cases in the Magento Functional Testing Framework (MFTF) are defined in XML as [`<tests>`]. -`<tests>` is a [Codeception test container][Codeception] that contains multiple individual tests with test metadata and before and after actions. +`<tests>` is a [Codeception test container][Codeception] that contains individual test [`<test>`] with its metadata ([`<annotations>`]), before ([`<before>`]) and after ([`<after>`]) section. -MFTF `<tests>` is considered a sequence of actions with associated parameters. +MFTF `<test>` is considered a sequence of actions with associated parameters. Any failed [assertion] within a test constitutes a failed test. <div class="bs-callout bs-callout-info" markdown="1"> @@ -18,7 +24,7 @@ The following diagram shows the structure of an MFTF test case: ## Format -The format of `<tests>` is: +The format of a test XML file is: ```xml <?xml version="1.0" encoding="UTF-8"?> @@ -44,28 +50,23 @@ The format of `<tests>` is: The following conventions apply to MFTF tests: -* All names within the framework are in the CamelCase format. -* `<test>` name must be alphanumeric. -* Each action and action group has its own identifier `<stepKey>` for reference purposes. +* One `<test>` tag is allowed per test XML file. +* All names within the framework are in the **PascalCase** format and must be alphanumeric. +* Each action and action group call should have its own identifier `<stepKey>`. * A test may have any number of [assertions][assertion] at any point within the `<test>`. -* If `<test>` is included in `<suite>`, it **cannot be generated in isolation** to the rest of the contents of the suite (see [suites] for details). - -Multiple `<test>` tags per XML file can make it hard to find and organize tags. -To simplify, we generate one `test.php` file per `<test>` tag provided, though we support both single and multiple `<test>` tags per XML file. +* If `<test>` is included in [`<suite>`][suites], it **cannot be generated in isolation** from `<before>` and `<after>` section of the suite (see [suites] for details). ## Elements reference -There are several XML elements that are used in `<tests>` in the MFTF. +There are several XML elements that are used within `<test>` in the MFTF. ### tests {#tests-tag} -`<tests>` is a container for multiple tests. It is a group of test methods that define test flows within a test case. - -`<tests>` must contain at least one [`<test>`]. +`<tests>` is a container for test and must contain exactly one [`<test>`]. ### test {#test-tag} -`<test>` is a set of steps, including [actions] and [assertions][assertion]. It is a sequence of test steps that define test flow within a test method. +`<test>` is a set of steps, including [actions], [assertions][assertion] and Action Group calls. It is a sequence of test steps that define test flow within a test method. Attribute|Type|Use|Description ---|---|---|--- @@ -73,6 +74,7 @@ Attribute|Type|Use|Description `remove`|boolean|optional|Set `true` to remove the test when merging. `insertBefore`|string|optional| This option is used for [merging]. It enables you to add all test actions contained in the original test into a test with the same name BEFORE the test step with `stepKey` that you assigned in `insertBefore`. `insertAfter`|string|optional| Set `stepKey` of the test step after which you want to insert the test when [merging]. +`deprecated`|string|optional|Used to warn about the future deprecation of the test. String will appear in Allure reports and console output at runtime. `extends`|string|optional|A name of the parent test to [extend]. `<test>` may also contain [`<annotations>`], [`<before>`], [`<after>`], any [action][actions], or [`<actionGroup>`]. @@ -86,21 +88,20 @@ Allure annotations provide metadata for reporting. ### before {#before-tag} -`<before>` wraps the steps to perform before the [`<test>`]. +`<before>` wraps the steps that are preconditions for the [`<test>`]. For example: Change configuration, create Customer Account, Create Category and Product. `<before>` may contain these child elements: -* Any [`<action>`][actions] +* Any [Action][actions] * [`<actionGroup>`] ### after {#after-tag} -`<after>` wraps the steps to perform after the [`<test>`]. -The steps are run in both successful **and** failed test runs. +`<after>` wraps the steps to perform after the [`<test>`]. The steps are run in both successful **and** failed test runs. The goal of this section is to perform cleanup (revert the environment to the pre-test state). `<after>` may contain: -* Any [`<action>`][actions] +* Any [Action][actions] * [`<actionGroup>`] ### actionGroup {#actiongroup-tag} diff --git a/docs/test/action-groups.md b/docs/test/action-groups.md index 0743481f6..0950b6cd8 100644 --- a/docs/test/action-groups.md +++ b/docs/test/action-groups.md @@ -1,3 +1,9 @@ +--- +title: Action groups +redirect_to: https://developer.adobe.com/commerce/testing/functional-testing-framework/test/action-groups/ +status: migrated +--- + # Action groups In the MFTF, you can re-use a group of [actions][], such as logging in as an administrator or a customer, declared in an XML file when you need to perform the same sequence of actions multiple times. @@ -8,10 +14,14 @@ The following diagram shows the structure of an MFTF action group: ## Principles +{% raw %} + The following conventions apply to MFTF action groups: -- All action groups are declared in XML files and stored in the `<module>/ActionGroup/` directory. -- Every file name ends with `ActionGroup`, such as `LoginToAdminActionGroup`. +- All action groups are declared in XML files and stored in the `<module>/Test/Mftf/ActionGroup/` directory. +- Every file name ends with `ActionGroup` suffix. For exampe `LoginAsAdminActionGroup.xml`. +- Action group name should be the same as file name without extension. +- One `<actionGroup>` tag is allowed per action group XML file. The XML format for the `actionGroups` declaration is: @@ -34,32 +44,31 @@ The XML format for the `actionGroups` declaration is: These examples build a declaration for a group of actions that grant authorization to the Admin area, and use the declaration in a test. -The _Backend/ActionGroup/LoginToAdminActionGroup.xml_ `<actionGroup>` relates to the functionality of the _Backend_ module. -In [test][], the name and identifier of the `<actionGroup>` is used as a reference in the `ref` parameter, such as `ref="LoginToAdminActionGroup"`. +The _Magento/Backend/Test/Mftf/ActionGroup/LoginAsAdminActionGroup.xml_ `<actionGroup>` relates to the functionality of the _Magento_Backend_ module. + +In [test][], the name and identifier of the `<actionGroup>` is used as a reference in the `ref` parameter, such as `ref="LoginAsAdminActionGroup"`. ### Create an action group declaration To create the `<actionGroup>` declaration: -1. Begin with a _Backend/ActionGroup/LoginToAdminActionGroup.xml_ template for the `<actionGroup>`: +1. Begin with a template for the `<actionGroup>`: ```xml <?xml version="1.0" encoding="UTF-8"?> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> - <actionGroup name="LoginToAdminActionGroup"> - ... + <actionGroup name="{Action Group Name}"> + </actionGroup> </actionGroups> ``` -<!-- {% raw %} --> - 1. Add actions to the `actionGroup` arguments: ```xml - <actionGroup name="LoginToAdminActionGroup"> + <actionGroup name="LoginAsAdminActionGroup"> <fillField stepKey="fillUsername" selector="#username" userInput="{{adminUser.username}}" /> <fillField stepKey="fillPassword" selector="#password" userInput="{{adminUser.password}}" /> <click stepKey="click" selector="#login" /> @@ -81,14 +90,20 @@ To create the `<actionGroup>` declaration: <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> - <actionGroup name="LoginToAdminActionGroup"> - <arguments> - <argument name="adminUser" defaultValue="_defaultAdmin"/> - </arguments> - <fillField stepKey="fillUsername" selector="#username" userInput="{{adminUser.username}}" /> - <fillField stepKey="fillPassword" selector="#password" userInput="{{adminUser.password}}" /> - <click stepKey="click" selector="#login" /> - </actionGroup> + <actionGroup name="LoginAsAdmin"> + <annotations> + <description>Login to Backend Admin using provided User Data. PLEASE NOTE: This Action Group does NOT validate that you are Logged In.</description> + </annotations> + <arguments> + <argument name="adminUser" type="entity" defaultValue="DefaultAdminUser"/> + </arguments> + + <amOnPage url="{{AdminLoginPage.url}}" stepKey="navigateToAdmin"/> + <fillField selector="{{AdminLoginFormSection.username}}" userInput="{{adminUser.username}}" stepKey="fillUsername"/> + <fillField selector="{{AdminLoginFormSection.password}}" userInput="{{adminUser.password}}" stepKey="fillPassword"/> + <click selector="{{AdminLoginFormSection.signIn}}" stepKey="clickLogin"/> + <closeAdminNotification stepKey="closeAdminNotification"/> + </actionGroup> </actionGroups> ``` @@ -97,23 +112,23 @@ To create the `<actionGroup>` declaration: In this test example, we want to add the following set of actions: ```xml -<fillField stepKey="fillUsername" selector="#username" userInput="{{CustomAdminUser.username}}" /> -<fillField stepKey="fillPassword" selector="#password" userInput="{{CustomAdminUser.password}}" /> -<click stepKey="click" selector="#login" /> +<fillField selector="{{AdminLoginFormSection.username}}" userInput="{{adminUser.username}}" stepKey="fillUsername"/> +<fillField selector="{{AdminLoginFormSection.password}}" userInput="{{adminUser.password}}" stepKey="fillPassword"/> +<click selector="{{AdminLoginFormSection.signIn}}" stepKey="clickLogin"/> ``` -Instead of adding this set of actions, use the _LoginToAdminActionGroup_ `<actionGroup>` declaration in tests: +Instead of adding this set of actions, use the _LoginAsAdminActionGroup_ `<actionGroup>` declaration in tests: -1. Reference the `LoginToAdminActionGroup` action group: +1. Reference the `LoginAsAdminActionGroup` action group: ```xml - <actionGroup stepKey="loginToAdminPanel" ref="LoginToAdminActionGroup"/> + <actionGroup stepKey="loginToAdminPanel" ref="LoginAsAdminActionGroup"/> ``` 1. Update the argument name/value pair to `adminUser` and `CustomAdminUser`: ```xml - <actionGroup stepKey="loginToAdminPanel" ref="LoginToAdminActionGroup"> + <actionGroup stepKey="loginToAdminPanel" ref="LoginAsAdminActionGroup"> <argument name="adminUser" value="CustomAdminUser"/> </actionGroup> ``` @@ -171,6 +186,34 @@ MFTF resolves `{{myCustomEntity.field1}}` the same as it would in a `selector` o </actionGroup> ``` +## Return a value + +Action groups can return a value using a `return` tag. + +```xml +<actionGroup name="GetOrderIdActionGroup"> + <seeElement selector="{{CheckoutSuccessMainSection.orderLink}}" stepKey="assertOrderLink"/> + <grabTextFrom selector="{{CheckoutSuccessMainSection.orderLink}}" stepKey="orderId"/> + <return value="{$orderId}" stepKey="returnOrderId"/> +</actionGroup> +``` + +The value returned can be accessed in later steps using action group step key `{$getOrderId}`. +```xml +<actionGroup ref="GetOrderIdActionGroup" stepKey="getOrderId"/> +<!--Filter the Order using Order ID --> +<actionGroup ref="FilterOrderGridByIdActionGroup" stepKey="filterOrderGridById"> + <argument name="orderId" value="{$getOrderId}"/> +</actionGroup> +``` +### Convention to return a value + +The following conventions apply to action groups returning a value: +- Only action groups can return value. Use of `return` tag is dis-allowed in tests and suites. +- An action group does not support multiple `return` tags. +- For [merging action groups](../merging.md#merge-action-groups), `return` is allowed only in one of the merging action groups. +- Value returned by an action group can only be referenced within the scope that the action group is defined in (`test`, `before/after`). + ## Optimizing action group structures Structuring properly an action group increases code reusability and readability. @@ -196,30 +239,34 @@ Starting with an action group such as: ``` It can be reworked into more manageable pieces, as below. These smaller steps are easier to read, update, and reuse. - -```xml -<actionGroup name="GoToCategoryGridAndAddNewCategory"> - <seeInCurrentUrl url="{{AdminCategoryPage.url}}" stepKey="seeOnCategoryPage"/> - <click selector="{{AdminCategorySidebarActionSection.AddSubcategoryButton}}" stepKey="clickOnAddSubCategory"/> - <see selector="{{AdminHeaderSection.pageTitle}}" userInput="New Category" stepKey="seeCategoryPageTitle"/> -</actionGroup> - -<actionGroup name="FillInBasicCategoryFields"> - <arguments> - <argument name="categoryEntity" defaultValue="_defaultCategory"/> - </arguments> - <fillField selector="{{AdminCategoryBasicFieldSection.CategoryNameInput}}" userInput="{{categoryEntity.name}}" stepKey="enterCategoryName"/> - <click selector="{{AdminCategorySEOSection.SectionHeader}}" stepKey="openSEO"/> - <fillField selector="{{AdminCategorySEOSection.UrlKeyInput}}" userInput="{{categoryEntity.name_lwr}}" stepKey="enterURLKey"/> -</actionGroup> - -<actionGroup name="SaveAndVerifyCategoryCreation"> - <click selector="{{AdminCategoryMainActionsSection.SaveButton}}" stepKey="saveCategory"/> - <seeElement selector="{{AdminCategoryMessagesSection.SuccessMessage}}" stepKey="assertSuccess"/> - <seeInTitle userInput="{{categoryEntity.name}}" stepKey="seeNewCategoryPageTitle"/> - <seeElement selector="{{AdminCategorySidebarTreeSection.categoryInTree(categoryEntity.name)}}" stepKey="seeCategoryInTree"/> -</actionGroup> -``` +* GoToCategoryGridAndAddNewCategory + ```xml + <actionGroup name="GoToCategoryGridAndAddNewCategory"> + <seeInCurrentUrl url="{{AdminCategoryPage.url}}" stepKey="seeOnCategoryPage"/> + <click selector="{{AdminCategorySidebarActionSection.AddSubcategoryButton}}" stepKey="clickOnAddSubCategory"/> + <see selector="{{AdminHeaderSection.pageTitle}}" userInput="New Category" stepKey="seeCategoryPageTitle"/> + </actionGroup> + ``` +* FillInBasicCategoryFields + ```xml + <actionGroup name="FillInBasicCategoryFields"> + <arguments> + <argument name="categoryEntity" defaultValue="_defaultCategory"/> + </arguments> + <fillField selector="{{AdminCategoryBasicFieldSection.CategoryNameInput}}" userInput="{{categoryEntity.name}}" stepKey="enterCategoryName"/> + <click selector="{{AdminCategorySEOSection.SectionHeader}}" stepKey="openSEO"/> + <fillField selector="{{AdminCategorySEOSection.UrlKeyInput}}" userInput="{{categoryEntity.name_lwr}}" stepKey="enterURLKey"/> + </actionGroup> + ``` +* SaveAndVerifyCategoryCreation + ```xml + <actionGroup name="SaveAndVerifyCategoryCreation"> + <click selector="{{AdminCategoryMainActionsSection.SaveButton}}" stepKey="saveCategory"/> + <seeElement selector="{{AdminCategoryMessagesSection.SuccessMessage}}" stepKey="assertSuccess"/> + <seeInTitle userInput="{{categoryEntity.name}}" stepKey="seeNewCategoryPageTitle"/> + <seeElement selector="{{AdminCategorySidebarTreeSection.categoryInTree(categoryEntity.name)}}" stepKey="seeCategoryInTree"/> + </actionGroup> + ``` <!-- {% endraw %} --> @@ -242,6 +289,7 @@ Attribute|Type|Use|Description ---|---|---|--- `name`|string|required|Identifier of the action group. `extends`|string|optional|Identifies the action group to extend. +`deprecated`|string|optional|Used to warn about the future deprecation of the actionGroup. String will appear in Allure reports and console output at runtime. It may contain `<arguments>`. @@ -261,4 +309,4 @@ Attribute|Type|Use|Description [actions]: ./actions.md [test]: ../test.md [`argument`]: #argument-tag -[created]: ../data.md#persist-data \ No newline at end of file +[created]: ../data.md#persist-data diff --git a/docs/test/actions.md b/docs/test/actions.md index 9b125d7fc..3c63d504f 100644 --- a/docs/test/actions.md +++ b/docs/test/actions.md @@ -1,7 +1,13 @@ +--- +title: Test actions +redirect_to: https://developer.adobe.com/commerce/testing/functional-testing-framework/test/actions/ +status: migrated +--- + # Test actions Actions in the MFTF allow you to automate different scenarios of Magento user's actions. -They are mostly XML implementations of [Codeception actions](http://codeception.com/docs/modules/WebDriver#Actions). +They are mostly XML implementations of [Codeception actions](https://codeception.com/docs/modules/WebDriver#Actions). Some actions drive browser elements, while others use REST APIs. ## Common attributes @@ -22,13 +28,13 @@ This step can be referenced within the test using `conditionalClickStep1`. The value format should met the following principles: -* Must be unique within [`<test>`](../test.md#test-tag). -* Naming should be as descriptive as possible: - * Describe the action performed. - * Briefly describe the purpose. - * Describe which data is in use. -* Should be in camelCase with lowercase first letter. -* Should be the last attribute of an element. +* Must be unique within [`<test>`](../test.md#test-tag). +* Naming should be as descriptive as possible: + * Describe the action performed. + * Briefly describe the purpose. + * Describe which data is in use. +* Should be in camelCase with lowercase first letter. +* Should be the last attribute of an element. ### `before` and `after` @@ -95,7 +101,7 @@ Here, `url` contains a pointer to a `url` attribute of the `StorefrontCustomerSi ### 2. Enter a customer's email {#example-step2} ```xml -<fillField userInput="$$customer.email$$" selector="{{StorefrontCustomerSignInFormSection.emailField}}" stepKey="fillEmail"/> +<fillField userInput="$customer.email$" selector="{{StorefrontCustomerSignInFormSection.emailField}}" stepKey="fillEmail"/> ``` [`<fillField>`](#fillfield) fills a text field with the given string. @@ -122,7 +128,7 @@ This section is declared in `.../Customer/Section/StorefrontCustomerSignInFormSe ### 3. Enter a customer's password {#example-step3} ```xml -<fillField userInput="$$customer.password$$" selector="{{StorefrontCustomerSignInFormSection.passwordField}}" stepKey="fillPassword"/> +<fillField userInput="$customer.password$" selector="{{StorefrontCustomerSignInFormSection.passwordField}}" stepKey="fillPassword"/> ``` This `<action>` is very similar to the `<action>` in a previous step. @@ -142,14 +148,17 @@ Here, [`<click>`](#click) performs a click on a button that can be found by the The following test actions return a variable: -* [grabAttributeFrom](#grabattributefrom) -* [grabCookie](#grabcookie) -* [grabFromCurrentUrl](#grabfromcurrenturl) -* [grabMultiple](#grabmultiple) -* [grabPageSource](#grabpagesource) -* [grabTextFrom](#grabtextfrom) -* [grabValueFrom](#grabvaluefrom) -* [executeJS](#executejs) +* [grabAttributeFrom](#grabattributefrom) +* [grabCookie](#grabcookie) +* [grabCookieAttributes](#grabcookieattributes) +* [grabFromCurrentUrl](#grabfromcurrenturl) +* [grabMultiple](#grabmultiple) +* [grabPageSource](#grabpagesource) +* [grabTextFrom](#grabtextfrom) +* [grabValueFrom](#grabvaluefrom) +* [executeJS](#executejs) +* [getOTP](#getotp) +* [return](#return) Learn more in [Using data returned by test actions](../data.md#use-data-returned-by-test-actions). @@ -157,10 +166,10 @@ Learn more in [Using data returned by test actions](../data.md#use-data-returned The following test actions handle data entities using [metadata](../metadata.md): -* [createData](#createdata) -* [deleteData](#deletedata) -* [updateData](#updatedata) -* [getData](#getdata) +* [createData](#createdata) +* [deleteData](#deletedata) +* [updateData](#updatedata) +* [getData](#getdata) Learn more in [Handling a REST API response](../metadata.md#rest-response). @@ -185,7 +194,7 @@ If the description of an element does not include a link to Codeception analogue Accepts the current popup visible on the page. -See [acceptPopup docs on codeception.com](http://codeception.com/docs/modules/WebDriver#acceptPopup). +See [acceptPopup docs on codeception.com](https://codeception.com/docs/modules/WebDriver#acceptPopup). Attribute|Type|Use|Description ---|---|---|--- @@ -204,7 +213,7 @@ Attribute|Type|Use|Description Opens the page by the URL relative to the one set in the `MAGENTO_BASE_URL` configuration variable. -See [amOnPage docs on codeception.com](http://codeception.com/docs/modules/WebDriver#amOnPage). +See [amOnPage docs on codeception.com](https://codeception.com/docs/modules/WebDriver#amOnPage). Attribute|Type|Use|Description ---|---|---|--- @@ -217,14 +226,14 @@ Attribute|Type|Use|Description ```xml <!-- Open the `(baseURL)/admin` page. --> -<amOnPage url="/admin" stepKey="goToLogoutPage"/> +<amOnPage url="{{AdminLogoutPage.url}}" stepKey="goToLogoutPage"/> ``` ### amOnSubdomain Takes the base URL and changes the subdomain. -See [amOnSubdomain docs on codeception.com](http://codeception.com/docs/modules/WebDriver#amOnSubdomain). +See [amOnSubdomain docs on codeception.com](https://codeception.com/docs/modules/WebDriver#amOnSubdomain). Attribute|Type|Use|Description ---|---|---|--- @@ -248,7 +257,7 @@ Pre-condition: the current base URL is `https://www.magento.com`. Opens a page by the absolute URL. -See [amOnUrl docs on codeception.com](http://codeception.com/docs/modules/WebDriver#amOnUrl). +See [amOnUrl docs on codeception.com](https://codeception.com/docs/modules/WebDriver#amOnUrl). Attribute|Type|Use|Description ---|---|---|--- @@ -266,7 +275,7 @@ Attribute|Type|Use|Description ### appendField -See [appendField docs on codeception.com](http://codeception.com/docs/modules/WebDriver#appendField). +See [appendField docs on codeception.com](https://codeception.com/docs/modules/WebDriver#appendField). Attribute|Type|Use|Description ---|---|---|--- @@ -285,7 +294,7 @@ Attribute|Type|Use|Description ### attachFile -See [attachFile docs on codeception.com](http://codeception.com/docs/modules/WebDriver#attachFile). +See [attachFile docs on codeception.com](https://codeception.com/docs/modules/WebDriver#attachFile). Attribute|Type|Use|Description ---|---|---|--- @@ -304,7 +313,7 @@ Attribute|Type|Use|Description ### cancelPopup -See [cancelPopup docs on codeception.com](http://codeception.com/docs/modules/WebDriver#cancelPopup). +See [cancelPopup docs on codeception.com](https://codeception.com/docs/modules/WebDriver#cancelPopup). Attribute|Type|Use|Description ---|---|---|--- @@ -321,7 +330,7 @@ Attribute|Type|Use|Description ### checkOption -See [checkOption docs on codeception.com](http://codeception.com/docs/modules/WebDriver#checkOption). +See [checkOption docs on codeception.com](https://codeception.com/docs/modules/WebDriver#checkOption). Attribute|Type|Use|Description ---|---|---|--- @@ -358,12 +367,12 @@ Attribute|Type|Use|Description ### click -See [click docs on codeception.com](http://codeception.com/docs/modules/WebDriver#click). +See [click docs on codeception.com](https://codeception.com/docs/modules/WebDriver#click). Attribute|Type|Use|Description ---|---|---|--- `selector`|string|optional| The selector identifying the corresponding HTML element. -`selectorArray`|string|optional| Selects an element as a key value array. See [strict locator](http://codeception.com/docs/modules/WebDriver#locating-elements). +`selectorArray`|string|optional| Selects an element as a key value array. See [strict locator](https://codeception.com/docs/modules/WebDriver#locating-elements). `userInput`|string|optional| Data to be sent with the click. `stepKey`|string|required| A unique identifier of the action. `before`|string|optional| `stepKey` of action that must be executed next. @@ -383,7 +392,7 @@ Attribute|Type|Use|Description ### clickWithLeftButton -See [clickWithLeftButton docs on codeception.com](http://codeception.com/docs/modules/WebDriver#clickWithLeftButton). +See [clickWithLeftButton docs on codeception.com](https://codeception.com/docs/modules/WebDriver#clickWithLeftButton). Attribute|Type|Use|Description ---|---|---|--- @@ -414,7 +423,7 @@ Attribute|Type|Use|Description ### clickWithRightButton -See [clickWithRightButton docs on codeception.com](http://codeception.com/docs/modules/WebDriver#clickWithRightButton). +See [clickWithRightButton docs on codeception.com](https://codeception.com/docs/modules/WebDriver#clickWithRightButton). Attribute|Type|Use|Description ---|---|---|--- @@ -462,7 +471,7 @@ Attribute|Type|Use|Description ### closeTab -See [closeTab docs on codeception.com](http://codeception.com/docs/modules/WebDriver#closeTab). +See [closeTab docs on codeception.com](https://codeception.com/docs/modules/WebDriver#closeTab). Attribute|Type|Use|Description ---|---|---|--- @@ -620,7 +629,7 @@ Delete an entity using [REST API](https://devdocs.magento.com/redoc/2.3/) reques ### dontSee -See [the codeception.com documentation for more information about this action](http://codeception.com/docs/modules/WebDriver#dontSee). +See [the codeception.com documentation for more information about this action](https://codeception.com/docs/modules/WebDriver#dontSee). Attribute|Type|Use|Description ---|---|---|--- @@ -640,7 +649,7 @@ Attribute|Type|Use|Description ### dontSeeCheckboxIsChecked -See [dontSeeCheckboxIsChecked docs on codeception.com](http://codeception.com/docs/modules/WebDriver#dontSeeCheckboxIsChecked). +See [dontSeeCheckboxIsChecked docs on codeception.com](https://codeception.com/docs/modules/WebDriver#dontSeeCheckboxIsChecked). Attribute|Type|Use|Description ---|---|---|--- @@ -658,7 +667,7 @@ Attribute|Type|Use|Description ### dontSeeCookie -See [dontSeeCookie docs on codeception.com](http://codeception.com/docs/modules/WebDriver#dontSeeCookie). +See [dontSeeCookie docs on codeception.com](https://codeception.com/docs/modules/WebDriver#dontSeeCookie). Attribute|Type|Use|Description ---|---|---|--- @@ -682,7 +691,7 @@ Attribute|Type|Use|Description ### dontSeeCurrentUrlEquals -See [dontSeeCurrentUrlEquals docs on codeception.com](http://codeception.com/docs/modules/WebDriver#dontSeeCurrentUrlEquals). +See [dontSeeCurrentUrlEquals docs on codeception.com](https://codeception.com/docs/modules/WebDriver#dontSeeCurrentUrlEquals). Attribute|Type|Use|Description ---|---|---|--- @@ -700,7 +709,7 @@ Attribute|Type|Use|Description ### dontSeeCurrentUrlMatches -See [dontSeeCurrentUrlMatches docs on codeception.com](http://codeception.com/docs/modules/WebDriver#dontSeeCurrentUrlMatches) +See [dontSeeCurrentUrlMatches docs on codeception.com](https://codeception.com/docs/modules/WebDriver#dontSeeCurrentUrlMatches) Attribute|Type|Use|Description ---|---|---|--- @@ -718,7 +727,7 @@ Attribute|Type|Use|Description ### dontSeeElement -See [dontSeeElement docs on codeception.com](http://codeception.com/docs/modules/WebDriver#dontSeeElement). +See [dontSeeElement docs on codeception.com](https://codeception.com/docs/modules/WebDriver#dontSeeElement). Attribute|Type|Use|Description ---|---|---|--- @@ -737,7 +746,7 @@ Attribute|Type|Use|Description ### dontSeeElementInDOM -See [dontSeeElementInDOM docs on codeception.com](http://codeception.com/docs/modules/WebDriver#dontSeeElementInDOM). +See [dontSeeElementInDOM docs on codeception.com](https://codeception.com/docs/modules/WebDriver#dontSeeElementInDOM). Attribute|Type|Use|Description ---|---|---|--- @@ -756,7 +765,7 @@ Attribute|Type|Use|Description ### dontSeeInCurrentUrl -See [dontSeeInCurrentUrl docs on codeception.com](http://codeception.com/docs/modules/WebDriver#dontSeeInCurrentUrl). +See [dontSeeInCurrentUrl docs on codeception.com](https://codeception.com/docs/modules/WebDriver#dontSeeInCurrentUrl). Attribute|Type|Use|Description ---|---|---|--- @@ -774,7 +783,7 @@ Attribute|Type|Use|Description ### dontSeeInField -See [dontSeeInField docs on codeception.com](http://codeception.com/docs/modules/WebDriver#dontSeeInField). +See [dontSeeInField docs on codeception.com](https://codeception.com/docs/modules/WebDriver#dontSeeInField). Attribute|Type|Use|Description ---|---|---|--- @@ -794,7 +803,7 @@ Attribute|Type|Use|Description ### dontSeeInFormFields -See [dontSeeInFormFields docs on codeception.com](http://codeception.com/docs/modules/WebDriver#dontSeeInFormFields). +See [dontSeeInFormFields docs on codeception.com](https://codeception.com/docs/modules/WebDriver#dontSeeInFormFields). Attribute|Type|Use|Description ---|---|---|--- @@ -813,7 +822,7 @@ Attribute|Type|Use|Description ### dontSeeInPageSource -See [dontSeeInPageSource docs on codeception.com](http://codeception.com/docs/modules/WebDriver#dontSeeInPageSource). +See [dontSeeInPageSource docs on codeception.com](https://codeception.com/docs/modules/WebDriver#dontSeeInPageSource). Attribute|Type|Use|Description ---|---|---|--- @@ -831,7 +840,7 @@ Attribute|Type|Use|Description ### dontSeeInSource -See [dontSeeInSource docs on codeception.com](http://codeception.com/docs/modules/WebDriver#dontSeeInSource). +See [dontSeeInSource docs on codeception.com](https://codeception.com/docs/modules/WebDriver#dontSeeInSource). Attribute|Type|Use|Description ---|---|---|--- @@ -851,7 +860,7 @@ You must encode the `html` using a tool such as [CyberChef](https://gchq.github. ### dontSeeInTitle -See [dontSeeInTitle docs on codeception.com](http://codeception.com/docs/modules/WebDriver#dontSeeInTitle). +See [dontSeeInTitle docs on codeception.com](https://codeception.com/docs/modules/WebDriver#dontSeeInTitle). Attribute|Type|Use|Description ---|---|---|--- @@ -886,7 +895,7 @@ Attribute|Type|Use|Description ### dontSeeLink -See [dontSeeLink docs on codeception.com](http://codeception.com/docs/modules/WebDriver#dontSeeLink). +See [dontSeeLink docs on codeception.com](https://codeception.com/docs/modules/WebDriver#dontSeeLink). Attribute|Type|Use|Description ---|---|---|--- @@ -910,7 +919,7 @@ Attribute|Type|Use|Description ### dontSeeOptionIsSelected -See [dontSeeOptionIsSelected docs on codeception.com](http://codeception.com/docs/modules/WebDriver#dontSeeOptionIsSelected). +See [dontSeeOptionIsSelected docs on codeception.com](https://codeception.com/docs/modules/WebDriver#dontSeeOptionIsSelected). Attribute|Type|Use|Description ---|---|---|--- @@ -929,7 +938,7 @@ Attribute|Type|Use|Description ### doubleClick -See [doubleClick docs on codeception.com](http://codeception.com/docs/modules/WebDriver#doubleClick). +See [doubleClick docs on codeception.com](https://codeception.com/docs/modules/WebDriver#doubleClick). Attribute|Type|Use|Description ---|---|---|--- @@ -947,7 +956,7 @@ Attribute|Type|Use|Description ### dragAndDrop -See [dragAndDrop docs on codeception.com](http://codeception.com/docs/modules/WebDriver#dragAndDrop). +See [dragAndDrop docs on codeception.com](https://codeception.com/docs/modules/WebDriver#dragAndDrop). Attribute|Type|Use|Description ---|---|---|--- @@ -971,27 +980,28 @@ Attribute|Type|Use|Description <dragAndDrop selector1="#block1" selector2="#block2" x="50" y="50" stepKey="dragAndDrop"/> ``` -### executeInSelenium +### rapidClick -See [executeInSelenium docs on codeception.com](http://codeception.com/docs/modules/WebDriver#executeInSelenium). +See [rapidClick docs on codeception.com](https://codeception.com/docs/modules/WebDriver#rapidClick). -Attribute|Type|Use|Description ----|---|---|--- -`function`|string|optional| Name of Selenium function to run. -`stepKey`|string|required| A unique identifier of the action. -`before`|string|optional| `stepKey` of action that must be executed next. -`after`|string|optional| `stepKey` of preceding action. +| Attribute | Type | Use | Description | +|------------|--------|----------|-------------------------------------------------| +| `selector` | string | optional | A selector for the HTML element to rapid click. | +| `count` | string | required | Click count. | +| `stepKey` | string | required | A unique identifier of the action. | +| `before` | string | optional | `stepKey` of action that must be executed next. | +| `after` | string | optional | `stepKey` of preceding action. | -#### Example +#### Examples ```xml -<!-- Execute the Selenium function `function(\Facebook\WebDriver\Remote\RemoteWebDriver $webdriver) {$webdriver->get('http://google.com');}`. --> -<executeInSelenium function="function(\Facebook\WebDriver\Remote\RemoteWebDriver $webdriver) {$webdriver->get('http://google.com');}" stepKey="executeInSelenium"/> +<!-- Rapid click the selected element as per given count number --> +<rapidClick selector="#selector" count="50" stepKey="rapidClick"/> ``` ### executeJS -See [executeJS docs on codeception.com](http://codeception.com/docs/modules/WebDriver#executeJS). +See [executeJS docs on codeception.com](https://codeception.com/docs/modules/WebDriver#executeJS). Attribute|Type|Use|Description ---|---|---|--- @@ -1012,7 +1022,7 @@ To access this value you would use `{$returnTime}` in later actions. ### fillField -See [fillField docs on codeception.com](http://codeception.com/docs/modules/WebDriver#fillField). +See [fillField docs on codeception.com](https://codeception.com/docs/modules/WebDriver#fillField). Attribute|Type|Use|Description ---|---|---|--- @@ -1030,12 +1040,15 @@ Attribute|Type|Use|Description <fillField userInput="Sample text" selector="input#myfield" stepKey="fillField"/> ``` -### formatMoney +### formatCurrency +Format input to specified currency according to the locale specified. Returns formatted string for test use. +Use NumberFormatter::formatCurrency(), see https://www.php.net/manual/en/numberformatter.formatcurrency.php Attribute|Type|Use|Description ---|---|---|--- -`userInput`|string|optional| Value for the money form field. -`locale`|string|optional| The PHP locale value for the store. +`userInput`|string|required| Number to be formatted. +`locale`|string|required| The locale to format to. +`currency`|string|required| The 3-letter ISO 4217 currency code indicating the currency to use. `stepKey`|string|required| A unique identifier of the action. `before`|string|optional| `stepKey` of action that must be executed next. `after`|string|optional| `stepKey` of preceding action. @@ -1087,9 +1100,28 @@ The `ProductAttributeOptionGetter` entity must be defined in the corresponding [ This action can optionally contain one or more [requiredEntity](#requiredentity) child elements. +### getOTP + +Generate a one-time password (OTP) based on a saved `secret` at path `magento/tfa/OTP_SHARED_SECRET` in a MFTF credential storage. +The one-time password (OTP) is returned and accessible through the stepkey. + +MFTF use TOTP from [Spomky-Labs/otphp](https://github.com/Spomky-Labs/otphp), if you want to learn more about this action. + +Attribute|Type|Use|Description +---|---|---|--- +`stepKey`|string|required| A unique identifier of the action. +`before`|string|optional| `stepKey` of action that must be executed next. +`after`|string|optional| `stepKey` of preceding action. + +#### Example + +```xml +<getOTP stepKey="getOtp"/> +``` + ### grabAttributeFrom -See [grabAttributeFrom docs on codeception.com](http://codeception.com/docs/modules/WebDriver#grabAttributeFrom). +See [grabAttributeFrom docs on codeception.com](https://codeception.com/docs/modules/WebDriver#grabAttributeFrom). Attribute|Type|Use|Description ---|---|---|--- @@ -1109,7 +1141,7 @@ To access this value, use `{$grabAttributeFromInput}` in later actions. --> ### grabCookie -See [grabCookie docs on codeception.com](http://codeception.com/docs/modules/WebDriver#grabCookie). +See [grabCookie docs on codeception.com](https://codeception.com/docs/modules/WebDriver#grabCookie). Attribute|Type|Use|Description ---|---|---|--- @@ -1133,9 +1165,37 @@ To access this value, use `{$grabCookieExampleDomain}` in later actions. --> <grabCookie userInput="cookie1" parameterArray="['domainName' => '.example.com']" stepKey="grabCookieExampleDomain"/> ``` +### grabCookieAttributes + +See [grabCookieAttributes docs on codeception.com](https://codeception.com/docs/modules/WebDriver#grabCookieAttributes). + +Attribute|Type|Use|Description +---|---|---|--- +`userInput`|string|optional| Name of the cookie to grab. +`parameterArray`|string|optional| Array of cookie parameters to grab. +`stepKey`|string|required| A unique identifier of the action. +`before`|string|optional| `stepKey` of action that must be executed next. +`after`|string|optional| `stepKey` of preceding action. + +#### Examples + +```xml +<!-- Grab the cookie attributes with the given name `cookie1`. +To access these values, use `{$grabCookie1}` in later actions. --> +<grabCookieAttributes userInput="cookie1" stepKey="grabCookie1"/> +``` + +```xml +<!-- Grab the cookie attributes with the given name `cookie1` from the domain `www.example.com`. +To access these values, use `{$grabCookieExampleDomain}` in later actions. +To access expiry date, use `{$grabCookieExampleDomain.expiry}` in later actions. +--> +<grabCookieAttributes userInput="cookie1" parameterArray="['domainName' => '.example.com']" stepKey="grabCookieExampleDomain"/> +``` + ### grabFromCurrentUrl -See [grabFromCurrentUrl docs on codeception.com](http://codeception.com/docs/modules/WebDriver#grabFromCurrentUrl).. +See [grabFromCurrentUrl docs on codeception.com](https://codeception.com/docs/modules/WebDriver#grabFromCurrentUrl).. Attribute|Type|Use|Description ---|---|---|--- @@ -1154,7 +1214,7 @@ To access this value, use `{$grabFromCurrentUrl}` in later actions. --> ### grabMultiple -See [grabMultiple docs on codeception.com](http://codeception.com/docs/modules/WebDriver#grabMultiple).. +See [grabMultiple docs on codeception.com](https://codeception.com/docs/modules/WebDriver#grabMultiple).. Attribute|Type|Use|Description ---|---|---|--- @@ -1180,7 +1240,7 @@ To access this value, use `{$grabAllLinks}` in later actions. --> ### grabPageSource -See [grabPageSource docs on codeception.com](http://codeception.com/docs/modules/WebDriver#grabPageSource). +See [grabPageSource docs on codeception.com](https://codeception.com/docs/modules/WebDriver#grabPageSource). Attribute|Type|Use|Description ---|---|---|--- @@ -1198,7 +1258,7 @@ To access this value, use `{$grabPageSource}` in later actions. --> ### grabTextFrom -See [grabTextFrom docs on codeception.com](http://codeception.com/docs/modules/WebDriver#grabTextFrom). +See [grabTextFrom docs on codeception.com](https://codeception.com/docs/modules/WebDriver#grabTextFrom). Attribute|Type|Use|Description ---|---|---|--- @@ -1235,9 +1295,25 @@ To access this value, use `{$grabInputName}` in later actions. --> <grabValueFrom selector="input#name" stepKey="grabInputName"/> ``` +### return + +Specifies what value is returned by an action group. The value can be then accessed in later steps using the action group stepKey. See [Action groups returning a value](./action-groups.md#return-a-value) for usage information. + +Attribute|Type|Use|Description +---|---|---|--- +`value`|string|required| value returned by action group. +`stepKey`|string|required| A unique identifier of the action. + +#### Example + +```xml +<!-- Returns value of $grabInputName to the calling --> +<return value="{$grabInputName}" stepKey="returnInputName"/> +``` + ### loadSessionSnapshot -See [loadSessionSnapshot docs on codeception.com](http://codeception.com/docs/modules/WebDriver#loadSessionSnapshot). +See [loadSessionSnapshot docs on codeception.com](https://codeception.com/docs/modules/WebDriver#loadSessionSnapshot). Attribute|Type|Use|Description ---|---|---|--- @@ -1262,6 +1338,7 @@ Attribute|Type|Use|Description ---|---|---|--- `command`|string |optional| CLI command to be executed in Magento environment. `arguments`|string |optional| Unescaped arguments to be passed in with the CLI command. +`timeout`|string|optional| Number of seconds CLI command can run without outputting anything. `stepKey`|string|required| A unique identifier of the action. `before`|string|optional| `stepKey` of action that must be executed next. `after`|string|optional| `stepKey` of preceding action. @@ -1273,9 +1350,31 @@ Attribute|Type|Use|Description <magentoCLI command="indexer:reindex" stepKey="reindex"/> ``` +### magentoCron + +Used to execute Magento Cron jobs. Groups may be provided optionally. Internal mechanism of `<magentoCron>` ensures that Cron Job of single group is ran with 60 seconds interval. + +Attribute|Type|Use|Description +---|---|---|--- +`groups`|string |optional| Run only specified groups of Cron Jobs +`arguments`|string |optional| Unescaped arguments to be passed in with the CLI command. +`timeout`|string|optional| Number of seconds CLI command can run without outputting anything. +`stepKey`|string|required| A unique identifier of the action. +`before`|string|optional| `stepKey` of action that must be executed next. +`after`|string|optional| `stepKey` of preceding action. + +#### Example +```xml +<magentoCron stepKey="runStagingCronJobs" groups="staging"/> +<!-- No interval here --> +<magentoCron stepKey="runIndexCronJobs" groups="index"/> +<!-- 60 seconds interval takes place here --> +<magentoCron stepKey="runAllCronJobs"/> +``` + ### makeScreenshot -See [makeScreenshot docs on codeception.com](http://codeception.com/docs/modules/WebDriver#makeScreenshot). +See [makeScreenshot docs on codeception.com](https://codeception.com/docs/modules/WebDriver#makeScreenshot). Attribute|Type|Use|Description ---|---|---|--- @@ -1298,7 +1397,7 @@ This action does not add a screenshot to the Allure [report](../reporting.md).</ ### maximizeWindow -See [maximizeWindow docs on codeception.com](http://codeception.com/docs/modules/WebDriver#maximizeWindow). +See [maximizeWindow docs on codeception.com](https://codeception.com/docs/modules/WebDriver#maximizeWindow). Attribute|Type|Use|Description ---|---|---|--- @@ -1315,7 +1414,7 @@ Attribute|Type|Use|Description ### moveBack -See [moveBack docs on codeception.com](http://codeception.com/docs/modules/WebDriver#moveBack). +See [moveBack docs on codeception.com](https://codeception.com/docs/modules/WebDriver#moveBack). Attribute|Type|Use|Description ---|---|---|--- @@ -1332,7 +1431,7 @@ Attribute|Type|Use|Description ### moveForward -See [moveForward docs on codeception.com](http://codeception.com/docs/modules/WebDriver#moveForward).. +See [moveForward docs on codeception.com](https://codeception.com/docs/modules/WebDriver#moveForward).. Attribute|Type|Use|Description ---|---|---|--- @@ -1347,7 +1446,7 @@ Attribute|Type|Use|Description ### moveMouseOver -See [moveMouseOver docs on codeception.com](http://codeception.com/docs/modules/WebDriver#moveMouseOver). +See [moveMouseOver docs on codeception.com](https://codeception.com/docs/modules/WebDriver#moveMouseOver). Attribute|Type|Use|Description ---|---|---|--- @@ -1391,7 +1490,7 @@ Attribute|Type|Use|Description ### openNewTab -See [openNewTab docs on codeception.com](http://codeception.com/docs/modules/WebDriver#openNewTab). +See [openNewTab docs on codeception.com](https://codeception.com/docs/modules/WebDriver#openNewTab). Attribute|Type|Use|Description ---|---|---|--- @@ -1417,9 +1516,9 @@ Attribute|Type|Use|Description `before`|string|optional| `stepKey` of action that must be executed next. `after`|string|optional| `stepKey` of preceding action. -### pauseExecution +### pause -See [pauseExecution docs on codeception.com](http://codeception.com/docs/modules/WebDriver#pauseExecution). +See usage of `<pause` in [interactive-pause](../interactive-pause.md) and [pause docs on codeception.com](https://codeception.com/docs/02-GettingStarted#Interactive-Pause). Attribute|Type|Use|Description ---|---|---|--- @@ -1431,24 +1530,12 @@ Attribute|Type|Use|Description ```xml <!-- Halt test execution until the `enter` key is pressed to continue. --> -<pauseExecution stepKey="pause"/> +<pause stepKey="pause"/> ``` -### performOn - -See [performOn docs on codeception.com](http://codeception.com/docs/modules/WebDriver#performOn). - -Attribute|Type|Use|Description ----|---|---|--- -`selector`|string|optional| The selector identifying the corresponding HTML element. -`function`|string|optional| Function or actions to be taken on the selected element. -`stepKey`|string|required| A unique identifier of the action. -`before`|string|optional| `stepKey` of action that must be executed next. -`after`|string|optional| `stepKey` of preceding action. - ### pressKey -See [pressKey docs on codeception.com](http://codeception.com/docs/modules/WebDriver#pressKey). +See [pressKey docs on codeception.com](https://codeception.com/docs/modules/WebDriver#pressKey). Attribute|Type|Use|Description ---|---|---|--- @@ -1476,7 +1563,7 @@ To press more than one key at a time, wrap the keys in secondary `[]`. ### reloadPage -See [reloadPage docs on codeception.com](http://codeception.com/docs/modules/WebDriver#reloadPage). +See [reloadPage docs on codeception.com](https://codeception.com/docs/modules/WebDriver#reloadPage). Attribute|Type|Use|Description ---|---|---|--- @@ -1508,7 +1595,7 @@ Attribute|Type|Use|Description ### resetCookie -See [resetCookie docs on codeception.com](http://codeception.com/docs/modules/WebDriver#resetCookie). +See [resetCookie docs on codeception.com](https://codeception.com/docs/modules/WebDriver#resetCookie). Attribute|Type|Use|Description ---|---|---|--- @@ -1532,7 +1619,7 @@ Attribute|Type|Use|Description ### resizeWindow -See [resizeWindow docs on codeception.com](http://codeception.com/docs/modules/WebDriver#resizeWindow). +See [resizeWindow docs on codeception.com](https://codeception.com/docs/modules/WebDriver#resizeWindow). Attribute|Type|Use|Description ---|---|---|--- @@ -1551,7 +1638,7 @@ Attribute|Type|Use|Description ### saveSessionSnapshot -See [saveSessionSnapshot docs on codeception.com](http://codeception.com/docs/modules/WebDriver#saveSessionSnapshot). +See [saveSessionSnapshot docs on codeception.com](https://codeception.com/docs/modules/WebDriver#saveSessionSnapshot). Attribute|Type|Use|Description ---|---|---|--- @@ -1569,7 +1656,7 @@ Attribute|Type|Use|Description ### scrollTo -See [scrollTo docs on codeception.com](http://codeception.com/docs/modules/WebDriver#scrollTo). +See [scrollTo docs on codeception.com](https://codeception.com/docs/modules/WebDriver#scrollTo). Attribute|Type|Use|Description ---|---|---|--- @@ -1642,7 +1729,7 @@ On this test step the MFTF: ### see -See [see docs on codeception.com](http://codeception.com/docs/modules/WebDriver#see). +See [see docs on codeception.com](https://codeception.com/docs/modules/WebDriver#see). Attribute|Type|Use|Description ---|---|---|--- @@ -1662,7 +1749,7 @@ Attribute|Type|Use|Description ### seeCheckboxIsChecked -See [seeCheckboxIsChecked docs on codeception.com](http://codeception.com/docs/modules/WebDriver#seeCheckboxIsChecked). +See [seeCheckboxIsChecked docs on codeception.com](https://codeception.com/docs/modules/WebDriver#seeCheckboxIsChecked). Attribute|Type|Use|Description ---|---|---|--- @@ -1680,7 +1767,7 @@ Attribute|Type|Use|Description ### seeCookie -See [seeCookie docs on codeception.com](http://codeception.com/docs/modules/WebDriver#seeCookie). +See [seeCookie docs on codeception.com](https://codeception.com/docs/modules/WebDriver#seeCookie). Attribute|Type|Use|Description ---|---|---|--- @@ -1704,7 +1791,7 @@ Attribute|Type|Use|Description ### seeCurrentUrlEquals -See [seeCurrentUrlEquals docs on codeception.com](http://codeception.com/docs/modules/WebDriver#seeCurrentUrlEquals). +See [seeCurrentUrlEquals docs on codeception.com](https://codeception.com/docs/modules/WebDriver#seeCurrentUrlEquals). Attribute|Type|Use|Description ---|---|---|--- @@ -1722,7 +1809,7 @@ Attribute|Type|Use|Description ### seeCurrentUrlMatches -See [seeCurrentUrlMatches docs on codeception.com](http://codeception.com/docs/modules/WebDriver#seeCurrentUrlMatches). +See [seeCurrentUrlMatches docs on codeception.com](https://codeception.com/docs/modules/WebDriver#seeCurrentUrlMatches). Attribute|Type|Use|Description ---|---|---|--- @@ -1740,7 +1827,7 @@ Attribute|Type|Use|Description ### seeElement -See [seeElement docs on codeception.com](http://codeception.com/docs/modules/WebDriver#seeElement). +See [seeElement docs on codeception.com](https://codeception.com/docs/modules/WebDriver#seeElement). Attribute|Type|Use|Description ---|---|---|--- @@ -1760,7 +1847,7 @@ Attribute|Type|Use|Description ### seeElementInDOM -See [seeElementInDOM docs on codeception.com](http://codeception.com/docs/modules/WebDriver#seeElementInDOM). +See [seeElementInDOM docs on codeception.com](https://codeception.com/docs/modules/WebDriver#seeElementInDOM). Attribute|Type|Use|Description ---|---|---|--- @@ -1779,7 +1866,7 @@ Attribute|Type|Use|Description ### seeInCurrentUrl -See [seeInCurrentUrl docs on codeception.com](http://codeception.com/docs/modules/WebDriver#seeInCurrentUrl). +See [seeInCurrentUrl docs on codeception.com](https://codeception.com/docs/modules/WebDriver#seeInCurrentUrl). Attribute|Type|Use|Description ---|---|---|--- @@ -1797,7 +1884,7 @@ Attribute|Type|Use|Description ### seeInField -See [seeInField docs on codeception.com](http://codeception.com/docs/modules/WebDriver#seeInField). +See [seeInField docs on codeception.com](https://codeception.com/docs/modules/WebDriver#seeInField). Attribute|Type|Use|Description ---|---|---|--- @@ -1817,7 +1904,7 @@ Attribute|Type|Use|Description ### seeInFormFields -See [seeInFormFields docs on codeception.com](http://codeception.com/docs/modules/WebDriver#seeInFormFields). +See [seeInFormFields docs on codeception.com](https://codeception.com/docs/modules/WebDriver#seeInFormFields). Attribute|Type|Use|Description ---|---|---|--- @@ -1836,7 +1923,7 @@ Attribute|Type|Use|Description ### seeInPageSource -See [seeInPageSource docs on codeception.com](http://codeception.com/docs/modules/WebDriver#seeInPageSource). +See [seeInPageSource docs on codeception.com](https://codeception.com/docs/modules/WebDriver#seeInPageSource). Attribute|Type|Use|Description ---|---|---|--- @@ -1856,7 +1943,7 @@ You must encode the `html` using a tool such as [CyberChef](https://gchq.github. ### seeInPopup -See [seeInPopup docs on codeception.com](http://codeception.com/docs/modules/WebDriver#seeInPopup). +See [seeInPopup docs on codeception.com](https://codeception.com/docs/modules/WebDriver#seeInPopup). Attribute|Type|Use|Description ---|---|---|--- @@ -1874,7 +1961,7 @@ Attribute|Type|Use|Description ### seeInSource -See [seeInSource docs on codeception.com](http://codeception.com/docs/modules/WebDriver#seeInSource). +See [seeInSource docs on codeception.com](https://codeception.com/docs/modules/WebDriver#seeInSource). Attribute|Type|Use|Description ---|---|---|--- @@ -1894,7 +1981,7 @@ You must encode the `html` using a tool such as [CyberChef](https://gchq.github. ### seeInTitle -See [seeInTitle docs on codeception.com](http://codeception.com/docs/modules/WebDriver#seeInTitle). +See [seeInTitle docs on codeception.com](https://codeception.com/docs/modules/WebDriver#seeInTitle). Attribute|Type|Use|Description ---|---|---|--- @@ -1912,7 +1999,7 @@ Attribute|Type|Use|Description ### seeLink -See [seeLink docs on codeception.com](http://codeception.com/docs/modules/WebDriver#seeLink). +See [seeLink docs on codeception.com](https://codeception.com/docs/modules/WebDriver#seeLink). Attribute|Type|Use|Description ---|---|---|--- @@ -1936,7 +2023,7 @@ Attribute|Type|Use|Description ### seeNumberOfElements -See [seeNumberOfElements docs on codeception.com](http://codeception.com/docs/modules/WebDriver#seeNumberOfElements). +See [seeNumberOfElements docs on codeception.com](https://codeception.com/docs/modules/WebDriver#seeNumberOfElements). Attribute|Type|Use|Description ---|---|---|--- @@ -1950,18 +2037,18 @@ Attribute|Type|Use|Description #### Examples ```xml -<!-- Verify there are 10 `<div id="product" ... >...</div>` elements on the page. --> +<!-- Verify there are 10 `<div class="product" ... >...</div>` elements on the page. --> <seeNumberOfElements userInput="10" selector="div.product" stepKey="seeTenProducts"/> ``` ```xml -<!-- Verify there are between 5 and 10 `<div id="product" ... >...</div>` elements on the page. --> -<seeNumberOfElements userInput="[5, 10]" selector=".product" stepKey="seeFiveToTenProducts"/> +<!-- Verify there are between 5 and 10 `<div class="product" ... >...</div>` elements on the page. --> +<seeNumberOfElements parameterArray="[5, 10]" selector="div.product" stepKey="seeFiveToTenProducts"/> ``` ### seeOptionIsSelected -See [seeOptionIsSelected docs on codeception.com](http://codeception.com/docs/modules/WebDriver#seeOptionIsSelected). +See [seeOptionIsSelected docs on codeception.com](https://codeception.com/docs/modules/WebDriver#seeOptionIsSelected). Attribute|Type|Use|Description ---|---|---|--- @@ -1980,7 +2067,7 @@ Attribute|Type|Use|Description ### selectOption -See [selectOption docs on codeception.com](http://codeception.com/docs/modules/WebDriver#selectOption). +See [selectOption docs on codeception.com](https://codeception.com/docs/modules/WebDriver#selectOption). Attribute|Type|Use|Description ---|---|---|--- @@ -2023,7 +2110,7 @@ It contains a child element `<array>` where you specify the options that must be ### setCookie -See [setCookie docs on codeception.com](http://codeception.com/docs/modules/WebDriver#setCookie). +See [setCookie docs on codeception.com](https://codeception.com/docs/modules/WebDriver#setCookie). Attribute|Type|Use|Description ---|---|---|--- @@ -2043,7 +2130,7 @@ Attribute|Type|Use|Description ### submitForm -See [submitForm docs on codeception.com](http://codeception.com/docs/modules/WebDriver#submitForm). +See [submitForm docs on codeception.com](https://codeception.com/docs/modules/WebDriver#submitForm). Attribute|Type|Use|Description ---|---|---|--- @@ -2063,7 +2150,7 @@ Attribute|Type|Use|Description ### switchToIFrame -See [switchToIFrame docs on codeception.com](http://codeception.com/docs/modules/WebDriver#switchToIFrame). +See [switchToIFrame docs on codeception.com](https://codeception.com/docs/modules/WebDriver#switchToIFrame). Attribute|Type|Use|Description ---|---|---|--- @@ -2082,7 +2169,7 @@ Attribute|Type|Use|Description ### switchToNextTab -See [switchToNextTab docs on codeception.com](http://codeception.com/docs/modules/WebDriver#switchToNextTab). +See [switchToNextTab docs on codeception.com](https://codeception.com/docs/modules/WebDriver#switchToNextTab). Attribute|Type|Use|Description ---|---|---|--- @@ -2105,7 +2192,7 @@ Attribute|Type|Use|Description ### switchToPreviousTab -See [switchToPreviousTab docs on codeception.com](http://codeception.com/docs/modules/WebDriver#switchToPreviousTab). +See [switchToPreviousTab docs on codeception.com](https://codeception.com/docs/modules/WebDriver#switchToPreviousTab). Attribute|Type|Use|Description ---|---|---|--- @@ -2128,7 +2215,7 @@ Attribute|Type|Use|Description ### switchToWindow -See [switchToWindow docs on codeception.com](http://codeception.com/docs/modules/WebDriver#switchToWindow). +See [switchToWindow docs on codeception.com](https://codeception.com/docs/modules/WebDriver#switchToWindow). Attribute|Type|Use|Description ---|---|---|--- @@ -2146,7 +2233,7 @@ Attribute|Type|Use|Description ### typeInPopup -See [typeInPopup docs on codeception.com](http://codeception.com/docs/modules/WebDriver#typeInPopup). +See [typeInPopup docs on codeception.com](https://codeception.com/docs/modules/WebDriver#typeInPopup). Attribute|Type|Use|Description ---|---|---|--- @@ -2164,7 +2251,7 @@ Attribute|Type|Use|Description ### uncheckOption -See [uncheckOption docs on codeception.com](http://codeception.com/docs/modules/WebDriver#uncheckOption). +See [uncheckOption docs on codeception.com](https://codeception.com/docs/modules/WebDriver#uncheckOption). Attribute|Type|Use|Description ---|---|---|--- @@ -2182,7 +2269,7 @@ Attribute|Type|Use|Description ### unselectOption -See [unselectOption docs on codeception.com](http://codeception.com/docs/modules/WebDriver#unselectOption). +See [unselectOption docs on codeception.com](https://codeception.com/docs/modules/WebDriver#unselectOption). Attribute|Type|Use|Description ---|---|---|--- @@ -2234,7 +2321,7 @@ This action can optionally contain one or more [requiredEntity](#requiredentity) ### wait -See [wait docs on codeception.com](http://codeception.com/docs/modules/WebDriver#wait). +See [wait docs on codeception.com](https://codeception.com/docs/modules/WebDriver#wait). Attribute|Type|Use|Description ---|---|---|--- @@ -2270,7 +2357,7 @@ Attribute|Type|Use|Description ### waitForElementChange -See [waitForElementChange docs on codeception.com](http://codeception.com/docs/modules/WebDriver#waitForElementChange). +See [waitForElementChange docs on codeception.com](https://codeception.com/docs/modules/WebDriver#waitForElementChange). Attribute|Type|Use|Description ---|---|---|--- @@ -2290,7 +2377,7 @@ Attribute|Type|Use|Description ### waitForElement -See [waitForElement docs on codeception.com](http://codeception.com/docs/modules/WebDriver#waitForElement). +See [waitForElement docs on codeception.com](https://codeception.com/docs/modules/WebDriver#waitForElement). Attribute|Type|Use|Description ---|---|---|--- @@ -2309,7 +2396,7 @@ Attribute|Type|Use|Description ### waitForElementNotVisible -See [waitForElementNotVisible docs on codeception.com](http://codeception.com/docs/modules/WebDriver#waitForElementNotVisible). +See [waitForElementNotVisible docs on codeception.com](https://codeception.com/docs/modules/WebDriver#waitForElementNotVisible). Attribute|Type|Use|Description ---|---|---|--- @@ -2328,7 +2415,7 @@ Attribute|Type|Use|Description ### waitForElementVisible -See [waitForElementVisible docs on codeception.com](http://codeception.com/docs/modules/WebDriver#waitForElementVisible). +See [waitForElementVisible docs on codeception.com](https://codeception.com/docs/modules/WebDriver#waitForElementVisible). Attribute|Type|Use|Description ---|---|---|--- @@ -2341,13 +2428,31 @@ Attribute|Type|Use|Description #### Example ```xml -<!-- Wait up to 30 seconds for `<div id="changedElement" ... >...</div>` to become visible on the page before continuing. --> <waitForElementVisible selector="#changedElement" stepKey="waitForElementVisible"/> ``` +### waitForElementClickable + +See [waitForElementClickable docs on codeception.com](https://codeception.com/docs/modules/WebDriver#waitForElementClickable). + +Attribute|Type|Use|Description +---|---|---|--- +`selector`|string|optional| The selector identifying the corresponding HTML element. +`time`|string|optional| The number of seconds to wait for the element to appear. +`stepKey`|string|required| A unique identifier of the action. +`before`|string|optional| `stepKey` of action that must be executed next. +`after`|string|optional| `stepKey` of preceding action. + +#### Example + +```xml +<!-- Waits up to $timeout seconds for the given element to be clickable. If element doesn't become clickable, a timeout exception is thrown. --> +<waitForElementClickable selector="#changedElement" stepKey="waitForElementClickable"/> +``` + ### waitForJS -See [waitForJS docs on codeception.com](http://codeception.com/docs/modules/WebDriver#waitForJS). +See [waitForJS docs on codeception.com](https://codeception.com/docs/modules/WebDriver#waitForJS). Attribute|Type|Use|Description ---|---|---|--- @@ -2453,7 +2558,7 @@ Attribute|Type|Use|Description ### waitForText -See [waitForText docs on codeception.com](http://codeception.com/docs/modules/WebDriver#waitForText). +See [waitForText docs on codeception.com](https://codeception.com/docs/modules/WebDriver#waitForText). Attribute|Type|Use|Description ---|---|---|--- diff --git a/docs/test/annotations.md b/docs/test/annotations.md index f30b8104e..c51e09923 100644 --- a/docs/test/annotations.md +++ b/docs/test/annotations.md @@ -1,3 +1,9 @@ +--- +title: Annotations +redirect_to: https://developer.adobe.com/commerce/testing/functional-testing-framework/test/annotations/ +status: migrated +--- + # Annotations @@ -87,7 +93,7 @@ Add `<skip>` to the test to skip it during test run. Attribute|Type|Use|Definition ---|---|---|--- -`value`|string|required|A value that is used to group tests. It should be lower case. `skip` is reserved to ignore content of the test and generate an empty test. +`value`|string|required|A value that is used to group tests. It should be lower case. #### Example @@ -112,11 +118,11 @@ Attribute|Type|Use ### severity -The `<return>` element is an implementation of a [`@Severity`] Allure tag; Metadata for report. +The `<severity>` element is an implementation of the [`@Severity`] Allure annotation, which is used to prioritise tests by severity. Attribute|Type|Use|Acceptable values ---|---|---|--- -`value`|string|required|`MINOR`, `AVERAGE`, `MAJOR`, `BLOCKER`, `CRITICAL` +`value`|string|required|`MINOR`, `AVERAGE`, `MAJOR`, `CRITICAL`, `BLOCKER` #### Example @@ -124,6 +130,16 @@ Attribute|Type|Use|Acceptable values <severity value="CRITICAL"/> ``` +#### Usage guidelines + +Severity Level|Usage +---|--- +`BLOCKER`|If this test fails, the customer is completely blocked from purchasing a product. +`CRITICAL`|This is a serious problem impacting conversion, or affecting the operation of the store. +`MAJOR`|Store conversion rate is reduced owing to this issue. For example, something is broken or missing that impacts checkout frequency or cart volume. +`AVERAGE`|A fault on the storefront that can negatively impact conversion rate (like UI errors or omissions), or problems with Magento admin functionality. +`MINOR`|An application or configuration fault that has no impact on conversion rate. + ### skip Use the `<skip>` element to skip a test. @@ -213,14 +229,14 @@ Attribute|Type|Use <!-- Link definitions --> -[`@Description`]: https://devhub.io/zh/repos/allure-framework-allure-phpunit#extended-test-class-or-test-method-description -[`@Features`]: https://devhub.io/zh/repos/allure-framework-allure-phpunit#map-test-classes-and-test-methods-to-features-and-stories -[`@group`]: http://codeception.com/docs/07-AdvancedUsage#Groups -[`@return`]: http://codeception.com/docs/07-AdvancedUsage#Examples -[`@Severity`]: https://devhub.io/zh/repos/allure-framework-allure-phpunit#set-test-severity -[`@Stories`]: https://devhub.io/zh/repos/allure-framework-allure-phpunit#map-test-classes-and-test-methods-to-features-and-stories +[`@Description`]: https://github.com/allure-framework/allure-phpunit#extended-test-class-or-test-method-description +[`@Features`]: https://github.com/allure-framework/allure-phpunit#map-test-classes-and-test-methods-to-features-and-stories +[`@group`]: https://codeception.com/docs/07-AdvancedUsage#Groups +[`@return`]: https://codeception.com/docs/07-AdvancedUsage#Examples +[`@Severity`]: https://github.com/allure-framework/allure-phpunit#set-test-severity +[`@Stories`]: https://github.com/allure-framework/allure-phpunit#map-test-classes-and-test-methods-to-features-and-stories [`@TestCaseId`]: https://github.com/allure-framework/allure1/wiki/Test-Case-ID -[`@Title`]: https://devhub.io/zh/repos/allure-framework-allure-phpunit#human-readable-test-class-or-test-method-title +[`@Title`]: https://github.com/allure-framework/allure-phpunit#human-readable-test-class-or-test-method-title [description]: #description [features]: #features [group]: #group diff --git a/docs/test/assertions.md b/docs/test/assertions.md index 17a3d194a..dedca2e74 100644 --- a/docs/test/assertions.md +++ b/docs/test/assertions.md @@ -1,5 +1,10 @@ -# Assertions +--- +title: Assertions +redirect_to: https://developer.adobe.com/commerce/testing/functional-testing-framework/test/assertions/ +status: migrated +--- +# Assertions Assertions serve to pass or fail the [test](../test.md#test-tag) if a condition is not met. These assertions will look familiar to you if you've used any other testing framework, like PHPUnit. @@ -31,6 +36,8 @@ To use variables embedded in a string in `expected` and `actual` of your asserti `actual="A long assert string {$stepKeyOfGrab} with an embedded variable reference." actualType="variable"` +In case of `assertContains` actions, `<expectedResult>` is the needle and `<actualResult>` is the haystack. + ## Example The following example shows a common test that gets text from a page and asserts that it matches what we expect to see. If it does not, the test will fail at the assert step. @@ -50,17 +57,18 @@ The following example shows a common test that gets text from a page and asserts ### assertElementContainsAttribute +The `<assertElementContainsAttribute>` asserts that the selected html element contains and matches the expected value for the given attribute. + Example: ```xml -<assertElementContainsAttribute selector=".admin__menu-overlay" attribute="style" expectedValue="color: #333;" stepKey="assertElementContainsAttribute"/> +<assertElementContainsAttribute stepKey="assertElementContainsAttribute"> + <expectedResult selector=".admin__menu-overlay" attribute="style" type="string">color: #333;</expectedResult> +</assertElementContainsAttribute> ``` Attribute|Type|Use|Description ---|---|---|--- -`selector`|string|required| -`expectedValue`|string|optional| A value of the expected result. -`attribute`|string|required| `stepKey`|string|required| A unique identifier of the text step. `before`|string|optional| `stepKey` of action that must be executed next. `after`|string|optional| `stepKey` of the preceding action. @@ -89,14 +97,10 @@ It must be in typical array format like `[1,2,3,4,5]` or `[alpha, brontosaurus, ### assertArrayHasKey -See [assertArrayHasKey docs on codeception.com](http://codeception.com/docs/modules/Asserts#assertArrayHasKey) +See [assertArrayHasKey docs on codeception.com](https://codeception.com/docs/modules/Asserts#assertArrayHasKey) Attribute|Type|Use|Description ---|---|---|--- -`expected`|string|required| A value of the expected result. -`expectedType`|string|optional| A type of the expected result. Possible values: `const` (default), `int`, `float`, `bool`, `string`, `variable`, `array`. -`actual`|string|required| A value of the actual result. -`actualType`|string|optional| A type of the actual result. Possible values: `const` (default), `int`, `float`, `bool`, `string`, `variable`, `array`. `message`|string|optional|Text of informational message about a cause of failure. `stepKey`|string|required| A unique identifier of the text step. `before`|string|optional| `stepKey` of action that must be executed next. @@ -104,60 +108,72 @@ Attribute|Type|Use|Description ### assertArrayNotHasKey -See [assertArrayNotHasKey docs on codeception.com](http://codeception.com/docs/modules/Asserts#assertArrayNotHasKey). +See [assertArrayNotHasKey docs on codeception.com](https://codeception.com/docs/modules/Asserts#assertArrayNotHasKey). Attribute|Type|Use|Description ---|---|---|--- -`expected`|string|required| A value of the expected result. -`expectedType`|string|optional| A type of the expected result. Possible values: `const` (default), `int`, `float`, `bool`, `string`, `variable`, `array`. -`actual`|string|required| A value of the actual result. -`actualType`|string|optional| A type of the actual result. Possible values: `const` (default), `int`, `float`, `bool`, `string`, `variable`, `array`. `message`|string|optional|Text of informational message about a cause of failure. `stepKey`|string|required| A unique identifier of the text step. `before`|string|optional| `stepKey` of action that must be executed next. `after`|string|optional| `stepKey` of the preceding action. -### assertArraySubset +### assertContains -See [assertArraySubset docs on codeception.com](http://codeception.com/docs/modules/Asserts#assertArraySubset). +See [assertContains docs on codeception.com](https://codeception.com/docs/modules/Asserts#assertContains). Attribute|Type|Use|Description ---|---|---|--- -`expected`|string|required| A value of the expected result. -`expectedType`|string|optional| A type of the expected result. Possible values: `const` (default), `int`, `float`, `bool`, `string`, `variable`, `array`. -`actual`|string|required| A value of the actual result. -`actualType`|string|optional| A type of the actual result. Possible values: `const` (default), `int`, `float`, `bool`, `string`, `variable`, `array`. -`strict`|boolean|optional| `message`|string|optional|Text of informational message about a cause of failure. `stepKey`|string|required| A unique identifier of the text step. `before`|string|optional| `stepKey` of action that must be executed next. `after`|string|optional| `stepKey` of the preceding action. -### assertContains +### assertStringContainsString -See [assertContains docs on codeception.com](http://codeception.com/docs/modules/Asserts#assertContains). +See [assertStringContainsString docs on codeception.com](https://codeception.com/docs/modules/Asserts#assertStringContainsString). + +Example: + +```xml +<assertStringContainsString stepKey="assertDropDownTierPriceTextProduct1"> + <expectedResult type="string">Buy 5 for $5.00 each and save 50%</expectedResult> + <actualResult type="variable">DropDownTierPriceTextProduct1</actualResult> +</assertStringContainsString> +``` Attribute|Type|Use|Description ---|---|---|--- -`expected`|string|required| A value of the expected result. -`expectedType`|string|optional| A type of the expected result. Possible values: `const` (default), `int`, `float`, `bool`, `string`, `variable`, `array`. -`actual`|string|required| A value of the actual result. -`actualType`|string|optional| A type of the actual result. Possible values: `const` (default), `int`, `float`, `bool`, `string`, `variable`, `array`. -`message`|string|optional|Text of informational message about a cause of failure. +`message`|string|optional|Text describing the cause of the failure. +`stepKey`|string|required| Unique identifier of the text step. +`before`|string|optional| `stepKey` of the action that must be executed next. +`after`|string|optional| `stepKey` of the preceding action. + +### assertStringContainsStringIgnoringCase + +See [assertStringContainsStringIgnoringCase docs on codeception.com](https://codeception.com/docs/modules/Asserts#assertStringContainsStringIgnoringCase). + +Example: + +```xml +<assertStringContainsStringIgnoringCase stepKey="verifyContentType"> + <actualResult type="variable">grabContentType</actualResult> + <expectedResult type="string">{{image.extension}}</expectedResult> +</assertStringContainsStringIgnoringCase> +``` + +Attribute|Type|Use|Description +---|---|---|--- +`message`|string|optional|Message describing the cause of failure. `stepKey`|string|required| A unique identifier of the text step. -`before`|string|optional| `stepKey` of action that must be executed next. +`before`|string|optional| `stepKey` of the action that must be executed next. `after`|string|optional| `stepKey` of the preceding action. ### assertCount -See [assertCount docs on codeception.com](http://codeception.com/docs/modules/Asserts#assertCount). +See [assertCount docs on codeception.com](https://codeception.com/docs/modules/Asserts#assertCount). Attribute|Type|Use|Description ---|---|---|--- -`expected`|string|required| A value of the expected result. -`expectedType`|string|optional| A type of the expected result. Possible values: `const` (default), `int`, `float`, `bool`, `string`, `variable`, `array`. -`actual`|string|required| A value of the actual result. -`actualType`|string|optional| A type of the actual result. Possible values: `const` (default), `int`, `float`, `bool`, `string`, `variable`, `array`. `message`|string|optional|Text of informational message about a cause of failure. `stepKey`|string|required| A unique identifier of the text step. `before`|string|optional| `stepKey` of action that must be executed next. @@ -165,12 +181,18 @@ Attribute|Type|Use|Description ### assertEmpty -See [assertEmpty docs on codeception.com](http://codeception.com/docs/modules/Asserts#assertEmpty). +See [assertEmpty docs on codeception.com](https://codeception.com/docs/modules/Asserts#assertEmpty). + +Example: + +```xml +<assertEmpty stepKey="assertSearchButtonEnabled"> + <actualResult type="string">$grabSearchButtonAttribute</actualResult> +</assertEmpty> +``` Attribute|Type|Use|Description ---|---|---|--- -`actual`|string|required| A value of the actual result. -`actualType`|string|optional| A type of the actual result. Possible values: `const` (default), `int`, `float`, `bool`, `string`, `variable`, `array`. `message`|string|optional|Text of informational message about a cause of failure. `stepKey`|string|required| A unique identifier of the text step. `before`|string|optional| `stepKey` of action that must be executed next. @@ -178,28 +200,64 @@ Attribute|Type|Use|Description ### assertEquals -See [assertEquals docs on codeception.com](http://codeception.com/docs/modules/Asserts#assertEquals). +See [assertEquals docs on codeception.com](https://codeception.com/docs/modules/Asserts#assertEquals). + +Example: + +```xml +<assertEquals message="ExpectedPrice" stepKey="assertBundleProductPrice"> + <actualResult type="variable">grabProductPrice</actualResult> + <expectedResult type="string">$75.00</expectedResult> +</assertEquals> +``` + +Attribute|Type|Use|Description +---|---|---|--- +`message`|string|optional|Text of informational message about a cause of failure. +`stepKey`|string|required| A unique identifier of the text step. +`before`|string|optional| `stepKey` of action that must be executed next. +`after`|string|optional| `stepKey` of the preceding action. + +### assertEqualsWithDelta + +See [assertEqualsWithDelta docs on codeception.com](https://codeception.com/docs/modules/Asserts#assertEqualsWithDelta). Attribute|Type|Use|Description ---|---|---|--- -`expected`|string|required| A value of the expected result. -`expectedType`|string|optional| A type of the expected result. Possible values: `const` (default), `int`, `float`, `bool`, `string`, `variable`, `array`. -`actual`|string|required| A value of the actual result. -`actualType`|string|optional| A type of the actual result. Possible values: `const` (default), `int`, `float`, `bool`, `string`, `variable`, `array`. `delta`|string|optional| `message`|string|optional|Text of informational message about a cause of failure. `stepKey`|string|required| A unique identifier of the text step. `before`|string|optional| `stepKey` of action that must be executed next. `after`|string|optional| `stepKey` of the preceding action. +### assertEqualsCanonicalizing + +See [assertEqualsCanonicalizing docs on codeception.com](https://codeception.com/docs/modules/Asserts#assertEqualsCanonicalizing). + +Attribute|Type|Use|Description +---|---|---|--- +`message`|string|optional|Text of informational message about a cause of failure. +`stepKey`|string|required| A unique identifier of the text step. +`before`|string|optional| `stepKey` of action that must be executed next. +`after`|string|optional| `stepKey` of the preceding action. + +### assertEqualsIgnoringCase + +See [assertEqualsIgnoringCase docs on codeception.com](https://codeception.com/docs/modules/Asserts#assertEqualsIgnoringCase). + +Attribute|Type|Use|Description +---|---|---|--- +`message`|string|optional|Text of informational message about a cause of failure. +`stepKey`|string|required| A unique identifier of the text step. +`before`|string|optional| `stepKey` of action that must be executed next. +`after`|string|optional| `stepKey` of the preceding action. + ### assertFalse -See [assertFalse docs on codeception.com](http://codeception.com/docs/modules/Asserts#assertFalse). +See [assertFalse docs on codeception.com](https://codeception.com/docs/modules/Asserts#assertFalse). Attribute|Type|Use|Description ---|---|---|--- -`actual`|string|required| Actual value. -`actualType`|assertEnum|optional| Type of actual value. `message`|string|optional|Text of informational message about a cause of failure. `stepKey`|string|required| A unique identifier of the text step. `before`|string|optional| `stepKey` of action that must be executed next. @@ -207,12 +265,10 @@ Attribute|Type|Use|Description ### assertFileExists -See [assertFileExists docs on codeception.com](http://codeception.com/docs/modules/Asserts#assertFileExists). +See [assertFileExists docs on codeception.com](https://codeception.com/docs/modules/Asserts#assertFileExists). Attribute|Type|Use|Description ---|---|---|--- -`actual`|string|required| A value of the actual result. -`actualType`|string|optional| A type of the actual result. Possible values: `const` (default), `int`, `float`, `bool`, `string`, `variable`, `array`. `message`|string|optional|Text of informational message about a cause of failure. `stepKey`|string|required| A unique identifier of the text step. `before`|string|optional| `stepKey` of action that must be executed next. @@ -220,12 +276,10 @@ Attribute|Type|Use|Description ### assertFileNotExists -See [assertFileNotExists docs on codeception.com](http://codeception.com/docs/modules/Asserts#assertFileNotExists). +See [assertFileNotExists docs on codeception.com](https://codeception.com/docs/modules/Asserts#assertFileNotExists). Attribute|Type|Use|Description ---|---|---|--- -`actual`|string|required| A value of the actual result. -`actualType`|string|optional| A type of the actual result. Possible values: `const` (default), `int`, `float`, `bool`, `string`, `variable`, `array`. `message`|string|optional|Text of informational message about a cause of failure. `stepKey`|string|required| A unique identifier of the text step. `before`|string|optional| `stepKey` of action that must be executed next. @@ -233,14 +287,19 @@ Attribute|Type|Use|Description ### assertGreaterOrEquals -See [assertGreaterOrEquals docs on codeception.com](http://codeception.com/docs/modules/Asserts#assertGreaterOrEquals). +See [assertGreaterOrEquals docs on codeception.com](https://codeception.com/docs/modules/Asserts#assertGreaterOrEquals). + +Example: + +```xml +<assertGreaterOrEquals stepKey="checkStatusSortOrderAsc" after="getOrderStatusSecondRow"> + <actualResult type="const">$getOrderStatusSecondRow</actualResult> + <expectedResult type="const">$getOrderStatusFirstRow</expectedResult> +</assertGreaterOrEquals> +``` Attribute|Type|Use|Description ---|---|---|--- -`expected`|string|required| A value of the expected result. -`expectedType`|string|optional| A type of the expected result. Possible values: `const` (default), `int`, `float`, `bool`, `string`, `variable`, `array`. -`actual`|string|required| A value of the actual result. -`actualType`|string|optional| A type of the actual result. Possible values: `const` (default), `int`, `float`, `bool`, `string`, `variable`, `array`. `message`|string|optional|Text of informational message about a cause of failure. `stepKey`|string|required| A unique identifier of the text step. `before`|string|optional| `stepKey` of action that must be executed next. @@ -248,14 +307,19 @@ Attribute|Type|Use|Description ### assertGreaterThan -See [assertGreaterThan docs on codeception.com](http://codeception.com/docs/modules/Asserts#assertGreaterThan). +See [assertGreaterThan docs on codeception.com](https://codeception.com/docs/modules/Asserts#assertGreaterThan). + +Example: + +```xml +<assertGreaterThan stepKey="checkQuantityWasChanged"> + <actualResult type="const">$grabEndQuantity</actualResult> + <expectedResult type="const">$grabStartQuantity</expectedResult> +</assertGreaterThan> +``` Attribute|Type|Use|Description ---|---|---|--- -`expected`|string|required| A value of the expected result. -`expectedType`|string|optional| A type of the expected result. Possible values: `const` (default), `int`, `float`, `bool`, `string`, `variable`, `array`. -`actual`|string|required| A value of the actual result. -`actualType`|string|optional| A type of the actual result. Possible values: `const` (default), `int`, `float`, `bool`, `string`, `variable`, `array`. `message`|string|optional|Text of informational message about a cause of failure. `stepKey`|string|required| A unique identifier of the text step. `before`|string|optional| `stepKey` of action that must be executed next. @@ -263,14 +327,19 @@ Attribute|Type|Use|Description ### assertGreaterThanOrEqual -See [assertGreaterThanOrEqual docs on codeception.com](http://codeception.com/docs/modules/Asserts#assertGreaterThanOrEqual). +See [assertGreaterThanOrEqual docs on codeception.com](https://codeception.com/docs/modules/Asserts#assertGreaterThanOrEqual). + +Example: + +```xml +<assertGreaterThanOrEqual stepKey="checkStatusSortOrderAsc" after="getOrderStatusSecondRow"> + <actualResult type="const">$getOrderStatusSecondRow</actualResult> + <expectedResult type="const">$getOrderStatusFirstRow</expectedResult> +</assertGreaterThanOrEqual> +``` Attribute|Type|Use|Description ---|---|---|--- -`expected`|string|required| A value of the expected result. -`expectedType`|string|optional| A type of the expected result. Possible values: `const` (default), `int`, `float`, `bool`, `string`, `variable`, `array`. -`actual`|string|required| A value of the actual result. -`actualType`|string|optional| A type of the actual result. Possible values: `const` (default), `int`, `float`, `bool`, `string`, `variable`, `array`. `message`|string|optional|Text of informational message about a cause of failure. `stepKey`|string|required| A unique identifier of the text step. `before`|string|optional| `stepKey` of action that must be executed next. @@ -278,102 +347,141 @@ Attribute|Type|Use|Description ### assertInstanceOf -See [assertInstanceOf docs on codeception.com](http://codeception.com/docs/modules/Asserts#assertInstanceOf). +See [assertInstanceOf docs on codeception.com](https://codeception.com/docs/modules/Asserts#assertInstanceOf). Attribute|Type|Use|Description ---|---|---|--- -`expected`|string|required| A value of the expected result. -`expectedType`|string|optional| A type of the expected result. Possible values: `const` (default), `int`, `float`, `bool`, `string`, `variable`, `array`. -`actual`|string|required| A value of the actual result. -`actualType`|string|optional| A type of the actual result. Possible values: `const` (default), `int`, `float`, `bool`, `string`, `variable`, `array`. `message`|string|optional|Text of informational message about a cause of failure. `stepKey`|string|required| A unique identifier of the text step. `before`|string|optional| `stepKey` of action that must be executed next. `after`|string|optional| `stepKey` of the preceding action. -### assertInternalType +### assertIsEmpty -See [assertInternalType docs on codeception.com](http://codeception.com/docs/modules/Asserts#assertInternalType). +See [assertIsEmpty docs on codeception.com](https://codeception.com/docs/modules/Asserts#assertIsEmpty). Attribute|Type|Use|Description ---|---|---|--- -`expected`|string|required| A value of the expected result. -`expectedType`|string|optional| A type of the expected result. Possible values: `const` (default), `int`, `float`, `bool`, `string`, `variable`, `array`. -`actual`|string|required| A value of the actual result. -`actualType`|string|optional| A type of the actual result. Possible values: `const` (default), `int`, `float`, `bool`, `string`, `variable`, `array`. `message`|string|optional|Text of informational message about a cause of failure. `stepKey`|string|required| A unique identifier of the text step. `before`|string|optional| `stepKey` of action that must be executed next. `after`|string|optional| `stepKey` of the preceding action. -### assertIsEmpty +### assertLessOrEquals + +See [assertLessOrEquals docs on codeception.com](https://codeception.com/docs/modules/Asserts#assertLessOrEquals). + +Example: -See [assertIsEmpty docs on codeception.com](http://codeception.com/docs/modules/Asserts#assertIsEmpty). +```xml +<assertLessOrEquals stepKey="checkHeightIsCorrect"> + <actualResult type="variable">getImageHeight</actualResult> + <expectedResult type="variable">getSectionHeight</expectedResult> +</assertLessOrEquals> +``` Attribute|Type|Use|Description ---|---|---|--- -`actual`|string|required| A value of the actual result. -`actualType`|string|optional| A type of the actual result. Possible values: `const` (default), `int`, `float`, `bool`, `string`, `variable`, `array`. `message`|string|optional|Text of informational message about a cause of failure. `stepKey`|string|required| A unique identifier of the text step. `before`|string|optional| `stepKey` of action that must be executed next. `after`|string|optional| `stepKey` of the preceding action. -### assertLessOrEquals +### assertLessThan -See [assertLessOrEquals docs on codeception.com](http://codeception.com/docs/modules/Asserts#assertLessOrEquals). +See [assertLessThan docs on codeception.com](https://codeception.com/docs/modules/Asserts#assertLessThan). + +Example: + +```xml +<assertLessThan stepKey="assertLessImages"> + <expectedResult type="variable">initialImages</expectedResult> + <actualResult type="variable">newImages</actualResult> +</assertLessThan> +``` Attribute|Type|Use|Description ---|---|---|--- -`expected`|string|required| A value of the expected result. -`expectedType`|string|optional| A type of the expected result. Possible values: `const` (default), `int`, `float`, `bool`, `string`, `variable`, `array`. -`actual`|string|required| A value of the actual result. -`actualType`|string|optional| A type of the actual result. Possible values: `const` (default), `int`, `float`, `bool`, `string`, `variable`, `array`. `message`|string|optional|Text of informational message about a cause of failure. `stepKey`|string|required| A unique identifier of the text step. `before`|string|optional| `stepKey` of action that must be executed next. `after`|string|optional| `stepKey` of the preceding action. -### assertLessThan +### assertLessThanOrEqual + +See [assertLessThanOrEqual docs on codeception.com](https://codeception.com/docs/modules/Asserts#assertLessThanOrEqual). -See [assertLessThan docs on codeception.com](http://codeception.com/docs/modules/Asserts#assertLessThan). +Example: + +```xml +<assertLessThanOrEqual stepKey="checkHeightIsCorrect"> + <actualResult type="variable">getImageHeight</actualResult> + <expectedResult type="variable">getSectionHeight</expectedResult> +</assertLessThanOrEqual> +``` Attribute|Type|Use|Description ---|---|---|--- -`expected`|string|required| A value of the expected result. -`expectedType`|string|optional| A type of the expected result. Possible values: `const` (default), `int`, `float`, `bool`, `string`, `variable`, `array`. -`actual`|string|required| A value of the actual result. -`actualType`|string|optional| A type of the actual result. Possible values: `const` (default), `int`, `float`, `bool`, `string`, `variable`, `array`. `message`|string|optional|Text of informational message about a cause of failure. `stepKey`|string|required| A unique identifier of the text step. `before`|string|optional| `stepKey` of action that must be executed next. `after`|string|optional| `stepKey` of the preceding action. -### assertLessThanOrEqual +### assertNotContains -See [assertLessThanOrEqual docs on codeception.com](http://codeception.com/docs/modules/Asserts#assertLessThanOrEqual). +See [assertNotContains docs on codeception.com](https://codeception.com/docs/modules/Asserts#assertNotContains). + +Example: + +```xml +<assertNotContains stepKey="assertCustomerGroupNotInOptions"> + <actualResult type="variable">customerGroups</actualResult> + <expectedResult type="string">{{customerGroup.code}}</expectedResult> +</assertNotContains> +``` Attribute|Type|Use|Description ---|---|---|--- -`expected`|string|required| A value of the expected result. -`expectedType`|string|optional| A type of the expected result. Possible values: `const` (default), `int`, `float`, `bool`, `string`, `variable`, `array`. -`actual`|string|required| A value of the actual result. -`actualType`|string|optional| A type of the actual result. Possible values: `const` (default), `int`, `float`, `bool`, `string`, `variable`, `array`. `message`|string|optional|Text of informational message about a cause of failure. `stepKey`|string|required| A unique identifier of the text step. `before`|string|optional| `stepKey` of action that must be executed next. `after`|string|optional| `stepKey` of the preceding action. -### assertNotContains +### assertStringNotContainsString -See [assertNotContains docs on codeception.com](http://codeception.com/docs/modules/Asserts#assertNotContains). +See [assertStringNotContainsString docs on codeception.com](https://codeception.com/docs/modules/Asserts#assertStringNotContainsString). + +Example: + +```xml +<assertStringNotContainsString stepKey="checkoutAsGuest"> + <expectedResult type="string">{{CaptchaData.checkoutAsGuest}}</expectedResult> + <actualResult type="variable">$formItems</actualResult> +</assertStringNotContainsString> +``` + +Attribute|Type|Use|Description +---|---|---|--- +`message`|string|optional|Text of informational message about a cause of failure. +`stepKey`|string|required| A unique identifier of the text step. +`before`|string|optional| `stepKey` of action that must be executed next. +`after`|string|optional| `stepKey` of the preceding action. + +### assertStringContainsStringIgnoringCase + +See [assertStringNotContainsStringIgnoringCase docs on codeception.com](https://codeception.com/docs/modules/Asserts#assertStringNotContainsStringIgnoringCase). + +Example: + +```xml +<assertStringContainsStringIgnoringCase stepKey="verifyContentType"> + <actualResult type="variable">grabContentType</actualResult> + <expectedResult type="string">{{image.extension}}</expectedResult> +</assertStringContainsStringIgnoringCase> +``` Attribute|Type|Use|Description ---|---|---|--- -`expected`|string|required| A value of the expected result. -`expectedType`|string|optional| A type of the expected result. Possible values: `const` (default), `int`, `float`, `bool`, `string`, `variable`, `array`. -`actual`|string|required| A value of the actual result. -`actualType`|string|optional| A type of the actual result. Possible values: `const` (default), `int`, `float`, `bool`, `string`, `variable`, `array`. `message`|string|optional|Text of informational message about a cause of failure. `stepKey`|string|required| A unique identifier of the text step. `before`|string|optional| `stepKey` of action that must be executed next. @@ -381,12 +489,18 @@ Attribute|Type|Use|Description ### assertNotEmpty -See [assertNotEmpty docs on codeception.com](http://codeception.com/docs/modules/Asserts#assertNotEmpty). +See [assertNotEmpty docs on codeception.com](https://codeception.com/docs/modules/Asserts#assertNotEmpty). + +Example: + +```xml +<assertNotEmpty stepKey="checkSwatchFieldForAdmin"> + <actualResult type="const">$grabSwatchForAdmin</actualResult> +</assertNotEmpty> +``` Attribute|Type|Use|Description ---|---|---|--- -`actual`|string|required| A value of the actual result. -`actualType`|string|optional| A type of the actual result. Possible values: `const` (default), `int`, `float`, `bool`, `string`, `variable`, `array`. `message`|string|optional|Text of informational message about a cause of failure. `stepKey`|string|required| A unique identifier of the text step. `before`|string|optional| `stepKey` of action that must be executed next. @@ -394,30 +508,64 @@ Attribute|Type|Use|Description ### assertNotEquals -See [assertNotEquals docs on codeception.com](http://codeception.com/docs/modules/Asserts#assertNotEquals). +See [assertNotEquals docs on codeception.com](https://codeception.com/docs/modules/Asserts#assertNotEquals). + +Example: + +```xml +<assertNotEquals stepKey="assertNotEquals"> + <actualResult type="string">{$grabTotalAfter}</actualResult> + <expectedResult type="string">{$grabTotalBefore}</expectedResult> +</assertNotEquals> +``` + +Attribute|Type|Use|Description +---|---|---|--- +`message`|string|optional|Text of informational message about a cause of failure. +`stepKey`|string|required| A unique identifier of the text step. +`before`|string|optional| `stepKey` of action that must be executed next. +`after`|string|optional| `stepKey` of the preceding action. + +### assertNotEqualsWithDelta + +See [assertNotEqualsWithDelta docs on codeception.com](https://codeception.com/docs/modules/Asserts#assertNotEqualsWithDelta). Attribute|Type|Use|Description ---|---|---|--- -`expected`|string|required| A value of the expected result. -`expectedType`|string|optional| A type of the expected result. Possible values: `const` (default), `int`, `float`, `bool`, `string`, `variable`, `array`. -`actual`|string|required| A value of the actual result. -`actualType`|string|optional| A type of the actual result. Possible values: `const` (default), `int`, `float`, `bool`, `string`, `variable`, `array`. `delta`|string|optional| `message`|string|optional|Text of informational message about a cause of failure. `stepKey`|string|required| A unique identifier of the text step. `before`|string|optional| `stepKey` of action that must be executed next. `after`|string|optional| `stepKey` of the preceding action. +### assertNotEqualsCanonicalizing + +See [assertNotEqualsCanonicalizing docs on codeception.com](https://codeception.com/docs/modules/Asserts#assertNotEqualsCanonicalizing). + +Attribute|Type|Use|Description +---|---|---|--- +`message`|string|optional|Text of informational message about a cause of failure. +`stepKey`|string|required| A unique identifier of the text step. +`before`|string|optional| `stepKey` of action that must be executed next. +`after`|string|optional| `stepKey` of the preceding action. + +### assertNotEqualsIgnoringCase + +See [assertNotEqualsIgnoringCase docs on codeception.com](https://codeception.com/docs/modules/Asserts#assertNotEqualsIgnoringCase). + +Attribute|Type|Use|Description +---|---|---|--- +`message`|string|optional|Text of informational message about a cause of failure. +`stepKey`|string|required| A unique identifier of the text step. +`before`|string|optional| `stepKey` of action that must be executed next. +`after`|string|optional| `stepKey` of the preceding action. + ### assertNotInstanceOf -See [assertNotInstanceOf docs on codeception.com](http://codeception.com/docs/modules/Asserts#assertNotInstanceOf). +See [assertNotInstanceOf docs on codeception.com](https://codeception.com/docs/modules/Asserts#assertNotInstanceOf). Attribute|Type|Use|Description ---|---|---|--- -`expected`|string|required| A value of the expected result. -`expectedType`|string|optional| A type of the expected result. Possible values: `const` (default), `int`, `float`, `bool`, `string`, `variable`, `array`. -`actual`|string|required| A value of the actual result. -`actualType`|string|optional| A type of the actual result. Possible values: `const` (default), `int`, `float`, `bool`, `string`, `variable`, `array`. `message`|string|optional|Text of informational message about a cause of failure. `stepKey`|string|required| A unique identifier of the text step. `before`|string|optional| `stepKey` of action that must be executed next. @@ -425,12 +573,10 @@ Attribute|Type|Use|Description ### assertNotNull -See [assertNotNull docs on codeception.com](http://codeception.com/docs/modules/Asserts#assertNotNull). +See [assertNotNull docs on codeception.com](https://codeception.com/docs/modules/Asserts#assertNotNull). Attribute|Type|Use|Description ---|---|---|--- -`actual`|string|required| A value of the actual result. -`actualType`|string|optional| A type of the actual result. Possible values: `const` (default), `int`, `float`, `bool`, `string`, `variable`, `array`. `message`|string|optional|Text of informational message about a cause of failure. `stepKey`|string|required| A unique identifier of the text step. `before`|string|optional| `stepKey` of action that must be executed next. @@ -438,14 +584,19 @@ Attribute|Type|Use|Description ### assertNotRegExp -See [assertNotRegExp docs on codeception.com](http://codeception.com/docs/modules/Asserts#assertNotRegExp). +See [assertNotRegExp docs on codeception.com](https://codeception.com/docs/modules/Asserts#assertNotRegExp). + +Example: + +```xml +<assertNotRegExp stepKey="simpleThumbnailIsNotDefault"> + <actualResult type="const">$getSimpleProductThumbnail</actualResult> + <expectedResult type="const">'/placeholder\/thumbnail\.jpg/'</expectedResult> +</assertNotRegExp> +``` Attribute|Type|Use|Description ---|---|---|--- -`expected`|string|required| A value of the expected result. -`expectedType`|string|optional| A type of the expected result. Possible values: `const` (default), `int`, `float`, `bool`, `string`, `variable`, `array`. -`actual`|string|required| A value of the actual result. -`actualType`|string|optional| A type of the actual result. Possible values: `const` (default), `int`, `float`, `bool`, `string`, `variable`, `array`. `message`|string|optional|Text of informational message about a cause of failure. `stepKey`|string|required| A unique identifier of the text step. `before`|string|optional| `stepKey` of action that must be executed next. @@ -453,14 +604,10 @@ Attribute|Type|Use|Description ### assertNotSame -See [assertNotSame docs on codeception.com](http://codeception.com/docs/modules/Asserts#assertNotSame). +See [assertNotSame docs on codeception.com](https://codeception.com/docs/modules/Asserts#assertNotSame). Attribute|Type|Use|Description ---|---|---|--- -`expected`|string|required| A value of the expected result. -`expectedType`|string|optional| A type of the expected result. Possible values: `const` (default), `int`, `float`, `bool`, `string`, `variable`, `array`. -`actual`|string|required| A value of the actual result. -`actualType`|string|optional| A type of the actual result. Possible values: `const` (default), `int`, `float`, `bool`, `string`, `variable`, `array`. `message`|string|optional|Text of informational message about a cause of failure. `stepKey`|string|required| A unique identifier of the text step. `before`|string|optional| `stepKey` of action that must be executed next. @@ -468,12 +615,10 @@ Attribute|Type|Use|Description ### assertNull -See [assertNull docs on codeception.com](http://codeception.com/docs/modules/Asserts#assertNull). +See [assertNull docs on codeception.com](https://codeception.com/docs/modules/Asserts#assertNull). Attribute|Type|Use|Description ---|---|---|--- -`actual`|string|required| A value of the actual result. -`actualType`|string|optional| A type of the actual result. Possible values: `const` (default), `int`, `float`, `bool`, `string`, `variable`, `array`. `message`|string|optional|Text of informational message about a cause of failure. `stepKey`|string|required| A unique identifier of the text step. `before`|string|optional| `stepKey` of action that must be executed next. @@ -481,14 +626,19 @@ Attribute|Type|Use|Description ### assertRegExp -See [assertRegExp docs on codeception.com](http://codeception.com/docs/modules/Asserts#assertRegExp). +See [assertRegExp docs on codeception.com](https://codeception.com/docs/modules/Asserts#assertRegExp). + +Example: + +```xml +<assertRegExp message="adminAnalyticsMetadata object is invalid" stepKey="validateadminAnalyticsMetadata"> + <expectedResult type="string">#var\s+adminAnalyticsMetadata\s+=\s+{\s+("[\w_]+":\s+"[^"]*?",\s+)*?("[\w_]+":\s+"[^"]*?"\s+)};#s</expectedResult> + <actualResult type="variable">$pageSource</actualResult> +</assertRegExp> +``` Attribute|Type|Use|Description ---|---|---|--- -`expected`|string|required| A value of the expected result. -`expectedType`|string|optional| A type of the expected result. Possible values: `const` (default), `int`, `float`, `bool`, `string`, `variable`, `array`. -`actual`|string|required| A value of the actual result. -`actualType`|string|optional| A type of the actual result. Possible values: `const` (default), `int`, `float`, `bool`, `string`, `variable`, `array`. `message`|string|optional|Text of informational message about a cause of failure. `stepKey`|string|required| A unique identifier of the text step. `before`|string|optional| `stepKey` of action that must be executed next. @@ -496,14 +646,10 @@ Attribute|Type|Use|Description ### assertSame -See [assertSame docs on codeception.com](http://codeception.com/docs/modules/Asserts#assertSame). +See [assertSame docs on codeception.com](https://codeception.com/docs/modules/Asserts#assertSame). Attribute|Type|Use|Description ---|---|---|--- -`expected`|string|required| A value of the expected result. -`expectedType`|string|optional| A type of the expected result. Possible values: `const` (default), `int`, `float`, `bool`, `string`, `variable`, `array`. -`actual`|string|required| A value of the actual result. -`actualType`|string|optional| A type of the actual result. Possible values: `const` (default), `int`, `float`, `bool`, `string`, `variable`, `array`. `message`|string|optional|Text of informational message about a cause of failure. `stepKey`|string|required| A unique identifier of the text step. `before`|string|optional| `stepKey` of action that must be executed next. @@ -511,14 +657,10 @@ Attribute|Type|Use|Description ### assertStringStartsNotWith -See [assertStringStartsNotWith docs on codeception.com](http://codeception.com/docs/modules/Asserts#assertStringStartsNotWith). +See [assertStringStartsNotWith docs on codeception.com](https://codeception.com/docs/modules/Asserts#assertStringStartsNotWith). Attribute|Type|Use|Description ---|---|---|--- -`expected`|string|required| A value of the expected result. -`expectedType`|string|optional| A type of the expected result. Possible values: `const` (default), `int`, `float`, `bool`, `string`, `variable`, `array`. -`actual`|string|required| A value of the actual result. -`actualType`|string|optional| A type of the actual result. Possible values: `const` (default), `int`, `float`, `bool`, `string`, `variable`, `array`. `message`|string|optional|Text of informational message about a cause of failure. `stepKey`|string|required| A unique identifier of the text step. `before`|string|optional| `stepKey` of action that must be executed next. @@ -526,14 +668,10 @@ Attribute|Type|Use|Description ### assertStringStartsWith -See [assertStringStartsWith docs on codeception.com](http://codeception.com/docs/modules/Asserts#assertStringStartsWith). +See [assertStringStartsWith docs on codeception.com](https://codeception.com/docs/modules/Asserts#assertStringStartsWith). Attribute|Type|Use|Description ---|---|---|--- -`expected`|string|required| A value of the expected result. -`expectedType`|string|optional| A type of the expected result. Possible values: `const` (default), `int`, `float`, `bool`, `string`, `variable`, `array`. -`actual`|string|required| A value of the actual result. -`actualType`|string|optional| A type of the actual result. Possible values: `const` (default), `int`, `float`, `bool`, `string`, `variable`, `array`. `message`|string|optional|Text of informational message about a cause of failure. `stepKey`|string|required| A unique identifier of the text step. `before`|string|optional| `stepKey` of action that must be executed next. @@ -541,12 +679,10 @@ Attribute|Type|Use|Description ### assertTrue -See [assertTrue docs on codeception.com](http://codeception.com/docs/modules/Asserts#assertTrue). +See [assertTrue docs on codeception.com](https://codeception.com/docs/modules/Asserts#assertTrue). Attribute|Type|Use|Description ---|---|---|--- -`actual`|string|required| A value of the actual result. -`actualType`|string|optional| A type of the actual result. Possible values: `const` (default), `int`, `float`, `bool`, `string`, `variable`, `array`. `message`|string|optional|Text of informational message about a cause of failure. `stepKey`|string|required| A unique identifier of the text step. `before`|string|optional| `stepKey` of action that must be executed next. @@ -554,21 +690,17 @@ Attribute|Type|Use|Description ### expectException -See [expectException docs on codeception.com](http://codeception.com/docs/modules/WebDriver#expectException). +See [expectException docs on codeception.com](https://codeception.com/docs/modules/Asserts#expectException). Attribute|Type|Use|Description ---|---|---|--- -`expected`|string|required| A value of the expected result. -`expectedType`|string|optional| A type of the expected result. Possible values: `const` (default), `int`, `float`, `bool`, `string`, `variable`, `array`. -`actual`|string|required| A value of the actual result. -`actualType`|string|optional| A type of the actual result. Possible values: `const` (default), `int`, `float`, `bool`, `string`, `variable`, `array`. `stepKey`|string|required| A unique identifier of the text step. `before`|string|optional| `stepKey` of action that must be executed next. `after`|string|optional| `stepKey` of the preceding action. ### fail -See [fail docs on codeception.com](http://codeception.com/docs/modules/WebDriver#fail). +See [fail docs on codeception.com](https://codeception.com/docs/modules/Asserts#fail). Attribute|Type|Use|Description ---|---|---|--- diff --git a/docs/tips-tricks.md b/docs/tips-tricks.md index 4f7539c6d..8ef00f888 100644 --- a/docs/tips-tricks.md +++ b/docs/tips-tricks.md @@ -1,3 +1,9 @@ +--- +title: Tips and tricks +redirect_to: https://developer.adobe.com/commerce/testing/functional-testing-framework/test-writing/tips-tricks/ +status: migrated +--- + # Tips and Tricks Sometimes, little changes can make a big difference in your project. Here are some test writing tips to keep everything running smoothly. @@ -419,5 +425,5 @@ BAD: <!--{% endraw %}--> <!-- Link Definitions --> -[This test]: https://github.com/magento/magento2/blob/2.3-develop/app/code/Magento/Captcha/Test/Mftf/Test/StorefrontCaptchaRegisterNewCustomerTest.xml#L24 -[Data file]: https://github.com/magento/magento2/blob/2.3-develop/app/code/Magento/Captcha/Test/Mftf/Data/CaptchaConfigData.xml +[This test]: https://github.com/magento/magento2/blob/2.3/app/code/Magento/Captcha/Test/Mftf/Test/StorefrontCaptchaRegisterNewCustomerTest.xml#L24 +[Data file]: https://github.com/magento/magento2/blob/2.3/app/code/Magento/Captcha/Test/Mftf/Data/CaptchaConfigData.xml diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index d2a7dcabe..cf27a049e 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -1,3 +1,9 @@ +--- +title: Troubleshooting +redirect_to: https://developer.adobe.com/commerce/testing/functional-testing-framework/troubleshooting/ +status: migrated +--- + # Troubleshooting Having a little trouble with the MFTF? See some common errors and fixes below. diff --git a/docs/update.md b/docs/update.md index 129742346..311875b6d 100644 --- a/docs/update.md +++ b/docs/update.md @@ -1,79 +1,59 @@ +--- +title: Update the Magento Functional Testing Framework +redirect_to: https://developer.adobe.com/commerce/testing/functional-testing-framework/update/ +status: migrated +--- + # Update the Magento Functional Testing Framework <div class="bs-callout bs-callout-info" markdown="1"> -[Find your version][] of the MFTF. - -The latest Magento 2.3 release supports MFTF 2.3.13. -The latest Magento 2.2 release supports MFTF 2.3.8. +[Find your version] of MFTF. +The latest Magento 2.4.x release supports MFTF 3.x. +The latest Magento 2.3.x release supports MFTF 2.6.x. </div> -Magento tests and the framework are stored in different repositories. - -Magento tests are stored in the same repository as the Magento code base. -When you pull changes in the Magento code, you're potentially pulling corresponding tests as well. - -The MFTF is installed separately as a dependency using Composer. -When pulling the latest Magento code, update the corresponding Composer dependencies in the `magento2` root directory. -This ensures that the MFTF is up to date. +Tests and the Framework itself are stored in different repositories. -## Update the MFTF from 2.3.x - -To update the MFTF to the latest patch: - -1. Verify that the Magento [WYSIWYG settings][] and [Security settings][] are set appropriately. -1. Check details about backward incompatible changes in the [Changelog][] and update your new or customized tests. -1. Get the latest framework version using Composer: +* Tests are stored in Module's directory. +* MFTF is installed separately (usually as a Composer dependency) - ```bash - composer update - ``` +To understand different types of update - please follow the [Versioning][] page. -1. Generate the updated tests: +## Patch version update - ```bash - vendor/bin/mftf generate:tests - ``` +Takes place when **third** digit of version number changes. -## Update the MFTF from 2.2 +1. Make sure that [Security settings][] are set appropriately. +1. Get latest Framework version with `composer update magento/magento2-functional-testing-framework --with-dependencies` +1. Generate updated tests with `vendor/bin/mftf generate:tests` -To update the MFTF from the previous minor version: +## Minor version update -1. When you update Magento, verify that the Magento [WYSIWYG settings][] and [Security settings][] are set appropriately. -1. Starting at the `magento2/` root directory remove the `vendor/` directory: +Takes place when **second** digit of version number changes. - ```bash - rm -rf vendor/ - ``` - -1. Get the latest framework version from the Composer dependencies: - - ```bash - composer install - ``` - -1. Run the `upgrade:tests` using the new command line tool: - - ```bash - vendor/bin/mftf upgrade:tests app - ``` - -1. If you are using Phpstorm, update the urn catalog: +1. Check details about backward incompatible changes in the [Changelog][] and update your new or customized tests. +1. Perform all the actions provided for [Patch Version Update][] +1. When updating from versions below `2.5.0`, verify [WYSIWYG settings][] +1. You may need to run the `upgrade:tests` using `vendor/bin/mftf upgrade:tests` - ```bash - vendor/bin/mftf generate:urn-catalog .idea/ - ``` +## Major version update -1. Update your own tests, including data, metadata, and so on, if they contain tags that are unsupported in the newer version. +Takes place when **first** digit of version number changes. - Check details about backward incompatible changes and review new MFTF release documentation in the [Changelog][]. +1. Check detailed explanation and instructions on major version upgrade in [Backward Incompatible Changes][] and upgrade your new or customized tests. +1. Perform all the actions provided for [Minor Version Update][] -1. Generate newly pulled tests: +## After updating - ```bash - vendor/bin/mftf generate:tests - ``` +1. It is a good idea to regenerate your IDE Schema Definition catalog with `vendor/bin/mftf generate:urn-catalog .idea/misc.xml` +1. Update your tests, including data, metadata and other resources. Check if they contain tags that are unsupported in the newer version. +1. Remove the references to resources (ActionGroups, Sections, Tests) marked as deprecated. <!-- Link Definitions --> [Changelog]: https://github.com/magento/magento2-functional-testing-framework/blob/master/CHANGELOG.md +[Backward Incompatible Changes]: backward-incompatible-changes.md [WYSIWYG settings]: getting-started.md#wysiwyg-settings [Security settings]: getting-started.md#security-settings +[Find your version]: introduction.md#find-your-mftf-version +[Versioning]: versioning.md#versioning-policy +[Patch Version Update]: #patch-version-update diff --git a/docs/versioning.md b/docs/versioning.md index ecb8438c5..ff76ab867 100644 --- a/docs/versioning.md +++ b/docs/versioning.md @@ -1,3 +1,9 @@ +--- +title: MFTF versioning schema +redirect_to: https://developer.adobe.com/commerce/testing/functional-testing-framework/versioning/ +status: migrated +--- + # MFTF versioning schema This document describes the versioning policy for the Magento Functional Testing Framework (MFTF), including the version numbering schema. @@ -12,7 +18,6 @@ If a modification to MFTF forces tests to be changed, this is a backward incompa To find the version of MFTF that you are using, run the Magento CLI command: ```bash -cd <magento_root>/ vendor/bin/mftf --version ``` @@ -61,8 +66,12 @@ You must reset the patch and minor version to 0 when you change the major versio This table lists the version of the MFTF that was released with a particular version of Magento. |Magento version| MFTF version| -|---|---| +|--- |--- | +| 2.4.0 | 3.0.0 | +| 2.3.5 | 2.6.4 | +| 2.3.4 | 2.5.3 | +| 2.3.3 | 2.4.5 | | 2.3.2 | 2.3.14 | | 2.3.1 | 2.3.13 | -| 2.3.0 | 2.3.9 | +| 2.3.0 | 2.3.9 | | 2.2.8 | 2.3.13 | diff --git a/etc/config/.credentials.example b/etc/config/.credentials.example index 429e9d19f..25400bd63 100644 --- a/etc/config/.credentials.example +++ b/etc/config/.credentials.example @@ -1,3 +1,5 @@ +magento/tfa/OTP_SHARED_SECRET + #magento/carriers_fedex_account= #magento/carriers_fedex_meter_number= #magento/carriers_fedex_key= @@ -72,4 +74,3 @@ #magento/payment_paypal_alternative_payment_methods_express_checkout_us_express_checkout_required_express_checkout_required_express_checkout_api_password= #magento/payment_paypal_alternative_payment_methods_express_checkout_us_express_checkout_required_express_checkout_required_express_checkout_api_signature= -#magento/fraud_protection_signifyd_api_key= \ No newline at end of file diff --git a/etc/config/.env.example b/etc/config/.env.example index f25cb3de7..1c181cf8f 100644 --- a/etc/config/.env.example +++ b/etc/config/.env.example @@ -5,7 +5,7 @@ MAGENTO_BASE_URL=http://devdocs.magento.com/ #*** Uncomment if you are running Admin Panel on separate domain (used with MAGENTO_BACKEND_NAME) ***# -# MAGENTO_BACKEND_BASE_HOST=http://admin.example.com/ +# MAGENTO_BACKEND_BASE_URL=http://admin.example.com/ #*** Set the Admin Username and Password for your Magento instance ***# MAGENTO_BACKEND_NAME=admin @@ -21,6 +21,7 @@ MAGENTO_ADMIN_PASSWORD=123123q #SELENIUM_PORT=4444 #SELENIUM_PROTOCOL=http #SELENIUM_PATH=/wd/hub +SELENIUM_CLOSE_ALL_SESSIONS=true #*** Browser for running tests, default chrome. Uncomment and change if you want to run tests on another browser (ex. firefox). BROWSER=chrome @@ -30,10 +31,14 @@ BROWSER=chrome #MAGENTO_RESTAPI_SERVER_PORT=8080 #MAGENTO_RESTAPI_SERVER_PROTOCOL=https -#*** Uncomment and set vault address and secret base path if you want to use vault to manage _CREDS secrets ***# +#*** To use HashiCorp Vault to manage _CREDS secrets, uncomment and set vault address and secret base path ***# #CREDENTIAL_VAULT_ADDRESS=http://127.0.0.1:8200 #CREDENTIAL_VAULT_SECRET_BASE_PATH=secret +#*** To use AWS Secrets Manager to manage _CREDS secrets, uncomment and set region, profile is optional, when omitted, AWS default credential provider chain will be used ***# +#CREDENTIAL_AWS_SECRETS_MANAGER_PROFILE=default +#CREDENTIAL_AWS_SECRETS_MANAGER_REGION=us-east-1 + #*** Uncomment these properties to set up a dev environment with symlinked projects ***# #TESTS_BP= #FW_BP= @@ -43,7 +48,7 @@ BROWSER=chrome #DEFAULT_TIMEZONE=America/Los_Angeles #*** These properties impact the modules loaded into MFTF, you can point to your own full path, or a custom set of modules located with the core set -MODULE_WHITELIST=Magento_Framework,ConfigurableProductWishlist,ConfigurableProductCatalogSearch +MODULE_ALLOWLIST=Magento_Framework,ConfigurableProductWishlist,ConfigurableProductCatalogSearch #CUSTOM_MODULE_PATHS= #*** Bool property which allows the user to toggle debug output during test execution @@ -53,5 +58,22 @@ MODULE_WHITELIST=Magento_Framework,ConfigurableProductWishlist,ConfigurableProdu #ALLOW_SKIPPED=true #*** Default timeout for wait actions -#WAIT_TIMEOUT=10 +#WAIT_TIMEOUT=30 + +#*** Uncomment and set to enable all tests, regardless of passing status, to have all their Allure artifacts. +#VERBOSE_ARTIFACTS=true + +#*** Uncomment and set to enable browser log entries on actions in Allure. Blocklist is used to filter logs of a specific "source" +#ENABLE_BROWSER_LOG=true +BROWSER_LOG_BLOCKLIST=other + +#*** Uncomment and set to true to use Codeception's interactive pause functionality +#ENABLE_PAUSE=true + +#*** Elastic Search version used for test ***# +ELASTICSEARCH_VERSION=7 + +#*** Lifetime (in seconds) of Magento Admin WebAPI Token; if token is older than this value a refresh attempt will be made just before the next WebAPI call ***# +#MAGENTO_ADMIN_WEBAPI_TOKEN_LIFETIME=10800 + #*** End of .env ***# diff --git a/etc/config/command.php b/etc/config/command.php index 7b45a2595..0d8fc7997 100644 --- a/etc/config/command.php +++ b/etc/config/command.php @@ -14,24 +14,28 @@ $tokenPassedIn = urldecode($_POST['token'] ?? ''); $command = urldecode($_POST['command'] ?? ''); $arguments = urldecode($_POST['arguments'] ?? ''); + $timeout = floatval(urldecode($_POST['timeout'] ?? 60)); // Token returned will be null if the token we passed in is invalid $tokenFromMagento = $tokenModel->loadByToken($tokenPassedIn)->getToken(); - if (!empty($tokenFromMagento) && ($tokenFromMagento == $tokenPassedIn)) { + if (!empty($tokenFromMagento) && ($tokenFromMagento === $tokenPassedIn)) { $php = PHP_BINDIR ? PHP_BINDIR . '/php' : 'php'; $magentoBinary = $php . ' -f ../../../../bin/magento'; $valid = validateCommand($magentoBinary, $command); if ($valid) { $fullCommand = escapeshellcmd($magentoBinary . " $command" . " $arguments"); - $process = new Symfony\Component\Process\Process($fullCommand); - $process->setIdleTimeout(60); + $process = Symfony\Component\Process\Process::fromShellCommandline($fullCommand); + $process->setIdleTimeout($timeout); $process->setTimeout(0); $idleTimeout = false; try { $process->run(); $output = $process->getOutput(); if (!$process->isSuccessful()) { - $output = $process->getErrorOutput(); + $failureOutput = $process->getErrorOutput(); + if (!empty($failureOutput)) { + $output = $failureOutput; + } } if (empty($output)) { $output = "CLI did not return output."; @@ -48,7 +52,7 @@ $exitCode = $process->getExitCode(); - if ($exitCode == 0 || $idleTimeout) { + if ($process->isSuccessful() || $idleTimeout) { http_response_code(202); } else { http_response_code(500); diff --git a/etc/config/functional.suite.dist.yml b/etc/config/functional.suite.dist.yml index 12658515b..d5dbde466 100644 --- a/etc/config/functional.suite.dist.yml +++ b/etc/config/functional.suite.dist.yml @@ -12,11 +12,11 @@ namespace: Magento\FunctionalTestingFramework modules: enabled: - \Magento\FunctionalTestingFramework\Module\MagentoWebDriver - - \Magento\FunctionalTestingFramework\Helper\Acceptance - - \Magento\FunctionalTestingFramework\Helper\MagentoFakerData - \Magento\FunctionalTestingFramework\Module\MagentoSequence - \Magento\FunctionalTestingFramework\Module\MagentoAssert + - \Magento\FunctionalTestingFramework\Module\MagentoActionProxies - Asserts + - \Magento\FunctionalTestingFramework\Helper\HelperContainer config: \Magento\FunctionalTestingFramework\Module\MagentoWebDriver: url: "%MAGENTO_BASE_URL%" @@ -27,11 +27,15 @@ modules: window_size: 1280x1024 username: "%MAGENTO_ADMIN_USERNAME%" password: "%MAGENTO_ADMIN_PASSWORD%" - pageload_timeout: 30 + pageload_timeout: "%WAIT_TIMEOUT%" + request_timeout: "%WAIT_TIMEOUT%" + connection_timeout: "%WAIT_TIMEOUT%" host: "%SELENIUM_HOST%" port: "%SELENIUM_PORT%" protocol: "%SELENIUM_PROTOCOL%" path: "%SELENIUM_PATH%" + close_all_sessions: "%SELENIUM_CLOSE_ALL_SESSIONS%" capabilities: - chromeOptions: - args: ["--window-size=1280,1024", "--disable-extensions", "--enable-automation", "--disable-gpu", "--enable-Passthrough"] + unhandledPromptBehavior: "ignore" + chromeOptions: + args: ["--no-sandbox", "--window-size=1280,1024", "--disable-extensions", "--enable-automation", "--disable-gpu", "--enable-Passthrough", "--disable-dev-shm-usage", "--ignore-certificate-errors"] diff --git a/etc/di.xml b/etc/di.xml index 3e6313bf3..586158286 100644 --- a/etc/di.xml +++ b/etc/di.xml @@ -8,7 +8,7 @@ <!-- Entity value gets replaced in Dom.php before reading $xml --> <!DOCTYPE config [ - <!ENTITY commonTestActions "acceptPopup|actionGroup|amOnPage|amOnUrl|amOnSubdomain|appendField|assertArrayIsSorted|assertArraySubset|assertElementContainsAttribute|attachFile|cancelPopup|checkOption|clearField|click|clickWithLeftButton|clickWithRightButton|closeAdminNotification|closeTab|comment|conditionalClick|createData|deleteData|updateData|getData|dontSee|dontSeeJsError|dontSeeCheckboxIsChecked|dontSeeCookie|dontSeeCurrentUrlEquals|dontSeeCurrentUrlMatches|dontSeeElement|dontSeeElementInDOM|dontSeeInCurrentUrl|dontSeeInField|dontSeeInFormFields|dontSeeInPageSource|dontSeeInSource|dontSeeInTitle|dontSeeLink|dontSeeOptionIsSelected|doubleClick|dragAndDrop|entity|executeJS|executeInSelenium|fillField|formatMoney|generateDate|grabAttributeFrom|grabCookie|grabFromCurrentUrl|grabMultiple|grabPageSource|grabTextFrom|grabValueFrom|loadSessionSnapshot|loginAsAdmin|magentoCLI|makeScreenshot|maximizeWindow|moveBack|moveForward|moveMouseOver|mSetLocale|mResetLocale|openNewTab|pauseExecution|parseFloat|performOn|pressKey|reloadPage|resetCookie|submitForm|resizeWindow|saveSessionSnapshot|scrollTo|scrollToTopOfPage|searchAndMultiSelectOption|see|seeCheckboxIsChecked|seeCookie|seeCurrentUrlEquals|seeCurrentUrlMatches|seeElement|seeElementInDOM|seeInCurrentUrl|seeInField|seeInFormFields|seeInPageSource|seeInPopup|seeInSource|seeInTitle|seeLink|seeNumberOfElements|seeOptionIsSelected|selectOption|setCookie|submitForm|switchToIFrame|switchToNextTab|switchToPreviousTab|switchToWindow|typeInPopup|uncheckOption|unselectOption|wait|waitForAjaxLoad|waitForElement|waitForElementChange|waitForElementNotVisible|waitForElementVisible|waitForPwaElementNotVisible|waitForPwaElementVisible|waitForJS|waitForLoadingMaskToDisappear|waitForPageLoad|waitForText|assertArrayHasKey|assertArrayNotHasKey|assertArraySubset|assertContains|assertCount|assertEmpty|assertEquals|assertFalse|assertFileExists|assertFileNotExists|assertGreaterOrEquals|assertGreaterThan|assertGreaterThanOrEqual|assertInstanceOf|assertInternalType|assertIsEmpty|assertLessOrEquals|assertLessThan|assertLessThanOrEqual|assertNotContains|assertNotEmpty|assertNotEquals|assertNotInstanceOf|assertNotNull|assertNotRegExp|assertNotSame|assertNull|assertRegExp|assertSame|assertStringStartsNotWith|assertStringStartsWith|assertTrue|expectException|fail|dontSeeFullUrlEquals|dontSee|dontSeeFullUrlMatches|dontSeeInFullUrl|seeFullUrlEquals|seeFullUrlMatches|seeInFullUrl|grabFromFullUrl"> + <!ENTITY commonTestActions "acceptPopup|actionGroup|amOnPage|amOnUrl|amOnSubdomain|appendField|assertArrayIsSortasserted|assertElementContainsAttribute|attachFile|cancelPopup|checkOption|clearField|click|clickWithLeftButton|clickWithRightButton|closeAdminNotification|closeTab|comment|conditionalClick|createData|deleteData|updateData|getData|dontSee|dontSeeJsError|dontSeeCheckboxIsChecked|dontSeeCookie|dontSeeCurrentUrlEquals|dontSeeCurrentUrlMatches|dontSeeElement|dontSeeElementInDOM|dontSeeInCurrentUrl|dontSeeInField|dontSeeInFormFields|dontSeeInPageSource|dontSeeInSource|dontSeeInTitle|dontSeeLink|dontSeeOptionIsSelected|doubleClick|dragAndDrop|entity|executeJS|fillField|formatCurrency|generateDate|getOTP|grabAttributeFrom|grabCookie|grabCookieAttributes|grabFromCurrentUrl|grabMultiple|grabPageSource|grabTextFrom|grabValueFrom|return|loadSessionSnapshot|loginAsAdmin|magentoCLI|magentoCron|makeScreenshot|maximizeWindow|moveBack|moveForward|moveMouseOver|mSetLocale|mResetLocale|openNewTab|pause|parseFloat|pressKey|reloadPage|resetCookie|submitForm|resizeWindow|saveSessionSnapshot|scrollTo|scrollToTopOfPage|searchAndMultiSelectOption|see|seeCheckboxIsChecked|seeCookie|seeCurrentUrlEquals|seeCurrentUrlMatches|seeElement|seeElementInDOM|seeInCurrentUrl|seeInField|seeInFormFields|seeInPageSource|seeInPopup|seeInSource|seeInTitle|seeLink|seeNumberOfElements|seeOptionIsSelected|selectOption|setCookie|submitForm|switchToIFrame|switchToNextTab|switchToPreviousTab|switchToWindow|typeInPopup|uncheckOption|unselectOption|wait|waitForAjaxLoad|waitForElement|waitForElementChange|waitForElementNotVisible|waitForElementVisible|waitForPwaElementNotVisible|waitForPwaElementVisible|waitForJS|waitForLoadingMaskToDisappear|waitForPageLoad|waitForText|assertArrayHasKey|assertArrayNotHasKey|assertContains|assertStringContainsString|assertStringContainsStringIgnoringCase|assertCount|assertEmpty|assertEquals|assertFalse|assertFileExists|assertFileNotExists|assertGreaterOrEquals|assertGreaterThan|assertGreaterThanOrEqual|assertInstanceOf|assertIsEmpty|assertLessOrEquals|assertLessThan|assertLessThanOrEqual|assertNotContains|assertStringNotContainsString|assertStringNotContainsStringIgnoringCase|assertNotEmpty|assertNotEquals|assertNotInstanceOf|assertNotNull|assertNotRegExp|assertNotSame|assertNull|assertRegExp|assertSame|assertStringStartsNotWith|assertStringStartsWith|assertTrue|expectException|fail|dontSeeFullUrlEquals|dontSee|dontSeeFullUrlMatches|dontSeeInFullUrl|seeFullUrlEquals|seeFullUrlMatches|seeInFullUrl|grabFromFullUrl|helper|assertEqualsWithDelta|assertEqualsCanonicalizing|assertEqualsIgnoringCase|assertNotEqualsWithDelta|assertNotEqualsCanonicalizing|assertNotEqualsIgnoringCase"> ]> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../src/Magento/FunctionalTestingFramework/ObjectManager/etc/config.xsd"> @@ -70,17 +70,17 @@ <virtualType name="Magento\FunctionalTestingFramework\Config\SchemaLocator\Page" type="Magento\FunctionalTestingFramework\Config\SchemaLocator"> <arguments> - <argument name="schemaPath" xsi:type="string">Magento/FunctionalTestingFramework/Page/etc/PageObject.xsd</argument> + <argument name="schemaPath" xsi:type="string">Magento/FunctionalTestingFramework/Page/etc/mergedPageObject.xsd</argument> </arguments> </virtualType> <virtualType name="Magento\FunctionalTestingFramework\Config\SchemaLocator\Section" type="Magento\FunctionalTestingFramework\Config\SchemaLocator"> <arguments> - <argument name="schemaPath" xsi:type="string">Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd</argument> + <argument name="schemaPath" xsi:type="string">Magento/FunctionalTestingFramework/Page/etc/mergedSectionObject.xsd</argument> </arguments> </virtualType> <virtualType name="Magento\FunctionalTestingFramework\Config\Reader\Page" type="Magento\FunctionalTestingFramework\Config\Reader\MftfFilesystem"> <arguments> - <argument name="fileResolver" xsi:type="object">Magento\FunctionalTestingFramework\Config\FileResolver\Module</argument> + <argument name="fileResolver" xsi:type="object">Magento\FunctionalTestingFramework\Config\FileResolver\Mask</argument> <argument name="converter" xsi:type="object">Magento\FunctionalTestingFramework\Config\Converter</argument> <argument name="schemaLocator" xsi:type="object">Magento\FunctionalTestingFramework\Config\SchemaLocator\Page</argument> <argument name="domDocumentClass" xsi:type="string">Magento\FunctionalTestingFramework\Page\Config\Dom</argument> @@ -88,13 +88,13 @@ <item name="/pages/page" xsi:type="string">name</item> <item name="/pages/page/section" xsi:type="string">name</item> </argument> - <argument name="fileName" xsi:type="string">*Page.xml</argument> + <argument name="fileName" xsi:type="string">/Page\.xml$/</argument> <argument name="defaultScope" xsi:type="string">Page</argument> </arguments> </virtualType> <virtualType name="Magento\FunctionalTestingFramework\Config\Reader\Section" type="Magento\FunctionalTestingFramework\Config\Reader\MftfFilesystem"> <arguments> - <argument name="fileResolver" xsi:type="object">Magento\FunctionalTestingFramework\Config\FileResolver\Module</argument> + <argument name="fileResolver" xsi:type="object">Magento\FunctionalTestingFramework\Config\FileResolver\Mask</argument> <argument name="converter" xsi:type="object">Magento\FunctionalTestingFramework\Config\Converter</argument> <argument name="schemaLocator" xsi:type="object">Magento\FunctionalTestingFramework\Config\SchemaLocator\Section</argument> <argument name="domDocumentClass" xsi:type="string">Magento\FunctionalTestingFramework\Page\Config\SectionDom</argument> @@ -102,7 +102,7 @@ <item name="/sections/section" xsi:type="string">name</item> <item name="/sections/section/element" xsi:type="string">name</item> </argument> - <argument name="fileName" xsi:type="string">*Section.xml</argument> + <argument name="fileName" xsi:type="string">/Section\.xml$/</argument> <argument name="defaultScope" xsi:type="string">Section</argument> </arguments> </virtualType> @@ -150,20 +150,21 @@ </virtualType> <virtualType name="Magento\FunctionalTestingFramework\Config\Reader\DataProfile" type="Magento\FunctionalTestingFramework\Config\Reader\MftfFilesystem"> <arguments> - <argument name="fileResolver" xsi:type="object">Magento\FunctionalTestingFramework\Config\FileResolver\Module</argument> + <argument name="fileResolver" xsi:type="object">Magento\FunctionalTestingFramework\Config\FileResolver\Mask</argument> <argument name="converter" xsi:type="object">Magento\FunctionalTestingFramework\Config\Converter</argument> <argument name="domDocumentClass" xsi:type="string">Magento\FunctionalTestingFramework\DataGenerator\Config\Dom</argument> <argument name="schemaLocator" xsi:type="object">Magento\FunctionalTestingFramework\Config\SchemaLocator\DataProfile</argument> <argument name="idAttributes" xsi:type="array"> <item name="/entities/entity" xsi:type="string">name</item> <item name="/entities/entity/(data|array)" xsi:type="string">key</item> + <item name="/entities/entity/array/item" xsi:type="string">name</item> <item name="/entities/entity/requiredEntity" xsi:type="string">type</item> </argument> <argument name="mergeablePaths" xsi:type="array"> <item name="/entities/entity/requiredEntity" xsi:type="string"/> <item name="/entities/entity/array" xsi:type="string"/> </argument> - <argument name="fileName" xsi:type="string">*Data.xml</argument> + <argument name="fileName" xsi:type="string">/Data\.xml$/</argument> <argument name="defaultScope" xsi:type="string">Data</argument> </arguments> </virtualType> @@ -187,7 +188,7 @@ </virtualType> <virtualType name="Magento\FunctionalTestingFramework\Config\Reader\Metadata" type="Magento\FunctionalTestingFramework\Config\Reader\MftfFilesystem"> <arguments> - <argument name="fileResolver" xsi:type="object">Magento\FunctionalTestingFramework\Config\FileResolver\Module</argument> + <argument name="fileResolver" xsi:type="object">Magento\FunctionalTestingFramework\Config\FileResolver\Mask</argument> <argument name="converter" xsi:type="object">Magento\FunctionalTestingFramework\Config\Converter</argument> <argument name="domDocumentClass" xsi:type="string">Magento\FunctionalTestingFramework\DataGenerator\Config\OperationDom</argument> <argument name="schemaLocator" xsi:type="object">Magento\FunctionalTestingFramework\Config\SchemaLocator\Metadata</argument> @@ -198,7 +199,7 @@ <argument name="mergeablePaths" xsi:type="array"> <item name="/operations/operation/object" xsi:type="string"/> </argument> - <argument name="fileName" xsi:type="string">*-meta.xml</argument> + <argument name="fileName" xsi:type="string">/Meta\.xml$/</argument> <argument name="defaultScope" xsi:type="string">Metadata</argument> </arguments> </virtualType> @@ -212,7 +213,7 @@ </virtualType> <virtualType name="Magento\FunctionalTestingFramework\Config\Reader\TestData" type="Magento\FunctionalTestingFramework\Config\Reader\MftfFilesystem"> <arguments> - <argument name="fileResolver" xsi:type="object">Magento\FunctionalTestingFramework\Config\FileResolver\Module</argument> + <argument name="fileResolver" xsi:type="object">Magento\FunctionalTestingFramework\Config\FileResolver\Mask</argument> <argument name="converter" xsi:type="object">Magento\FunctionalTestingFramework\Config\TestDataConverter</argument> <argument name="schemaLocator" xsi:type="object">Magento\FunctionalTestingFramework\Config\SchemaLocator\TestData</argument> <argument name="domDocumentClass" xsi:type="string">Magento\FunctionalTestingFramework\Test\Config\Dom</argument> @@ -222,6 +223,8 @@ <item name="/tests/test/(createData|updateData|getData)/requiredEntity" xsi:type="string">createDataKey</item> <item name="/tests/test/(createData|updateData|getData)/field" xsi:type="string">key</item> <item name="/tests/test/(actionGroup|&commonTestActions;)" xsi:type="string">stepKey</item> + <item name="/tests/test/helper/argument" xsi:type="string">name</item> + <item name="/tests/test/(before|after)/helper/argument" xsi:type="string">name</item> <item name="/tests/test/(before|after)/(actionGroup|&commonTestActions;)" xsi:type="string">stepKey</item> <item name="/tests/test/remove" xsi:type="string">keyForRemoval</item> <item name="/tests/test/(before|after)/remove" xsi:type="string">keyForRemoval</item> @@ -229,7 +232,7 @@ <item name="/tests/test/(before|after)/(createData|updateData|getData)/field" xsi:type="string">key</item> <item name="/tests/test/annotations(/group)+" xsi:type="string">value</item> </argument> - <argument name="fileName" xsi:type="string">*.xml</argument> + <argument name="fileName" xsi:type="string">/\.xml$/</argument> <argument name="defaultScope" xsi:type="string">Test</argument> </arguments> </virtualType> @@ -239,6 +242,8 @@ <argument name="assocArrayAttributes" xsi:type="array"> <item name="/tests/test/(actionGroup|&commonTestActions;)" xsi:type="string">stepKey</item> <item name="/tests/test/(before|after)/(actionGroup|&commonTestActions;)" xsi:type="string">stepKey</item> + <item name="/tests/test/helper/argument" xsi:type="string">name</item> + <item name="/tests/test/(before|after)/helper/argument" xsi:type="string">name</item> <item name="/tests/test/remove" xsi:type="string">keyForRemoval</item> <item name="/tests/test/(before|after)/remove" xsi:type="string">keyForRemoval</item> <item name="/tests/test" xsi:type="string">name</item> @@ -286,25 +291,26 @@ <virtualType name="Magento\FunctionalTestingFramework\Config\SchemaLocator\ActionGroup" type="Magento\FunctionalTestingFramework\Config\SchemaLocator"> <arguments> - <argument name="schemaPath" xsi:type="string">Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd</argument> + <argument name="schemaPath" xsi:type="string">Magento/FunctionalTestingFramework/Test/etc/mergedActionGroupSchema.xsd</argument> </arguments> </virtualType> <virtualType name="Magento\FunctionalTestingFramework\Config\Reader\ActionGroupData" type="Magento\FunctionalTestingFramework\Config\Reader\MftfFilesystem"> <arguments> - <argument name="fileResolver" xsi:type="object">Magento\FunctionalTestingFramework\Config\FileResolver\Module</argument> + <argument name="fileResolver" xsi:type="object">Magento\FunctionalTestingFramework\Config\FileResolver\Mask</argument> <argument name="converter" xsi:type="object">Magento\FunctionalTestingFramework\Config\ActionGroupDataConverter</argument> <argument name="schemaLocator" xsi:type="object">Magento\FunctionalTestingFramework\Config\SchemaLocator\ActionGroup</argument> <argument name="domDocumentClass" xsi:type="string">Magento\FunctionalTestingFramework\Test\Config\ActionGroupDom</argument> <argument name="idAttributes" xsi:type="array"> <item name="/actionGroups/actionGroup" xsi:type="string">name</item> <item name="/actionGroups/actionGroup/arguments/argument" xsi:type="string">name</item> + <item name="/actionGroups/actionGroup/helper/argument" xsi:type="string">name</item> <item name="/actionGroups/actionGroup/(&commonTestActions;)" xsi:type="string">stepKey</item> <item name="/actionGroups/actionGroup/(createData|updateData|getData)/requiredEntity" xsi:type="string">createDataKey</item> <item name="/actionGroups/actionGroup/(createData|updateData|getData)/field" xsi:type="string">key</item> <item name="/actionGroups/actionGroup/remove" xsi:type="string">keyForRemoval</item> </argument> - <argument name="fileName" xsi:type="string">*ActionGroup.xml</argument> + <argument name="fileName" xsi:type="string">/ActionGroup\.xml$/</argument> <argument name="defaultScope" xsi:type="string">ActionGroup</argument> </arguments> </virtualType> @@ -316,6 +322,7 @@ <item name="/actionGroups/actionGroup/remove" xsi:type="string">keyForRemoval</item> <item name="/actionGroups/actionGroup" xsi:type="string">name</item> <item name="/actionGroups/actionGroup/arguments/argument" xsi:type="string">name</item> + <item name="/actionGroups/actionGroup/helper/argument" xsi:type="string">name</item> <item name="/actionGroups/actionGroup/(createData|updateData|getData)/requiredEntity" xsi:type="string">createDataKey</item> <item name="/actionGroups/actionGroup/(createData|updateData|getData)/field" xsi:type="string">key</item> </argument> @@ -353,14 +360,15 @@ </virtualType> <virtualType name="Magento\FunctionalTestingFramework\Config\SchemaLocator\SuiteData" type="Magento\FunctionalTestingFramework\Config\SchemaLocator"> <arguments> - <argument name="schemaPath" xsi:type="string">Magento/FunctionalTestingFramework/Suite/etc/suiteSchema.xsd</argument> + <argument name="schemaPath" xsi:type="string">Magento/FunctionalTestingFramework/Suite/etc/mergedSuiteSchema.xsd</argument> </arguments> </virtualType> - <virtualType name="Magento\FunctionalTestingFramework\Config\Reader\SuiteData" type="Magento\FunctionalTestingFramework\Config\Reader\Filesystem"> + <virtualType name="Magento\FunctionalTestingFramework\Config\Reader\SuiteData" type="Magento\FunctionalTestingFramework\Config\Reader\MftfFilesystem"> <arguments> <argument name="fileResolver" xsi:type="object">Magento\FunctionalTestingFramework\Config\FileResolver\Root</argument> <argument name="converter" xsi:type="object">Magento\FunctionalTestingFramework\Config\SuiteDataConverter</argument> <argument name="schemaLocator" xsi:type="object">Magento\FunctionalTestingFramework\Config\SchemaLocator\SuiteData</argument> + <argument name="domDocumentClass" xsi:type="string">Magento\FunctionalTestingFramework\Suite\Config\SuiteDom</argument> <argument name="idAttributes" xsi:type="array"> <item name="/suites/suite" xsi:type="string">name</item> <item name="/suites/suite/(before|after)/remove" xsi:type="string">keyForRemoval</item> @@ -371,7 +379,7 @@ <item name="/suites/suite/include/(group|test|module)" xsi:type="string">name</item> <item name="/suites/suite/exclude/(group|test|module)" xsi:type="string">name</item> </argument> - <argument name="fileName" xsi:type="string">*.xml</argument> + <argument name="fileName" xsi:type="string">/\.xml$/</argument> <argument name="defaultScope" xsi:type="string">Suite</argument> </arguments> </virtualType> diff --git a/src/Magento/FunctionalTestingFramework/Allure/Adapter/MagentoAllureAdapter.php b/src/Magento/FunctionalTestingFramework/Allure/Adapter/MagentoAllureAdapter.php index 4bce08d9f..d5efbbc0f 100644 --- a/src/Magento/FunctionalTestingFramework/Allure/Adapter/MagentoAllureAdapter.php +++ b/src/Magento/FunctionalTestingFramework/Allure/Adapter/MagentoAllureAdapter.php @@ -5,12 +5,18 @@ */ namespace Magento\FunctionalTestingFramework\Allure\Adapter; +use Codeception\Codecept; +use Codeception\Test\Cest; use Codeception\Step\Comment; use Magento\FunctionalTestingFramework\Suite\Handlers\SuiteObjectHandler; use Magento\FunctionalTestingFramework\Test\Objects\ActionGroupObject; -use \Magento\FunctionalTestingFramework\Util\TestGenerator; +use Magento\FunctionalTestingFramework\Test\Objects\ActionObject; +use Magento\FunctionalTestingFramework\Util\TestGenerator; +use Yandex\Allure\Adapter\Model\Failure; +use Yandex\Allure\Adapter\Model\Provider; use Yandex\Allure\Adapter\Model\Status; use Yandex\Allure\Adapter\Model\Step; +use Yandex\Allure\Adapter\Allure; use Yandex\Allure\Codeception\AllureCodeception; use Yandex\Allure\Adapter\Event\StepStartedEvent; use Yandex\Allure\Adapter\Event\StepFinishedEvent; @@ -18,9 +24,11 @@ use Yandex\Allure\Adapter\Event\TestCaseFailedEvent; use Yandex\Allure\Adapter\Event\TestCaseFinishedEvent; use Yandex\Allure\Adapter\Event\TestCaseBrokenEvent; +use Yandex\Allure\Adapter\Event\AddAttachmentEvent; use Codeception\Event\FailEvent; use Codeception\Event\SuiteEvent; use Codeception\Event\StepEvent; +use Codeception\Event\TestEvent; /** * Class MagentoAllureAdapter @@ -41,6 +49,13 @@ class MagentoAllureAdapter extends AllureCodeception */ private $testFiles = []; + /** + * Boolean value to indicate if steps are invisible steps + * + * @var boolean + */ + private $atInvisibleSteps = false; + /** * Array of group values passed to test runner command * @@ -48,7 +63,7 @@ class MagentoAllureAdapter extends AllureCodeception */ private function getGroup() { - if ($this->options['groups'] != null) { + if ($this->options['groups'] !== null) { return $this->options['groups'][0]; } return null; @@ -64,7 +79,7 @@ public function suiteBefore(SuiteEvent $suiteEvent) { $changeSuiteEvent = $suiteEvent; - if ($this->getGroup() != null) { + if ($this->getGroup() !== null) { $suite = $suiteEvent->getSuite(); $suiteName = ($suite->getName()) . "\\" . $this->sanitizeGroupName($this->getGroup()); @@ -106,6 +121,7 @@ private function sanitizeGroupName($group) // if we can't find this group in the generated suites we have to assume that the group was split for generation $groupNameSplit = explode("_", $group); array_pop($groupNameSplit); + array_pop($groupNameSplit); $originalName = implode("_", $groupNameSplit); // confirm our original name is one of the existing suite names otherwise just return the original group name @@ -125,6 +141,19 @@ private function sanitizeGroupName($group) */ public function stepBefore(StepEvent $stepEvent) { + $stepAction = $stepEvent->getStep()->getAction(); + + // Set atInvisibleSteps flag and return if step is in INVISIBLE_STEP_ACTIONS + if (in_array($stepAction, ActionObject::INVISIBLE_STEP_ACTIONS)) { + $this->atInvisibleSteps = true; + return; + } + + // Set back atInvisibleSteps flag + if ($this->atInvisibleSteps && !in_array($stepAction, ActionObject::INVISIBLE_STEP_ACTIONS)) { + $this->atInvisibleSteps = false; + } + //Hard set to 200; we don't expose this config in MFTF $argumentsLength = 200; $stepKey = null; @@ -134,11 +163,9 @@ public function stepBefore(StepEvent $stepEvent) } // DO NOT alter action if actionGroup is starting, need the exact actionGroup name for good logging - if (strpos($stepEvent->getStep()->getAction(), ActionGroupObject::ACTION_GROUP_CONTEXT_START) !== false - || $stepEvent->getStep() instanceof Comment + if (strpos($stepAction, ActionGroupObject::ACTION_GROUP_CONTEXT_START) === false + && !($stepEvent->getStep() instanceof Comment) ) { - $stepAction = $stepEvent->getStep()->getAction(); - } else { $stepAction = $stepEvent->getStep()->getHumanizedActionWithoutArguments(); } $stepArgs = $stepEvent->getStep()->getArgumentsAsString($argumentsLength); @@ -169,9 +196,18 @@ public function stepBefore(StepEvent $stepEvent) */ public function stepAfter(StepEvent $stepEvent = null) { + // Simply return if step is INVISIBLE_STEP_ACTIONS + if ($this->atInvisibleSteps) { + if ($stepEvent->getStep()->hasFailed()) { + $this->atInvisibleSteps = false; + } + return; + } + if ($stepEvent->getStep()->hasFailed()) { $this->getLifecycle()->fire(new StepFailedEvent()); } + $this->getLifecycle()->fire(new StepFinishedEvent()); } @@ -217,11 +253,17 @@ public function testError(FailEvent $failEvent) /** * Override of parent method, polls stepStorage for testcase and formats it according to actionGroup nesting. - * + * @param TestEvent $testEvent + * @throws \Yandex\Allure\Adapter\AllureException * @return void */ - public function testEnd() + public function testEnd(TestEvent $testEvent) { + $test = $this->getLifecycle()->getTestCaseStorage()->get(); + // update testClass label to consolidate re-try reporting + $this->formatAllureTestClassName($test); + // Peek top of testCaseStorage to check of failure + $testFailed = $test->getFailure(); // Pops top of stepStorage, need to add it back in after processing $rootStep = $this->getLifecycle()->getStepStorage()->pollLast(); $formattedSteps = []; @@ -229,6 +271,7 @@ public function testEnd() $actionGroupStepKey = null; foreach ($rootStep->getSteps() as $step) { + $this->removeAttachments($step, $testFailed); $stepKey = str_replace($actionGroupStepKey, '', $step->getName()); if ($stepKey !== '[]' && $stepKey !== null) { $step->setName($stepKey); @@ -281,9 +324,28 @@ function () use ($rootStep, $formattedSteps) { $this->getLifecycle()->getStepStorage()->put($rootStep); + $this->addAttachmentEvent($testEvent); + $this->getLifecycle()->fire(new TestCaseFinishedEvent()); } + /** + * Fire add attachment event + * @param TestEvent $testEvent + * @throws \Yandex\Allure\Adapter\AllureException + * @return void + */ + private function addAttachmentEvent(TestEvent $testEvent) + { + // attachments supported since Codeception 3.0 + if (version_compare(Codecept::VERSION, '3.0.0') > -1 && $testEvent->getTest() instanceof Cest) { + $artifacts = $testEvent->getTest()->getMetadata()->getReports(); + foreach ($artifacts as $name => $artifact) { + Allure::lifecycle()->fire(new AddAttachmentEvent($artifact, $name, null)); + } + } + } + /** * Reads action group stepKey from step. * @@ -326,4 +388,60 @@ private function retrieveStepKey($stepLine) return $stepKey; } + + /** + * Removes attachments from step depending on MFTF configuration + * @param Step $step + * @param Failure $testFailed + * @return void + */ + private function removeAttachments($step, $testFailed) + { + //Remove Attachments if verbose flag is not true AND test did not fail + if (getenv('VERBOSE_ARTIFACTS') !== "true" && $testFailed === null) { + foreach ($step->getAttachments() as $index => $attachment) { + $step->removeAttachment($index); + unlink(Provider::getOutputDirectory() . DIRECTORY_SEPARATOR . $attachment->getSource()); + } + } + } + + /** + * Format testClass label to consolidate re-try reporting for groups split for parallel execution + * @param TestCase $test + * @return void + */ + private function formatAllureTestClassName($test) + { + if ($this->getGroup() !== null) { + foreach ($test->getLabels() as $label) { + if ($label->getName() === 'testClass') { + $originalTestClass = $this->sanitizeTestClassLabel($label->getValue()); + call_user_func(\Closure::bind( + function () use ($label, $originalTestClass) { + $label->value = $originalTestClass; + }, + null, + $label + )); + break; + } + } + } + } + + /** + * Function which sanitizes testClass label for split group runs + * @param string $testClass + * @return string + */ + private function sanitizeTestClassLabel($testClass) + { + $originalTestClass = $testClass; + $originalGroupName = $this->sanitizeGroupName($this->getGroup()); + if ($originalGroupName !== $this->getGroup()) { + $originalTestClass = str_replace($this->getGroup(), $originalGroupName, $testClass); + } + return $originalTestClass; + } } diff --git a/src/Magento/FunctionalTestingFramework/Allure/AllureHelper.php b/src/Magento/FunctionalTestingFramework/Allure/AllureHelper.php index d8f1c63d8..cc0251a48 100644 --- a/src/Magento/FunctionalTestingFramework/Allure/AllureHelper.php +++ b/src/Magento/FunctionalTestingFramework/Allure/AllureHelper.php @@ -3,38 +3,66 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\FunctionalTestingFramework\Allure; +use Magento\FunctionalTestingFramework\Allure\Event\AddUniqueAttachmentEvent; +use Magento\FunctionalTestingFramework\ObjectManagerFactory; use Yandex\Allure\Adapter\Allure; -use Yandex\Allure\Adapter\Event\AddAttachmentEvent; +use Yandex\Allure\Adapter\AllureException; class AllureHelper { /** - * Adds attachment to the current step + * Adds attachment to the current step. + * * @param mixed $data * @param string $caption - * @throws \Yandex\Allure\Adapter\AllureException + * * @return void + * @throws AllureException */ - public static function addAttachmentToCurrentStep($data, $caption) + public static function addAttachmentToCurrentStep($data, $caption): void { - Allure::lifecycle()->fire(new AddAttachmentEvent($data, $caption)); + /** @var AddUniqueAttachmentEvent $event */ + $event = ObjectManagerFactory::getObjectManager()->create( + AddUniqueAttachmentEvent::class, + [ + 'filePathOrContents' => $data, + 'caption' => $caption + ] + ); + Allure::lifecycle()->fire($event); } /** * Adds Attachment to the last executed step. * Use this when adding attachments outside of an $I->doSomething() step/context. + * * @param mixed $data * @param string $caption + * * @return void */ - public static function addAttachmentToLastStep($data, $caption) + public static function addAttachmentToLastStep($data, $caption): void { $rootStep = Allure::lifecycle()->getStepStorage()->getLast(); $trueLastStep = array_last($rootStep->getSteps()); - $attachmentEvent = new AddAttachmentEvent($data, $caption); + if ($trueLastStep === null) { + // Nothing to attach to; do not fire off allure event + return; + } + + /** @var AddUniqueAttachmentEvent $attachmentEvent */ + $attachmentEvent = ObjectManagerFactory::getObjectManager()->create( + AddUniqueAttachmentEvent::class, + [ + 'filePathOrContents' => $data, + 'caption' => $caption + ] + ); $attachmentEvent->process($trueLastStep); } } diff --git a/src/Magento/FunctionalTestingFramework/Allure/Event/AddUniqueAttachmentEvent.php b/src/Magento/FunctionalTestingFramework/Allure/Event/AddUniqueAttachmentEvent.php new file mode 100644 index 000000000..af0ed2c52 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Allure/Event/AddUniqueAttachmentEvent.php @@ -0,0 +1,106 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\FunctionalTestingFramework\Allure\Event; + +use Magento\FunctionalTestingFramework\Config\MftfApplicationConfig; +use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; +use Symfony\Component\Mime\MimeTypes; +use Yandex\Allure\Adapter\AllureException; +use Yandex\Allure\Adapter\Event\AddAttachmentEvent; + +class AddUniqueAttachmentEvent extends AddAttachmentEvent +{ + private const DEFAULT_FILE_EXTENSION = 'txt'; + private const DEFAULT_MIME_TYPE = 'text/plain'; + + /** + * Near copy of parent function, added uniqid call for filename to prevent buggy allure behavior. + * + * @param mixed $filePathOrContents + * @param string $type + * + * @return string + * @throws AllureException + */ + public function getAttachmentFileName($filePathOrContents, $type): string + { + $filePath = $filePathOrContents; + + if (!is_string($filePath) || !file_exists($filePath) || !is_file($filePath)) { + //Save contents to temporary file + $filePath = tempnam(sys_get_temp_dir(), 'allure-attachment'); + if (!file_put_contents($filePath, $filePathOrContents)) { + throw new AllureException("Failed to save attachment contents to $filePath"); + } + } + + if (!isset($type)) { + $type = $this->guessFileMimeType($filePath); + } + $fileExtension = $this->guessFileExtension($type); + $fileSha1 = uniqid(sha1_file($filePath)); + $outputPath = parent::getOutputPath($fileSha1, $fileExtension); + + if (!$this->copyFile($filePath, $outputPath)) { + throw new AllureException("Failed to copy attachment from $filePath to $outputPath."); + } + + return $this->getOutputFileName($fileSha1, $fileExtension); + } + + /** + * Copies file from one path to another. Wrapper for mocking in unit test. + * + * @param string $filePath + * @param string $outputPath + * + * @return boolean + * @throws TestFrameworkException + */ + private function copyFile(string $filePath, string $outputPath): bool + { + if (MftfApplicationConfig::getConfig()->getPhase() === MftfApplicationConfig::UNIT_TEST_PHASE) { + return true; + } + return copy($filePath, $outputPath); + } + + /** + * Copy of parent private function. + * + * @param string $filePath + * + * @return string + */ + private function guessFileMimeType(string $filePath): string + { + $type = MimeTypes::getDefault()->guessMimeType($filePath); + + if (!isset($type)) { + return self::DEFAULT_MIME_TYPE; + } + return $type; + } + + /** + * Copy of parent private function. + * + * @param string $mimeType + * + * @return string + */ + private function guessFileExtension(string $mimeType): string + { + $candidate = MimeTypes::getDefault()->getExtensions($mimeType); + + if (empty($candidate)) { + return self::DEFAULT_FILE_EXTENSION; + } + return reset($candidate); + } +} diff --git a/src/Magento/FunctionalTestingFramework/Code/Reader/ClassReader.php b/src/Magento/FunctionalTestingFramework/Code/Reader/ClassReader.php index bc29d47fc..f3e67e35b 100644 --- a/src/Magento/FunctionalTestingFramework/Code/Reader/ClassReader.php +++ b/src/Magento/FunctionalTestingFramework/Code/Reader/ClassReader.php @@ -13,6 +13,7 @@ class ClassReader implements ClassReaderInterface * @param string $className * @return array|null * @throws \ReflectionException + * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ public function getConstructor($className) { @@ -24,9 +25,13 @@ public function getConstructor($className) /** @var $parameter \ReflectionParameter */ foreach ($constructor->getParameters() as $parameter) { try { + $paramType = $parameter->getType(); + $name = ($paramType && method_exists($paramType, 'isBuiltin') && !$paramType->isBuiltin()) + ? new \ReflectionClass($paramType->getName()) + : null; $result[] = [ $parameter->getName(), - $parameter->getClass() !== null ? $parameter->getClass()->getName() : null, + $name !== null ? $name->getName() : null, !$parameter->isOptional(), $parameter->isOptional() ? ($parameter->isDefaultValueAvailable() ? $parameter->getDefaultValue() : null) diff --git a/src/Magento/FunctionalTestingFramework/Codeception/Subscriber/Console.php b/src/Magento/FunctionalTestingFramework/Codeception/Subscriber/Console.php index 0ba1a3b2f..961f68440 100644 --- a/src/Magento/FunctionalTestingFramework/Codeception/Subscriber/Console.php +++ b/src/Magento/FunctionalTestingFramework/Codeception/Subscriber/Console.php @@ -7,16 +7,27 @@ namespace Magento\FunctionalTestingFramework\Codeception\Subscriber; use Codeception\Event\StepEvent; +use Codeception\Event\TestEvent; use Codeception\Lib\Console\Message; use Codeception\Step; use Codeception\Step\Comment; use Codeception\Test\Interfaces\ScenarioDriven; use Magento\FunctionalTestingFramework\Test\Objects\ActionGroupObject; +use Magento\FunctionalTestingFramework\Test\Objects\ActionObject; +use Magento\FunctionalTestingFramework\Util\Logger\LoggingUtil; use Magento\FunctionalTestingFramework\Util\TestGenerator; use Symfony\Component\Console\Formatter\OutputFormatter; +/** + * @SuppressWarnings(PHPMD) + */ class Console extends \Codeception\Subscriber\Console { + /** + * Regular expresion to find deprecated notices. + */ + const DEPRECATED_NOTICE = '/<li>(?<deprecatedMessage>.*?)<\/li>/m'; + /** * Test files cache. * @@ -31,6 +42,13 @@ class Console extends \Codeception\Subscriber\Console */ private $actionGroupStepKey = null; + /** + * Boolean value to indicate if steps are invisible steps + * + * @var boolean + */ + private $atInvisibleSteps = false; + /** * Console constructor. Parent constructor requires codeception CLI options, and does not have its own configs. * Constructor is only different than parent due to the way Codeception instantiates Extensions. @@ -45,6 +63,36 @@ public function __construct($extensionOptions = [], $options = []) parent::__construct($options); } + /** + * Triggered event before each test. + * + * @param TestEvent $e + * @return void + * @throws \Exception + */ + public function startTest(TestEvent $e) + { + $test = $e->getTest()->getTestClass(); + try { + $testReflection = new \ReflectionClass($test); + $isDeprecated = preg_match_all(self::DEPRECATED_NOTICE, $testReflection->getDocComment(), $match); + if ($isDeprecated) { + $this->message('DEPRECATION NOTICE(S): ') + ->style('debug') + ->writeln(); + foreach ($match['deprecatedMessage'] as $deprecatedMessage) { + $this->message(' - ' . $deprecatedMessage) + ->style('debug') + ->writeln(); + } + } + } catch (\ReflectionException $e) { + LoggingUtil::getInstance()->getLogger(self::class)->error($e->getMessage(), $e->getTrace()); + } + + parent::startTest($e); + } + /** * Printing stepKey in before step action. * @@ -57,8 +105,21 @@ public function beforeStep(StepEvent $e) return; } + $stepAction = $e->getStep()->getAction(); + + // Set atInvisibleSteps flag and return if step is in INVISIBLE_STEP_ACTIONS + if (in_array($stepAction, ActionObject::INVISIBLE_STEP_ACTIONS)) { + $this->atInvisibleSteps = true; + return; + } + + // Set back atInvisibleSteps flag + if ($this->atInvisibleSteps && !in_array($stepAction, ActionObject::INVISIBLE_STEP_ACTIONS)) { + $this->atInvisibleSteps = false; + } + $metaStep = $e->getStep()->getMetaStep(); - if ($metaStep and $this->metaStep != $metaStep) { + if ($metaStep and $this->metaStep !== $metaStep) { $this->message(' ' . $metaStep->getPrefix()) ->style('bold') ->append($metaStep->__toString()) @@ -77,9 +138,14 @@ public function beforeStep(StepEvent $e) */ public function afterStep(StepEvent $e) { - parent::afterStep($e); + // Do usual after step if step is not INVISIBLE_STEP_ACTIONS + if (!$this->atInvisibleSteps) { + parent::afterStep($e); + } + if ($e->getStep()->hasFailed()) { $this->actionGroupStepKey = null; + $this->atInvisibleSteps = false; } } @@ -92,7 +158,7 @@ public function afterStep(StepEvent $e) */ private function printStepKeys(Step $step) { - if ($step instanceof Comment and $step->__toString() == '') { + if ($step instanceof Comment and $step->__toString() === '') { return; // don't print empty comments } @@ -127,7 +193,7 @@ private function printStepKeys(Step $step) $stepString = str_replace( [ActionGroupObject::ACTION_GROUP_CONTEXT_START, ActionGroupObject::ACTION_GROUP_CONTEXT_END], '', - $step->toString(150) + $step->toString(1000) ); $msg->append(OutputFormatter::escape($stepString)); diff --git a/src/Magento/FunctionalTestingFramework/Composer/ComposerInstall.php b/src/Magento/FunctionalTestingFramework/Composer/ComposerInstall.php index f10c0b418..151fdc86e 100644 --- a/src/Magento/FunctionalTestingFramework/Composer/ComposerInstall.php +++ b/src/Magento/FunctionalTestingFramework/Composer/ComposerInstall.php @@ -50,7 +50,7 @@ public function isInstalledPackageOfType($packageName, $packageType) { /** @var CompletePackageInterface $package */ foreach ($this->getLocker()->getLockedRepository()->getPackages() as $package) { - if (($package->getName() == $packageName) && ($package->getType() == $packageType)) { + if (($package->getName() === $packageName) && ($package->getType() === $packageType)) { return true; } } @@ -67,7 +67,7 @@ public function getInstalledTestPackages() $packages = []; /** @var CompletePackageInterface $package */ foreach ($this->getLocker()->getLockedRepository()->getPackages() as $package) { - if ($package->getType() == self::TEST_MODULE_PACKAGE_TYPE) { + if ($package->getType() === self::TEST_MODULE_PACKAGE_TYPE) { $packages[$package->getName()] = [ self::PACKAGE_NAME => $package->getName(), self::PACKAGE_TYPE => $package->getType(), diff --git a/src/Magento/FunctionalTestingFramework/Composer/ComposerPackage.php b/src/Magento/FunctionalTestingFramework/Composer/ComposerPackage.php index 0a6bfdca2..5e2d21c15 100644 --- a/src/Magento/FunctionalTestingFramework/Composer/ComposerPackage.php +++ b/src/Magento/FunctionalTestingFramework/Composer/ComposerPackage.php @@ -116,7 +116,7 @@ public function getSuggestedMagentoModules() */ public function isMftfTestPackage() { - return ($this->getType() == self::TEST_MODULE_PACKAGE_TYPE) ? true : false; + return $this->getType() === self::TEST_MODULE_PACKAGE_TYPE; } /** diff --git a/src/Magento/FunctionalTestingFramework/Config/Converter.php b/src/Magento/FunctionalTestingFramework/Config/Converter.php index cf61805dc..14b96907e 100644 --- a/src/Magento/FunctionalTestingFramework/Config/Converter.php +++ b/src/Magento/FunctionalTestingFramework/Config/Converter.php @@ -91,7 +91,7 @@ protected function convertXml($elements) foreach ($elements as $element) { if ($element instanceof \DOMElement) { - if ($element->getAttribute('remove') == 'true') { + if ($element->getAttribute('remove') === 'true') { // Remove element continue; } @@ -119,7 +119,7 @@ protected function convertXml($elements) } elseif (!empty($elementData)) { $result[$element->nodeName][] = $elementData; } - } elseif ($element->nodeType == XML_TEXT_NODE && trim($element->nodeValue) != '') { + } elseif ($element->nodeType === XML_TEXT_NODE && trim($element->nodeValue) !== '') { return ['value' => $element->nodeValue]; } } @@ -156,9 +156,9 @@ protected function getElementKey(\DOMElement $element) protected function isKeyAttribute(\DOMElement $element, \DOMAttr $attribute) { if (isset($this->idAttributes[$element->nodeName])) { - return $attribute->name == $this->idAttributes[$element->nodeName]; + return $attribute->name === $this->idAttributes[$element->nodeName]; } else { - return $attribute->name == self::NAME_ATTRIBUTE; + return $attribute->name === self::NAME_ATTRIBUTE; } } @@ -174,7 +174,7 @@ protected function getAttributes(\DOMElement $element) if ($element->hasAttributes()) { /** @var \DomAttr $attribute */ foreach ($element->attributes as $attribute) { - if (trim($attribute->nodeValue) != '' && !$this->isKeyAttribute($element, $attribute)) { + if (trim($attribute->nodeValue) !== '' && !$this->isKeyAttribute($element, $attribute)) { $attributes[$attribute->nodeName] = $this->castNumeric($attribute->nodeValue); } } diff --git a/src/Magento/FunctionalTestingFramework/Config/Converter/Dom/Flat.php b/src/Magento/FunctionalTestingFramework/Config/Converter/Dom/Flat.php index 001a811d9..742ef64f3 100644 --- a/src/Magento/FunctionalTestingFramework/Config/Converter/Dom/Flat.php +++ b/src/Magento/FunctionalTestingFramework/Config/Converter/Dom/Flat.php @@ -41,7 +41,7 @@ protected function getNodeAttributes(\DOMNode $node) $attributes = $node->attributes ?: []; /** @var \DOMNode $attribute */ foreach ($attributes as $attribute) { - if ($attribute->nodeType == XML_ATTRIBUTE_NODE) { + if ($attribute->nodeType === XML_ATTRIBUTE_NODE) { $result[$attribute->nodeName] = $attribute->nodeValue; } } @@ -77,7 +77,7 @@ public function convert(\DOMNode $source, $basePath = '') $value = []; /** @var \DOMNode $node */ foreach ($source->childNodes as $node) { - if ($node->nodeType == XML_ELEMENT_NODE) { + if ($node->nodeType === XML_ELEMENT_NODE) { $nodeName = $node->nodeName; $nodePath = $basePath . '/' . $nodeName; @@ -107,8 +107,8 @@ public function convert(\DOMNode $source, $basePath = '') } else { $value[$nodeName] = $nodeData; } - } elseif ($node->nodeType == XML_CDATA_SECTION_NODE - || ($node->nodeType == XML_TEXT_NODE && trim($node->nodeValue) != '') + } elseif ($node->nodeType === XML_CDATA_SECTION_NODE + || ($node->nodeType === XML_TEXT_NODE && trim($node->nodeValue) !== '') ) { $value = $node->nodeValue; break; diff --git a/src/Magento/FunctionalTestingFramework/Config/Dom.php b/src/Magento/FunctionalTestingFramework/Config/Dom.php index 6d10811ca..91de64ae1 100644 --- a/src/Magento/FunctionalTestingFramework/Config/Dom.php +++ b/src/Magento/FunctionalTestingFramework/Config/Dom.php @@ -207,7 +207,7 @@ protected function replaceNodeValue($parentPath, \DOMElement $node, \DOMElement */ protected function isTextNode($node) { - return $node->childNodes->length == 1 && $node->childNodes->item(0) instanceof \DOMText; + return $node->childNodes->length === 1 && $node->childNodes->item(0) instanceof \DOMText; } /** @@ -246,8 +246,8 @@ protected function getNodePathByParent(\DOMElement $node, $parentPath) $idAttribute = $this->nodeMergingConfig->getIdAttribute($path); if ($idAttribute) { foreach (explode('|', $idAttribute) as $idAttributeValue) { - if ($value = $node->getAttribute($idAttributeValue)) { - $path .= "[@{$idAttributeValue}='{$value}']"; + if ($node->hasAttribute($idAttributeValue)) { + $path .= "[@{$idAttributeValue}='" . $node->getAttribute($idAttributeValue) . "']"; break; } } @@ -273,7 +273,7 @@ protected function getMatchedNode($nodePath) $node = null; if ($matchedNodes->length > 1) { throw new \Exception("More than one node matching the query: {$nodePath}"); - } elseif ($matchedNodes->length == 1) { + } elseif ($matchedNodes->length === 1) { $node = $matchedNodes->item(0); } return $node; diff --git a/src/Magento/FunctionalTestingFramework/Config/Dom/ArrayNodeConfig.php b/src/Magento/FunctionalTestingFramework/Config/Dom/ArrayNodeConfig.php index 5f03450dc..d224bb882 100644 --- a/src/Magento/FunctionalTestingFramework/Config/Dom/ArrayNodeConfig.php +++ b/src/Magento/FunctionalTestingFramework/Config/Dom/ArrayNodeConfig.php @@ -64,7 +64,7 @@ public function __construct( public function isNumericArray($nodeXpath) { foreach ($this->numericArrays as $pathPattern) { - if ($this->nodePathMatcher->match($pathPattern, $nodeXpath)) { + if ($this->nodePathMatcher->pathMatch($pathPattern, $nodeXpath)) { return true; } } @@ -84,7 +84,7 @@ public function getAssocArrayKeyAttribute($nodeXpath) } foreach ($this->assocArrays as $pathPattern => $keyAttribute) { - if ($this->nodePathMatcher->match($pathPattern, $nodeXpath)) { + if ($this->nodePathMatcher->pathMatch($pathPattern, $nodeXpath)) { return $keyAttribute; } } diff --git a/src/Magento/FunctionalTestingFramework/Config/Dom/NodeMergingConfig.php b/src/Magento/FunctionalTestingFramework/Config/Dom/NodeMergingConfig.php index e4231c8ec..f02b1dc5c 100644 --- a/src/Magento/FunctionalTestingFramework/Config/Dom/NodeMergingConfig.php +++ b/src/Magento/FunctionalTestingFramework/Config/Dom/NodeMergingConfig.php @@ -44,7 +44,7 @@ public function __construct(NodePathMatcher $nodePathMatcher, array $idAttribute public function getIdAttribute($nodeXpath) { foreach ($this->idAttributes as $pathPattern => $idAttribute) { - if ($this->nodePathMatcher->match($pathPattern, $nodeXpath)) { + if ($this->nodePathMatcher->pathMatch($pathPattern, $nodeXpath)) { return $idAttribute; } } diff --git a/src/Magento/FunctionalTestingFramework/Config/Dom/NodePathMatcher.php b/src/Magento/FunctionalTestingFramework/Config/Dom/NodePathMatcher.php index 2857bbea6..b7c3f9185 100644 --- a/src/Magento/FunctionalTestingFramework/Config/Dom/NodePathMatcher.php +++ b/src/Magento/FunctionalTestingFramework/Config/Dom/NodePathMatcher.php @@ -17,7 +17,7 @@ class NodePathMatcher * @param string $xpathSubject Example: '/some[@attr="value"]/static/ns:path'. * @return boolean */ - public function match($pathPattern, $xpathSubject) + public function pathMatch($pathPattern, $xpathSubject) { $pathSubject = $this->simplifyXpath($xpathSubject); $pathPattern = '#^' . $pathPattern . '$#'; diff --git a/src/Magento/FunctionalTestingFramework/Config/FileResolver/Primary.php b/src/Magento/FunctionalTestingFramework/Config/FileResolver/Primary.php index 2376b3cf1..3a976944b 100644 --- a/src/Magento/FunctionalTestingFramework/Config/FileResolver/Primary.php +++ b/src/Magento/FunctionalTestingFramework/Config/FileResolver/Primary.php @@ -6,8 +6,10 @@ namespace Magento\FunctionalTestingFramework\Config\FileResolver; +use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; use Magento\FunctionalTestingFramework\Util\Iterator\File; use Magento\FunctionalTestingFramework\Config\FileResolverInterface; +use Magento\FunctionalTestingFramework\Util\Path\FilePathFormatter; /** * Provides the list of global configuration files. @@ -54,6 +56,7 @@ private function getFilePaths($filename, $scope) * @param string $filename * @param string $scope * @return array + * @throws TestFrameworkException */ private function getPathPatterns($filename, $scope) { @@ -69,8 +72,9 @@ private function getPathPatterns($filename, $scope) $defaultPath . DIRECTORY_SEPARATOR . $scope . DIRECTORY_SEPARATOR . $filename, $defaultPath . DIRECTORY_SEPARATOR . $scope . DIRECTORY_SEPARATOR . '*' . DIRECTORY_SEPARATOR . $filename, - FW_BP . DIRECTORY_SEPARATOR . $scope . DIRECTORY_SEPARATOR . $filename, - FW_BP . DIRECTORY_SEPARATOR . $scope . DIRECTORY_SEPARATOR . '*' . DIRECTORY_SEPARATOR . $filename + FilePathFormatter::format(FW_BP) . $scope . DIRECTORY_SEPARATOR . $filename, + FilePathFormatter::format(FW_BP) . $scope . DIRECTORY_SEPARATOR . '*' . DIRECTORY_SEPARATOR + . $filename ]; } return str_replace(DIRECTORY_SEPARATOR, DIRECTORY_SEPARATOR, $patterns); diff --git a/src/Magento/FunctionalTestingFramework/Config/FileResolver/Root.php b/src/Magento/FunctionalTestingFramework/Config/FileResolver/Root.php index 3b0940b28..230586852 100644 --- a/src/Magento/FunctionalTestingFramework/Config/FileResolver/Root.php +++ b/src/Magento/FunctionalTestingFramework/Config/FileResolver/Root.php @@ -7,9 +7,11 @@ namespace Magento\FunctionalTestingFramework\Config\FileResolver; use Magento\FunctionalTestingFramework\Config\FileResolverInterface; +use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; use Magento\FunctionalTestingFramework\Util\Iterator\File; +use Magento\FunctionalTestingFramework\Util\Path\FilePathFormatter; -class Root extends Module +class Root extends Mask { const ROOT_SUITE_DIR = "tests/_suite"; @@ -20,19 +22,33 @@ class Root extends Module * @param string $filename * @param string $scope * @return array|\Iterator,\Countable + * @throws TestFrameworkException */ public function get($filename, $scope) { - // first pick up the root level test suite dir + // First pick up the root level test suite dir $paths = glob( - TESTS_BP . DIRECTORY_SEPARATOR . self::ROOT_SUITE_DIR - . DIRECTORY_SEPARATOR . $filename + FilePathFormatter::format(TESTS_BP) . self::ROOT_SUITE_DIR + . DIRECTORY_SEPARATOR . '*.xml' ); - // then merge this path into the module based paths - // Since we are sharing this code with Module based resolution we will unncessarily glob against modules in the + // include root suite dir when running standalone version + $altPath = FilePathFormatter::format(MAGENTO_BP) . 'dev/tests/acceptance'; + + if (realpath($altPath) && ($altPath !== TESTS_BP)) { + $paths = array_merge( + $paths, + glob( + FilePathFormatter::format($altPath) . self::ROOT_SUITE_DIR + . DIRECTORY_SEPARATOR . '*.xml' + ) + ); + } + + // Then merge this path into the module based paths + // Since we are sharing this code with Module based resolution we will unnecessarily glob against modules in the // dev/tests dir tree, however as we plan to migrate to app/code this will be a temporary unneeded check. - $paths = array_merge($paths, $this->getPaths($filename, $scope)); + $paths = array_merge($paths, $this->getFileCollection($filename, $scope)); // create and return the iterator for these file paths $iterator = new File($paths); diff --git a/src/Magento/FunctionalTestingFramework/Config/MftfApplicationConfig.php b/src/Magento/FunctionalTestingFramework/Config/MftfApplicationConfig.php index 80db27de0..7685ac919 100644 --- a/src/Magento/FunctionalTestingFramework/Config/MftfApplicationConfig.php +++ b/src/Magento/FunctionalTestingFramework/Config/MftfApplicationConfig.php @@ -6,6 +6,7 @@ namespace Magento\FunctionalTestingFramework\Config; use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; +use Magento\FunctionalTestingFramework\Filter\FilterList; class MftfApplicationConfig { @@ -22,8 +23,14 @@ class MftfApplicationConfig */ const LEVEL_DEFAULT = "default"; const LEVEL_DEVELOPER = "developer"; - const LEVEL_NONE = "none"; - const MFTF_DEBUG_LEVEL = [self::LEVEL_DEFAULT, self::LEVEL_DEVELOPER, self::LEVEL_NONE]; + const MFTF_DEBUG_LEVEL = [self::LEVEL_DEFAULT, self::LEVEL_DEVELOPER]; + + /** + * Contains object with test filters. + * + * @var FilterList + */ + private $filterList; /** * Determines whether the user has specified a force option for generation @@ -74,14 +81,16 @@ class MftfApplicationConfig * @param boolean $verboseEnabled * @param string $debugLevel * @param boolean $allowSkipped + * @param array $filters * @throws TestFrameworkException */ private function __construct( $forceGenerate = false, $phase = self::EXECUTION_PHASE, $verboseEnabled = null, - $debugLevel = self::LEVEL_NONE, - $allowSkipped = false + $debugLevel = self::LEVEL_DEFAULT, + $allowSkipped = false, + $filters = [] ) { $this->forceGenerate = $forceGenerate; @@ -91,16 +100,18 @@ private function __construct( $this->phase = $phase; $this->verboseEnabled = $verboseEnabled; - switch ($debugLevel) { - case self::LEVEL_DEVELOPER: + if (!in_array(strtolower($debugLevel), self::MFTF_DEBUG_LEVEL)) { + throw new TestFrameworkException("{$debugLevel} is not a debug level. Use 'DEFAULT' or 'DEVELOPER'"); + } + switch (strtolower($debugLevel)) { case self::LEVEL_DEFAULT: - case self::LEVEL_NONE: - $this->debugLevel = $debugLevel; + $this->debugLevel = self::LEVEL_DEFAULT; break; default: $this->debugLevel = self::LEVEL_DEVELOPER; } $this->allowSkipped = $allowSkipped; + $this->filterList = new FilterList($filters); } /** @@ -112,6 +123,7 @@ private function __construct( * @param boolean $verboseEnabled * @param string $debugLevel * @param boolean $allowSkipped + * @param array $filters * @return void * @throws TestFrameworkException */ @@ -119,12 +131,20 @@ public static function create( $forceGenerate = false, $phase = self::EXECUTION_PHASE, $verboseEnabled = null, - $debugLevel = self::LEVEL_NONE, - $allowSkipped = false + $debugLevel = self::LEVEL_DEFAULT, + $allowSkipped = false, + $filters = [] ) { - if (self::$MFTF_APPLICATION_CONTEXT == null) { + if (self::$MFTF_APPLICATION_CONTEXT === null) { self::$MFTF_APPLICATION_CONTEXT = - new MftfApplicationConfig($forceGenerate, $phase, $verboseEnabled, $debugLevel, $allowSkipped); + new MftfApplicationConfig( + $forceGenerate, + $phase, + $verboseEnabled, + $debugLevel, + $allowSkipped, + $filters + ); } } @@ -139,7 +159,7 @@ public static function getConfig() // TODO explicitly set this with AcceptanceTester or MagentoWebDriver // during execution we cannot guarantee the use of the robofile so we return the default application config, // we don't want to set the application context in case the user explicitly does so at a later time. - if (self::$MFTF_APPLICATION_CONTEXT == null) { + if (self::$MFTF_APPLICATION_CONTEXT === null) { return new MftfApplicationConfig(); } @@ -174,7 +194,7 @@ public function verboseEnabled() */ public function getDebugLevel() { - return $this->debugLevel ?? getenv('MFTF_DEBUG'); + return $this->debugLevel; } /** @@ -196,4 +216,14 @@ public function getPhase() { return $this->phase; } + + /** + * Returns a class with registered filter list. + * + * @return FilterList + */ + public function getFilterList() + { + return $this->filterList; + } } diff --git a/src/Magento/FunctionalTestingFramework/Config/Reader/Filesystem.php b/src/Magento/FunctionalTestingFramework/Config/Reader/Filesystem.php index 2f2cb8ad3..8e36a63d7 100644 --- a/src/Magento/FunctionalTestingFramework/Config/Reader/Filesystem.php +++ b/src/Magento/FunctionalTestingFramework/Config/Reader/Filesystem.php @@ -6,6 +6,7 @@ namespace Magento\FunctionalTestingFramework\Config\Reader; use Magento\FunctionalTestingFramework\Config\MftfApplicationConfig; +use Magento\FunctionalTestingFramework\Exceptions\FastFailException; use Magento\FunctionalTestingFramework\Util\Logger\LoggingUtil; /** @@ -146,7 +147,8 @@ protected function readFiles($fileList) { /** @var \Magento\FunctionalTestingFramework\Config\Dom $configMerger */ $configMerger = null; - foreach ($fileList as $key => $content) { + $debugLevel = MftfApplicationConfig::getConfig()->getDebugLevel(); + foreach ($fileList as $content) { //check if file is empty and continue to next if it is if (!$this->verifyFileEmpty($content, $fileList->getFilename())) { continue; @@ -157,7 +159,7 @@ protected function readFiles($fileList) } else { $configMerger->merge($content); } - if (MftfApplicationConfig::getConfig()->getDebugLevel() === MftfApplicationConfig::LEVEL_DEVELOPER) { + if (strcasecmp($debugLevel, MftfApplicationConfig::LEVEL_DEVELOPER) === 0) { $this->validateSchema($configMerger, $fileList->getFilename()); } } catch (\Magento\FunctionalTestingFramework\Config\Dom\ValidationException $e) { @@ -208,7 +210,7 @@ protected function verifyFileEmpty($content, $fileName) { if (empty($content)) { if (MftfApplicationConfig::getConfig()->verboseEnabled()) { - LoggingUtil::getInstance()->getLogger(Filesystem::class)->warn( + LoggingUtil::getInstance()->getLogger(Filesystem::class)->warning( "XML File is empty.", ["File" => $fileName] ); @@ -235,10 +237,11 @@ protected function validateSchema($configMerger, $filename = null) $error = str_replace(PHP_EOL, "", $error); LoggingUtil::getInstance()->getLogger(Filesystem::class)->criticalFailure( "Schema validation error ", - ($filename ? [ "file"=> $filename, "error" => $error]: ["error" => $error]) + ($filename ? [ "file"=> $filename, "error" => $error]: ["error" => $error]), + true ); } - throw new \Exception("Schema validation errors found in xml file(s)" . $filename); + throw new FastFailException("Schema validation errors found in xml file(s)" . $filename); } } } diff --git a/src/Magento/FunctionalTestingFramework/Config/Reader/MftfFilesystem.php b/src/Magento/FunctionalTestingFramework/Config/Reader/MftfFilesystem.php index cd075e57c..88a176a3c 100644 --- a/src/Magento/FunctionalTestingFramework/Config/Reader/MftfFilesystem.php +++ b/src/Magento/FunctionalTestingFramework/Config/Reader/MftfFilesystem.php @@ -42,7 +42,7 @@ public function readFiles($fileList) $configMerger->merge($content, $fileList->getFilename(), $exceptionCollector); } // run per file validation with generate:tests -d - if ($debugLevel === MftfApplicationConfig::LEVEL_DEVELOPER) { + if (strcasecmp($debugLevel, MftfApplicationConfig::LEVEL_DEVELOPER) === 0) { $this->validateSchema($configMerger, $fileList->getFilename()); } } catch (\Magento\FunctionalTestingFramework\Config\Dom\ValidationException $e) { @@ -52,7 +52,7 @@ public function readFiles($fileList) $exceptionCollector->throwException(); //run validation on merged file with generate:tests - if ($debugLevel === MftfApplicationConfig::LEVEL_DEFAULT) { + if (strcasecmp($debugLevel, MftfApplicationConfig::LEVEL_DEFAULT) === 0) { $this->validateSchema($configMerger); } diff --git a/src/Magento/FunctionalTestingFramework/Config/SchemaLocator.php b/src/Magento/FunctionalTestingFramework/Config/SchemaLocator.php index a92f536a3..869c58373 100644 --- a/src/Magento/FunctionalTestingFramework/Config/SchemaLocator.php +++ b/src/Magento/FunctionalTestingFramework/Config/SchemaLocator.php @@ -6,6 +6,9 @@ namespace Magento\FunctionalTestingFramework\Config; +use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; +use Magento\FunctionalTestingFramework\Util\Path\FilePathFormatter; + /** * Configuration schema locator. */ @@ -30,12 +33,14 @@ class SchemaLocator implements \Magento\FunctionalTestingFramework\Config\Schema * * @param string $schemaPath * @param string|null $perFileSchema + * @throws TestFrameworkException */ public function __construct($schemaPath, $perFileSchema = null) { - if (constant('FW_BP') && file_exists(FW_BP . DIRECTORY_SEPARATOR . $schemaPath)) { - $this->schemaPath = FW_BP . DIRECTORY_SEPARATOR . $schemaPath; - $this->perFileSchema = $perFileSchema === null ? null : FW_BP . DIRECTORY_SEPARATOR . $perFileSchema; + if (constant('FW_BP') && file_exists(FilePathFormatter::format(FW_BP) . $schemaPath)) { + $this->schemaPath = FilePathFormatter::format(FW_BP) . $schemaPath; + $this->perFileSchema = $perFileSchema === null ? null : FilePathFormatter::format(FW_BP) + . $perFileSchema; } else { $path = dirname(dirname(dirname(__DIR__))); $path = str_replace('\\', DIRECTORY_SEPARATOR, $path); diff --git a/src/Magento/FunctionalTestingFramework/Config/ValidationState.php b/src/Magento/FunctionalTestingFramework/Config/ValidationState.php index f08dc141a..d610f428a 100644 --- a/src/Magento/FunctionalTestingFramework/Config/ValidationState.php +++ b/src/Magento/FunctionalTestingFramework/Config/ValidationState.php @@ -37,6 +37,6 @@ public function __construct($appMode) */ public function isValidationRequired() { - return $this->appMode == 'developer'; // @todo + return $this->appMode === 'developer'; // @todo } } diff --git a/src/Magento/FunctionalTestingFramework/Console/BaseGenerateCommand.php b/src/Magento/FunctionalTestingFramework/Console/BaseGenerateCommand.php index 87b203d66..0c209d8f2 100644 --- a/src/Magento/FunctionalTestingFramework/Console/BaseGenerateCommand.php +++ b/src/Magento/FunctionalTestingFramework/Console/BaseGenerateCommand.php @@ -8,16 +8,71 @@ namespace Magento\FunctionalTestingFramework\Console; +use Magento\FunctionalTestingFramework\Exceptions\FastFailException; +use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; +use Magento\FunctionalTestingFramework\Exceptions\XmlException; +use Magento\FunctionalTestingFramework\Test\Handlers\TestObjectHandler; +use Magento\FunctionalTestingFramework\Util\Path\FilePathFormatter; use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Input\StringInput; use Symfony\Component\Console\Output\OutputInterface; use Magento\FunctionalTestingFramework\Util\Filesystem\DirSetupUtil; use Magento\FunctionalTestingFramework\Util\TestGenerator; use Magento\FunctionalTestingFramework\Config\MftfApplicationConfig; use Magento\FunctionalTestingFramework\Suite\Handlers\SuiteObjectHandler; +use Symfony\Component\Console\Style\SymfonyStyle; +/** + * Class BaseGenerateCommand + * @package Magento\FunctionalTestingFramework\Console + * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ class BaseGenerateCommand extends Command { + const MFTF_NOTICES = "Placeholder text for MFTF notices\n"; + const CODECEPT_RUN = 'codecept:run'; + const CODECEPT_RUN_FUNCTIONAL = self::CODECEPT_RUN . ' functional '; + const CODECEPT_RUN_OPTION_NO_EXIT = ' --no-exit '; + const FAILED_FILE = 'failed'; + + /** + * Enable pause() + * + * @var boolean + */ + private $enablePause = null; + + /** + * Full path to '_output' dir + * + * @var string + */ + private $testsOutputDir = null; + + /** + * String contains all 'failed' tests + * + * @var string + */ + private $allFailed; + + /** + * Console output style + * + * @var SymfonyStyle + */ + protected $ioStyle = null; + + /** + * Full path to 'failed' file + * + * @var string + */ + protected $testsFailedFile = null; + /** * Configures the base command. * @@ -44,8 +99,7 @@ protected function configure() 'debug', 'd', InputOption::VALUE_OPTIONAL, - 'Run extra validation when generating and running tests. Use option \'none\' to turn off debugging -- - added for backward compatibility, will be removed in the next MAJOR release', + 'Run extra validation when generating and running tests.', MftfApplicationConfig::LEVEL_DEFAULT ); } @@ -56,10 +110,11 @@ protected function configure() * @param OutputInterface $output * @param bool $verbose * @return void + * @throws TestFrameworkException */ protected function removeGeneratedDirectory(OutputInterface $output, bool $verbose) { - $generatedDirectory = TESTS_MODULE_PATH . DIRECTORY_SEPARATOR . TestGenerator::GENERATED_DIR; + $generatedDirectory = FilePathFormatter::format(TESTS_MODULE_PATH) . TestGenerator::GENERATED_DIR; if (file_exists($generatedDirectory)) { DirSetupUtil::rmdirRecursive($generatedDirectory); @@ -73,9 +128,8 @@ protected function removeGeneratedDirectory(OutputInterface $output, bool $verbo * Returns an array of test configuration to be used as an argument for generation of tests * @param array $tests * @return false|string - * @throws \Magento\FunctionalTestingFramework\Exceptions\XmlException + * @throws FastFailException */ - protected function getTestAndSuiteConfiguration(array $tests) { $testConfiguration['tests'] = null; @@ -84,6 +138,10 @@ protected function getTestAndSuiteConfiguration(array $tests) $suiteToTestPair = []; foreach($tests as $test) { + if (strpos($test, ':') !== false) { + $suiteToTestPair[] = $test; + continue; + } if (array_key_exists($test, $testsReferencedInSuites)) { $suites = $testsReferencedInSuites[$test]; foreach ($suites as $suite) { @@ -103,4 +161,232 @@ protected function getTestAndSuiteConfiguration(array $tests) $testConfigurationJson = json_encode($testConfiguration); return $testConfigurationJson; } + + /** + * Returns an array of test configuration to be used as an argument for generation of tests + * This function uses group or suite names for generation + * @return false|string + * @throws FastFailException + * @throws TestFrameworkException + */ + protected function getGroupAndSuiteConfiguration(array $groupOrSuiteNames) + { + $result['tests'] = []; + $result['suites'] = []; + + $groups = []; + $suites = []; + + $allSuites = SuiteObjectHandler::getInstance()->getAllObjects(); + $testsInSuites = SuiteObjectHandler::getInstance()->getAllTestReferences(); + + foreach ($groupOrSuiteNames as $groupOrSuiteName) { + if (array_key_exists($groupOrSuiteName, $allSuites)) { + $suites[] = $groupOrSuiteName; + } else { + $groups[] = $groupOrSuiteName; + } + } + + foreach ($suites as $suite) { + $result['suites'][$suite] = []; + } + + foreach ($groups as $group) { + $testsInGroup = TestObjectHandler::getInstance()->getTestsByGroup($group); + + $testsInGroupAndNotInAnySuite = array_diff( + array_keys($testsInGroup), + array_keys($testsInSuites) + ); + + $testsInGroupAndInAnySuite = array_diff( + array_keys($testsInGroup), + $testsInGroupAndNotInAnySuite + ); + + foreach ($testsInGroupAndInAnySuite as $testInGroupAndInAnySuite) { + $suiteName = $testsInSuites[$testInGroupAndInAnySuite][0]; + if (array_search($suiteName, $suites) !== false) { + // Suite is already being called to run in its entirety, do not filter list + continue; + } + $result['suites'][$suiteName][] = $testInGroupAndInAnySuite; + } + + $result['tests'] = array_merge( + $result['tests'], + $testsInGroupAndNotInAnySuite + ); + } + + if (empty($result['tests'])) { + $result['tests'] = null; + } + if (empty($result['suites'])) { + $result['suites'] = null; + } + + $json = json_encode($result); + return $json; + } + + /** + * Set Symfony IO Style + * + * @param InputInterface $input + * @param OutputInterface $output + * @return void + */ + protected function setIOStyle(InputInterface $input, OutputInterface $output) + { + // For IO style + if (null === $this->ioStyle) { + $this->ioStyle = new SymfonyStyle($input, $output); + } + } + + /** + * Show predefined global notice messages + * + * @param OutputInterface $output + * @return void + */ + protected function showMftfNotices(OutputInterface $output) + { + if (null !== $this->ioStyle) { + $this->ioStyle->note(self::MFTF_NOTICES); + } else { + $output->writeln(self::MFTF_NOTICES); + } + } + + /** + * Return if pause() is enabled + * + * @return boolean + */ + protected function pauseEnabled() + { + if (null === $this->enablePause) { + if (getenv('ENABLE_PAUSE') === 'true') { + $this->enablePause = true; + } else { + $this->enablePause = false; + } + } + return $this->enablePause; + } + + /** + * Runs the bin/mftf codecept:run command and returns exit code + * + * @param string $commandStr + * @param OutputInterface $output + * @return integer + * @throws \Exception + */ + protected function codeceptRunTest(string $commandStr, OutputInterface $output) + { + $input = new StringInput($commandStr); + $command = $this->getApplication()->find(self::CODECEPT_RUN); + return $command->run($input, $output); + } + + /** + * Return tests _output directory + * + * @return string + * @throws TestFrameworkException + */ + protected function getTestsOutputDir() + { + if (!$this->testsOutputDir) { + $this->testsOutputDir = FilePathFormatter::format(TESTS_BP) . + "tests" . + DIRECTORY_SEPARATOR . + "_output" . + DIRECTORY_SEPARATOR; + } + + return $this->testsOutputDir; + } + + /** + * Save 'failed' tests + * + * @return void + */ + protected function appendRunFailed() + { + try { + if (!$this->testsFailedFile) { + $this->testsFailedFile = $this->getTestsOutputDir() . self::FAILED_FILE; + } + + if (file_exists($this->testsFailedFile)) { + // Save 'failed' tests + $contents = file_get_contents($this->testsFailedFile); + if ($contents !== false && !empty($contents)) { + $this->allFailed .= trim($contents) . PHP_EOL; + } + } + } catch (TestFrameworkException $e) { + } + } + + /** + * Apply 'allFailed' in 'failed' file + * + * @return void + * @SuppressWarnings(PHPMD.CyclomaticComplexity) + */ + protected function applyAllFailed() + { + try { + if (!$this->testsFailedFile) { + $this->testsFailedFile = $this->getTestsOutputDir() . self::FAILED_FILE; + } + + if (!empty($this->allFailed)) { + // Update 'failed' with content from 'allFailed' + if (file_exists($this->testsFailedFile)) { + rename($this->testsFailedFile, $this->testsFailedFile . '.copy'); + } + if (file_put_contents($this->testsFailedFile, $this->allFailed) === false + && file_exists($this->testsFailedFile . '.copy')) { + rename($this->testsFailedFile . '.copy', $this->testsFailedFile); + } + if (file_exists($this->testsFailedFile . '.copy')) { + unlink($this->testsFailedFile . '.copy'); + } + } + } catch (TestFrameworkException $e) { + } + } + /** + * Codeception creates default xml file with name report.xml . + * This function renames default file name with name of the test. + * + * @param string $xml + * @param string $fileName + * @param OutputInterface $output + * @return void + * @throws \Exception + */ + public function movingXMLFileFromSourceToDestination($xml, $fileName, $output) + { + if(!empty($xml) && file_exists($this->getTestsOutputDir().'report.xml')) { + if (!file_exists($this->getTestsOutputDir().'xml')) { + mkdir($this->getTestsOutputDir().'xml' , 0777, true); + } + $fileName = str_replace("Cest.php", "",$fileName); + $existingFileName = $this->getTestsOutputDir().'report.xml'; + $newFileName = $this->getTestsOutputDir().'xml/'.$fileName.'_report.xml'; + $output->writeln( "<info>".sprintf(" report.xml file is moved to ". + $this->getTestsOutputDir().'xml/'. ' location with the new name '.$fileName.'_report.xml')."</info>") ; + rename($existingFileName , $newFileName); + } + } + } diff --git a/src/Magento/FunctionalTestingFramework/Console/BuildProjectCommand.php b/src/Magento/FunctionalTestingFramework/Console/BuildProjectCommand.php index 5102a3fdf..33cc4bc71 100644 --- a/src/Magento/FunctionalTestingFramework/Console/BuildProjectCommand.php +++ b/src/Magento/FunctionalTestingFramework/Console/BuildProjectCommand.php @@ -18,6 +18,7 @@ use Symfony\Component\Process\Process; use Magento\FunctionalTestingFramework\Util\Env\EnvProcessor; use Symfony\Component\Yaml\Yaml; +use Magento\FunctionalTestingFramework\Util\Path\FilePathFormatter; /** * Class BuildProjectCommand @@ -27,8 +28,8 @@ */ class BuildProjectCommand extends Command { - const DEFAULT_YAML_INLINE_DEPTH = 10; - const CREDENTIALS_FILE_PATH = TESTS_BP . DIRECTORY_SEPARATOR . '.credentials.example'; + private const SUCCESS_EXIT_CODE = 0; + public const DEFAULT_YAML_INLINE_DEPTH = 10; /** * Env processor manages .env files. @@ -41,6 +42,7 @@ class BuildProjectCommand extends Command * Configures the current command. * * @return void + * @throws TestFrameworkException */ protected function configure() { @@ -52,7 +54,7 @@ protected function configure() InputOption::VALUE_NONE, 'upgrade existing MFTF tests according to last major release requirements' ); - $this->envProcessor = new EnvProcessor(TESTS_BP . DIRECTORY_SEPARATOR . '.env'); + $this->envProcessor = new EnvProcessor(FilePathFormatter::format(TESTS_BP) . '.env'); $env = $this->envProcessor->getEnv(); foreach ($env as $key => $value) { $this->addOption($key, null, InputOption::VALUE_REQUIRED, '', $value); @@ -64,12 +66,11 @@ protected function configure() * * @param InputInterface $input * @param OutputInterface $output - * @return void - * @throws \Symfony\Component\Console\Exception\LogicException - * - * @SuppressWarnings(PHPMD.UnusedLocalVariable) + * @return integer + * @throws \Exception + * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ - protected function execute(InputInterface $input, OutputInterface $output) + protected function execute(InputInterface $input, OutputInterface $output): int { $resetCommand = new CleanProjectCommand(); $resetOptions = new ArrayInput([]); @@ -91,7 +92,7 @@ protected function execute(InputInterface $input, OutputInterface $output) // TODO can we just import the codecept symfony command? $codeceptBuildCommand = realpath(PROJECT_ROOT . '/vendor/bin/codecept') . ' build'; - $process = new Process($codeceptBuildCommand); + $process = Process::fromShellCommandline($codeceptBuildCommand); $process->setWorkingDirectory(TESTS_BP); $process->setIdleTimeout(600); $process->setTimeout(0); @@ -109,9 +110,11 @@ function ($type, $buffer) use ($output) { if ($input->getOption('upgrade')) { $upgradeCommand = new UpgradeTestsCommand(); - $upgradeOptions = new ArrayInput(['path' => TESTS_MODULE_PATH]); + $upgradeOptions = new ArrayInput([]); $upgradeCommand->run($upgradeOptions, $output); } + + return self::SUCCESS_EXIT_CODE; } /** @@ -119,6 +122,7 @@ function ($type, $buffer) use ($output) { * * @param OutputInterface $output * @return void + * @throws TestFrameworkException */ private function generateConfigFiles(OutputInterface $output) { @@ -126,45 +130,48 @@ private function generateConfigFiles(OutputInterface $output) //Find travel path from codeception.yml to FW_BP $relativePath = $fileSystem->makePathRelative(FW_BP, TESTS_BP); - if (!$fileSystem->exists(TESTS_BP . DIRECTORY_SEPARATOR . 'codeception.yml')) { + if (!$fileSystem->exists(FilePathFormatter::format(TESTS_BP) . 'codeception.yml')) { // read in the codeception.yml file - $configDistYml = Yaml::parse(file_get_contents(realpath(FW_BP . "/etc/config/codeception.dist.yml"))); + $configDistYml = Yaml::parse(file_get_contents( + realpath(FilePathFormatter::format(FW_BP) . "etc/config/codeception.dist.yml") + )); $configDistYml['paths']['support'] = $relativePath . 'src/Magento/FunctionalTestingFramework'; $configDistYml['paths']['envs'] = $relativePath . 'etc/_envs'; $configYmlText = Yaml::dump($configDistYml, self::DEFAULT_YAML_INLINE_DEPTH); // dump output to new codeception.yml file - file_put_contents(TESTS_BP . DIRECTORY_SEPARATOR . 'codeception.yml', $configYmlText); + file_put_contents(FilePathFormatter::format(TESTS_BP) . 'codeception.yml', $configYmlText); $output->writeln("codeception.yml configuration successfully applied."); } - $output->writeln("codeception.yml applied to " . TESTS_BP . DIRECTORY_SEPARATOR . 'codeception.yml'); + $output->writeln("codeception.yml applied to " . FilePathFormatter::format(TESTS_BP) . 'codeception.yml'); // copy the functional suite yml, will only copy if there are differences between the template the destination $fileSystem->copy( - realpath(FW_BP . '/etc/config/functional.suite.dist.yml'), - TESTS_BP . DIRECTORY_SEPARATOR . 'tests' . DIRECTORY_SEPARATOR . 'functional.suite.yml' + realpath(FilePathFormatter::format(FW_BP) . 'etc/config/functional.suite.dist.yml'), + FilePathFormatter::format(TESTS_BP) . 'tests' . DIRECTORY_SEPARATOR . 'functional.suite.yml' ); $output->writeln('functional.suite.yml configuration successfully applied.'); $output->writeln("functional.suite.yml applied to " . - TESTS_BP . DIRECTORY_SEPARATOR . 'tests' . DIRECTORY_SEPARATOR . 'functional.suite.yml'); + FilePathFormatter::format(TESTS_BP) . 'tests' . DIRECTORY_SEPARATOR . 'functional.suite.yml'); $fileSystem->copy( - FW_BP . '/etc/config/.credentials.example', - self::CREDENTIALS_FILE_PATH + FilePathFormatter::format(FW_BP) . 'etc/config/.credentials.example', + FilePathFormatter::format(TESTS_BP) . '.credentials.example' ); // copy command.php into magento instance - if (MAGENTO_BP === FW_BP) { + if (FilePathFormatter::format(MAGENTO_BP, false) + === FilePathFormatter::format(FW_BP, false)) { $output->writeln('MFTF standalone detected, command.php copy not applied.'); } else { $fileSystem->copy( - realpath(FW_BP . '/etc/config/command.php'), - TESTS_BP . DIRECTORY_SEPARATOR . "utils" . DIRECTORY_SEPARATOR .'command.php' + realpath(FilePathFormatter::format(FW_BP) . 'etc/config/command.php'), + FilePathFormatter::format(TESTS_BP) . "utils" . DIRECTORY_SEPARATOR .'command.php' ); $output->writeln('command.php copied to ' . - TESTS_BP . DIRECTORY_SEPARATOR . "utils" . DIRECTORY_SEPARATOR .'command.php'); + FilePathFormatter::format(TESTS_BP) . "utils" . DIRECTORY_SEPARATOR .'command.php'); } // Remove and Create Log File diff --git a/src/Magento/FunctionalTestingFramework/Console/CleanProjectCommand.php b/src/Magento/FunctionalTestingFramework/Console/CleanProjectCommand.php index b2b2c01f3..6517d3ab1 100644 --- a/src/Magento/FunctionalTestingFramework/Console/CleanProjectCommand.php +++ b/src/Magento/FunctionalTestingFramework/Console/CleanProjectCommand.php @@ -7,6 +7,8 @@ namespace Magento\FunctionalTestingFramework\Console; +use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; +use Magento\FunctionalTestingFramework\Util\Path\FilePathFormatter; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; @@ -16,20 +18,7 @@ class CleanProjectCommand extends Command { - const CONFIGURATION_FILES = [ - // codeception.yml file for top level config - TESTS_BP . DIRECTORY_SEPARATOR . 'codeception.yml', - // functional.suite.yml for test execution config - TESTS_BP . DIRECTORY_SEPARATOR . 'tests' . DIRECTORY_SEPARATOR . 'functional.suite.yml', - // Acceptance Tester Actions generated by codeception - FW_BP . '/src/Magento/FunctionalTestingFramework/_generated', - // AcceptanceTester Class generated by codeception - FW_BP . '/src/Magento/FunctionalTestingFramework/AcceptanceTester.php' - ]; - - const GENERATED_FILES = [ - TESTS_MODULE_PATH . '/_generated' - ]; + private const SUCCESS_EXIT_CODE = 0; /** * Configures the current command. @@ -50,24 +39,42 @@ protected function configure() * * @param InputInterface $input * @param OutputInterface $output - * @return void + * @return integer * @throws \Symfony\Component\Console\Exception\LogicException + * @throws TestFrameworkException */ - protected function execute(InputInterface $input, OutputInterface $output) + protected function execute(InputInterface $input, OutputInterface $output): int { + $configFiles = [ + // codeception.yml file for top level config + FilePathFormatter::format(TESTS_BP) . 'codeception.yml', + // functional.suite.yml for test execution config + FilePathFormatter::format(TESTS_BP) . 'tests' . DIRECTORY_SEPARATOR . 'functional.suite.yml', + // Acceptance Tester Actions generated by codeception + FilePathFormatter::format(FW_BP) . 'src/Magento/FunctionalTestingFramework/_generated', + // AcceptanceTester Class generated by codeception + FilePathFormatter::format(FW_BP) . 'src/Magento/FunctionalTestingFramework/AcceptanceTester.php' + ]; + + $generatedFiles = [ + FilePathFormatter::format(TESTS_MODULE_PATH) . '_generated' + ]; + $isHardReset = $input->getOption('hard'); $fileSystem = new Filesystem(); $finder = new Finder(); - $finder->files()->name('*.php')->in(realpath(FW_BP . '/src/Magento/FunctionalTestingFramework/Group/')); + $finder->files()->name('*.php')->in( + realpath(FilePathFormatter::format(FW_BP) . 'src/Magento/FunctionalTestingFramework/Group/') + ); $filesForRemoval = []; // include config files if user specifies a hard reset for deletion if ($isHardReset) { - $filesForRemoval = array_merge($filesForRemoval, self::CONFIGURATION_FILES); + $filesForRemoval = array_merge($filesForRemoval, $configFiles); } // include the files mftf generates during test execution in TESTS_BP for deletion - $filesForRemoval = array_merge($filesForRemoval, self::GENERATED_FILES); + $filesForRemoval = array_merge($filesForRemoval, $generatedFiles); if ($output->isVerbose()) { $output->writeln('Deleting Files:'); @@ -92,5 +99,7 @@ protected function execute(InputInterface $input, OutputInterface $output) } $output->writeln('mftf files removed from filesystem.'); + + return self::SUCCESS_EXIT_CODE; } } diff --git a/src/Magento/FunctionalTestingFramework/Console/Codecept/CodeceptCommandUtil.php b/src/Magento/FunctionalTestingFramework/Console/Codecept/CodeceptCommandUtil.php new file mode 100644 index 000000000..1a469b179 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Console/Codecept/CodeceptCommandUtil.php @@ -0,0 +1,68 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types = 1); + +namespace Magento\FunctionalTestingFramework\Console\Codecept; + +use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; +use Magento\FunctionalTestingFramework\Util\Path\FilePathFormatter; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\ArgvInput; + +class CodeceptCommandUtil +{ + const CODECEPTION_AUTOLOAD_FILE = PROJECT_ROOT . '/vendor/codeception/codeception/autoload.php'; + + /** + * Current working directory + * + * @var string + */ + private $cwd = null; + + // @codingStandardsIgnoreStart + /** + * Setup Codeception + * + * @param InputInterface $input + * @return void + */ + public function setup(InputInterface $input) + { + require_once realpath(self::CODECEPTION_AUTOLOAD_FILE); + + $tokens = preg_split('{\\s+}', $input->__toString()); + $tokens[0] = str_replace('codecept:', '', $tokens[0]); + \Closure::bind(function &(ArgvInput $input) use ($tokens) { + return $input->setTokens($tokens); + }, null, ArgvInput::class); + } + // @codingStandardsIgnoreEnd + + /** + * Save Codeception working directory + * + * @return void + * @throws TestFrameworkException + */ + public function setCodeceptCwd() + { + $this->cwd = getcwd(); + chdir(FilePathFormatter::format(TESTS_BP, false)); + } + + /** + * Restore current working directory + * + * @return void + */ + public function restoreCwd() + { + if ($this->cwd) { + chdir($this->cwd); + } + } +} diff --git a/src/Magento/FunctionalTestingFramework/Console/CodeceptRunCommand.php b/src/Magento/FunctionalTestingFramework/Console/CodeceptRunCommand.php new file mode 100644 index 000000000..de16bad84 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Console/CodeceptRunCommand.php @@ -0,0 +1,61 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types = 1); + +namespace Magento\FunctionalTestingFramework\Console; + +use Codeception\Command\Run; +use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Magento\FunctionalTestingFramework\Console\Codecept\CodeceptCommandUtil; + +class CodeceptRunCommand extends Run +{ + /** + * Configures the current command + * + * @return void + */ + protected function configure() + { + $this->setName('codecept:run') + ->setDescription( + "Wrapper command to vendor/bin/codecept:run. See https://codeception.com/docs/reference/Commands#Run" + ); + + parent::configure(); + } + + /** + * Executes the current command + * + * @param InputInterface $input + * @param OutputInterface $output + * @return integer + * @throws \Exception + */ + public function execute(InputInterface $input, OutputInterface $output): int + { + $commandUtil = new CodeceptCommandUtil(); + $commandUtil->setup($input); + $commandUtil->setCodeceptCwd(); + + try { + $exitCode = parent::execute($input, $output); + } catch (\Exception $e) { + throw new TestFrameworkException( + 'Make sure cest files are generated before running bin/mftf ' + . $this->getName() + . PHP_EOL + . $e->getMessage() + ); + } + + $commandUtil->restoreCwd(); + return $exitCode; + } +} diff --git a/src/Magento/FunctionalTestingFramework/Console/CommandList.php b/src/Magento/FunctionalTestingFramework/Console/CommandList.php index 34d221840..a31ebe175 100644 --- a/src/Magento/FunctionalTestingFramework/Console/CommandList.php +++ b/src/Magento/FunctionalTestingFramework/Console/CommandList.php @@ -29,19 +29,21 @@ class CommandList implements CommandListInterface public function __construct(array $commands = []) { $this->commands = [ - 'build:project' => new BuildProjectCommand(), - 'reset' => new CleanProjectCommand(), - 'generate:urn-catalog' => new GenerateDevUrnCommand(), - 'generate:suite' => new GenerateSuiteCommand(), - 'generate:tests' => new GenerateTestsCommand(), - 'run:test' => new RunTestCommand(), - 'run:group' => new RunTestGroupCommand(), - 'run:failed' => new RunTestFailedCommand(), - 'run:manifest' => new RunManifestCommand(), - 'setup:env' => new SetupEnvCommand(), - 'upgrade:tests' => new UpgradeTestsCommand(), - 'generate:docs' => new GenerateDocsCommand(), - 'static-checks' => new StaticChecksCommand() + 'build:project' => new BuildProjectCommand(), + 'codecept:run' => new CodeceptRunCommand(), + 'doctor' => new DoctorCommand(), + 'generate:suite' => new GenerateSuiteCommand(), + 'generate:tests' => new GenerateTestsCommand(), + 'generate:urn-catalog' => new GenerateDevUrnCommand(), + 'reset' => new CleanProjectCommand(), + 'generate:failed' => new GenerateTestFailedCommand(), + 'run:failed' => new RunTestFailedCommand(), + 'run:group' => new RunTestGroupCommand(), + 'run:manifest' => new RunManifestCommand(), + 'run:test' => new RunTestCommand(), + 'setup:env' => new SetupEnvCommand(), + 'static-checks' => new StaticChecksCommand(), + 'upgrade:tests' => new UpgradeTestsCommand(), ] + $commands; } diff --git a/src/Magento/FunctionalTestingFramework/Console/DoctorCommand.php b/src/Magento/FunctionalTestingFramework/Console/DoctorCommand.php new file mode 100644 index 000000000..f48da60d3 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Console/DoctorCommand.php @@ -0,0 +1,209 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types = 1); + +namespace Magento\FunctionalTestingFramework\Console; + +use Codeception\Configuration; +use Magento\FunctionalTestingFramework\DataTransport\Auth\WebApiAuth; +use Symfony\Component\EventDispatcher\EventDispatcher; +use Codeception\SuiteManager; +use Magento\FunctionalTestingFramework\Config\MftfApplicationConfig; +use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Magento\FunctionalTestingFramework\Module\MagentoWebDriver; +use Magento\FunctionalTestingFramework\Module\MagentoWebDriverDoctor; +use Symfony\Component\Console\Style\SymfonyStyle; + +class DoctorCommand extends Command +{ + const CODECEPTION_AUTOLOAD_FILE = PROJECT_ROOT . '/vendor/codeception/codeception/autoload.php'; + const MFTF_CODECEPTION_CONFIG_FILE = ENV_FILE_PATH . 'codeception.yml'; + const SUITE = 'functional'; + + /** + * Console output style + * + * @var SymfonyStyle + */ + private $ioStyle; + + /** + * Exception Context + * + * @var array + */ + private $context = []; + + /** + * Configures the current command. + * + * @return void + */ + protected function configure() + { + $this->setName('doctor') + ->setDescription( + 'This command checks environment readiness for generating and running MFTF tests.' + ); + } + + /** + * Executes the current command. + * + * @param InputInterface $input + * @param OutputInterface $output + * @return integer + * @throws TestFrameworkException + * @SuppressWarnings(PHPMD.CyclomaticComplexity) + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + // For output style + $this->ioStyle = new SymfonyStyle($input, $output); + + $cmdStatus = true; + + // Config application + $verbose = $output->isVerbose(); + MftfApplicationConfig::create( + false, + MftfApplicationConfig::GENERATION_PHASE, + $verbose, + MftfApplicationConfig::LEVEL_DEVELOPER, + false + ); + + // Check authentication to Magento Admin + $status = $this->checkAuthenticationToMagentoAdmin(); + $cmdStatus = $cmdStatus && !$status ? false : $cmdStatus; + + // Check connection to Selenium + $status = $this->checkContextOnStep( + MagentoWebDriverDoctor::EXCEPTION_CONTEXT_SELENIUM, + 'Connecting to Selenium Server' + ); + $cmdStatus = $cmdStatus && !$status ? false : $cmdStatus; + + // Check opening Magento Admin in web browser + $status = $this->checkContextOnStep( + MagentoWebDriverDoctor::EXCEPTION_CONTEXT_ADMIN, + 'Loading Admin page' + ); + $cmdStatus = $cmdStatus && !$status ? false : $cmdStatus; + + // Check opening Magento Storefront in web browser + $status = $this->checkContextOnStep( + MagentoWebDriverDoctor::EXCEPTION_CONTEXT_STOREFRONT, + 'Loading Storefront page' + ); + $cmdStatus = $cmdStatus && !$status ? false : $cmdStatus; + + // Check access to Magento CLI + $status = $this->checkContextOnStep( + MagentoWebDriverDoctor::EXCEPTION_CONTEXT_CLI, + 'Running Magento CLI' + ); + $cmdStatus = $cmdStatus && !$status ? false : $cmdStatus; + + return $cmdStatus ? 0 : 1; + } + + /** + * Check admin account authentication + * + * @return boolean + */ + private function checkAuthenticationToMagentoAdmin() + { + $result = false; + try { + $this->ioStyle->text("Requesting API token for admin user through cURL ..."); + WebApiAuth::getAdminToken(); + $this->ioStyle->success('Successful'); + $result = true; + } catch (TestFrameworkException $e) { + if (getenv('MAGENTO_BACKEND_BASE_URL')) { + $urlVar = 'MAGENTO_BACKEND_BASE_URL'; + } else { + $urlVar = 'MAGENTO_BASE_URL'; + } + $this->ioStyle->error( + $e->getMessage() . "\nPlease verify if " . $urlVar . ", " + . "MAGENTO_ADMIN_USERNAME and MAGENTO_ADMIN_PASSWORD in .env are valid." + ); + } + return $result; + } + + /** + * Check exception context after runMagentoWebDriverDoctor + * + * @param string $exceptionType + * @param string $message + * @return boolean + * @throws TestFrameworkException + */ + private function checkContextOnStep($exceptionType, $message) + { + $this->ioStyle->text($message . ' ...'); + $this->runMagentoWebDriverDoctor(); + + if (isset($this->context[$exceptionType])) { + $this->ioStyle->error($this->context[$exceptionType]); + return false; + } else { + $this->ioStyle->success('Successful'); + return true; + } + } + + /** + * Run diagnose through MagentoWebDriverDoctor + * + * @return void + * @throws TestFrameworkException + */ + private function runMagentoWebDriverDoctor() + { + if (!empty($this->context)) { + return; + } + + $magentoWebDriver = '\\' . MagentoWebDriver::class; + $magentoWebDriverDoctor = '\\' . MagentoWebDriverDoctor::class; + + require_once realpath(self::CODECEPTION_AUTOLOAD_FILE); + + $config = Configuration::config(realpath(self::MFTF_CODECEPTION_CONFIG_FILE)); + $settings = Configuration::suiteSettings(self::SUITE, $config); + + // Enable MagentoWebDriverDoctor + $settings['modules']['enabled'][] = $magentoWebDriverDoctor; + $settings['modules']['config'][$magentoWebDriverDoctor] = + $settings['modules']['config'][$magentoWebDriver]; + + // Disable MagentoWebDriver to avoid conflicts + foreach ($settings['modules']['enabled'] as $index => $module) { + if ($module === $magentoWebDriver) { + unset($settings['modules']['enabled'][$index]); + break; + } + } + unset($settings['modules']['config'][$magentoWebDriver]); + + $dispatcher = new EventDispatcher(); + $suiteManager = new SuiteManager($dispatcher, self::SUITE, $settings); + try { + $suiteManager->initialize(); + $this->context = ['Successful']; + } catch (TestFrameworkException $e) { + $this->context = $e->getContext(); + } + } +} diff --git a/src/Magento/FunctionalTestingFramework/Console/GenerateDevUrnCommand.php b/src/Magento/FunctionalTestingFramework/Console/GenerateDevUrnCommand.php index 1147704c0..72529158b 100644 --- a/src/Magento/FunctionalTestingFramework/Console/GenerateDevUrnCommand.php +++ b/src/Magento/FunctionalTestingFramework/Console/GenerateDevUrnCommand.php @@ -4,11 +4,12 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ -declare(strict_types = 1); +declare(strict_types=1); namespace Magento\FunctionalTestingFramework\Console; use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; +use Magento\FunctionalTestingFramework\Util\Path\FilePathFormatter; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; @@ -18,6 +19,15 @@ class GenerateDevUrnCommand extends Command { + private const SUCCESS_EXIT_CODE = 0; + /** + * Argument for the path to IDE config file + */ + public const IDE_FILE_PATH_ARGUMENT = 'path'; + + public const PROJECT_PATH_IDENTIFIER = '$PROJECT_DIR$'; + public const MFTF_SRC_PATH = 'src/Magento/FunctionalTestingFramework/'; + /** * Configures the current command. * @@ -26,8 +36,12 @@ class GenerateDevUrnCommand extends Command protected function configure() { $this->setName('generate:urn-catalog') - ->setDescription('This command generates an URN catalog to enable PHPStorm to recognize and highlight URNs.') - ->addArgument('path', InputArgument::REQUIRED, 'path to PHPStorm misc.xml file (typically located in [ProjectRoot]/.idea/misc.xml)') + ->setDescription('Generates the catalog of URNs to *.xsd mappings for the IDE to highlight xml.') + ->addArgument( + self::IDE_FILE_PATH_ARGUMENT, + InputArgument::REQUIRED, + 'Path to file to output the catalog. For PhpStorm use .idea/misc.xml' + ) ->addOption( "force", 'f', @@ -41,17 +55,17 @@ protected function configure() * * @param InputInterface $input * @param OutputInterface $output - * @return void + * @return int * @throws \Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException */ - protected function execute(InputInterface $input, OutputInterface $output) + protected function execute(InputInterface $input, OutputInterface $output): int { - $miscXmlFilePath = $input->getArgument('path') . DIRECTORY_SEPARATOR . "misc.xml"; + $miscXmlFilePath = $input->getArgument(self::IDE_FILE_PATH_ARGUMENT); $miscXmlFile = realpath($miscXmlFilePath); - $force = $input->getOption('force'); + $force = (bool) $input->getOption('force'); if ($miscXmlFile === false) { - if ($force == true) { + if ($force === true) { // create file and refresh realpath $xml = "<project version=\"4\"/>"; file_put_contents($miscXmlFilePath, $xml); @@ -70,7 +84,7 @@ protected function execute(InputInterface $input, OutputInterface $output) //Locate ProjectResources node, create one if none are found. $nodeForWork = null; - foreach($dom->getElementsByTagName('component') as $child) { + foreach ($dom->getElementsByTagName('component') as $child) { if ($child->getAttribute('name') === 'ProjectResources') { $nodeForWork = $child; } @@ -104,31 +118,80 @@ protected function execute(InputInterface $input, OutputInterface $output) //Save output $dom->save($miscXmlFile); $output->writeln("MFTF URN mapping successfully added to {$miscXmlFile}."); + + return self::SUCCESS_EXIT_CODE; } /** * Generates urn => location array for all MFTF schema. + * * @return array */ private function generateResourcesArray() { $resourcesArray = [ 'urn:magento:mftf:DataGenerator/etc/dataOperation.xsd' => - realpath(FW_BP . '/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataOperation.xsd'), + $this->getResourcePath('DataGenerator/etc/dataOperation.xsd'), 'urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd' => - realpath(FW_BP . '/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd'), + $this->getResourcePath('DataGenerator/etc/dataProfileSchema.xsd'), 'urn:magento:mftf:Page/etc/PageObject.xsd' => - realpath(FW_BP . '/src/Magento/FunctionalTestingFramework/Page/etc/PageObject.xsd'), + $this->getResourcePath('Page/etc/PageObject.xsd'), 'urn:magento:mftf:Page/etc/SectionObject.xsd' => - realpath(FW_BP . '/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd'), + $this->getResourcePath('Page/etc/SectionObject.xsd'), 'urn:magento:mftf:Test/etc/actionGroupSchema.xsd' => - realpath(FW_BP . '/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd'), + $this->getResourcePath('Test/etc/actionGroupSchema.xsd'), 'urn:magento:mftf:Test/etc/testSchema.xsd' => - realpath(FW_BP . '/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd'), + $this->getResourcePath('Test/etc/testSchema.xsd'), 'urn:magento:mftf:Suite/etc/suiteSchema.xsd' => - realpath(FW_BP . '/src/Magento/FunctionalTestingFramework/Suite/etc/suiteSchema.xsd') + $this->getResourcePath('Suite/etc/suiteSchema.xsd') ]; return $resourcesArray; } + /** + * Returns path (full or PhpStorm project-based) to XSD file + * + * @param $relativePath + * @return string + * @throws TestFrameworkException + */ + private function getResourcePath($relativePath) + { + $urnPath = realpath(FilePathFormatter::format(FW_BP) . self::MFTF_SRC_PATH . $relativePath); + $projectRoot = $this->getProjectRootPath(); + + if ($projectRoot !== null) { + return str_replace($projectRoot, self::PROJECT_PATH_IDENTIFIER, $urnPath); + } + + return $urnPath; + } + + /** + * Returns Project root directory absolute path + * @TODO Find out how to detect other types of installation + * + * @return string|null + */ + private function getProjectRootPath() + { + $frameworkRoot = realpath(__DIR__); + + if ($this->isInstalledByComposer($frameworkRoot)) { + return strstr($frameworkRoot, '/vendor/', true); + } + + return null; + } + + /** + * Determines whether MFTF was installed using Composer + * + * @param string $frameworkRoot + * @return bool + */ + private function isInstalledByComposer($frameworkRoot) + { + return false !== strpos($frameworkRoot, '/vendor/'); + } } diff --git a/src/Magento/FunctionalTestingFramework/Console/GenerateDocsCommand.php b/src/Magento/FunctionalTestingFramework/Console/GenerateDocsCommand.php deleted file mode 100644 index 7def3894f..000000000 --- a/src/Magento/FunctionalTestingFramework/Console/GenerateDocsCommand.php +++ /dev/null @@ -1,86 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -declare(strict_types = 1); - -namespace Magento\FunctionalTestingFramework\Console; - -use Magento\FunctionalTestingFramework\Config\MftfApplicationConfig; -use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; -use Magento\FunctionalTestingFramework\Test\Handlers\ActionGroupObjectHandler; -use Magento\FunctionalTestingFramework\Util\DocGenerator; -use PhpParser\Comment\Doc; -use Symfony\Component\Console\Command\Command; -use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Input\InputOption; -use Symfony\Component\Console\Output\OutputInterface; - -class GenerateDocsCommand extends Command -{ - /** - * Configures the current command. - * - * @return void - */ - protected function configure() - { - $this->setName('generate:docs') - ->setDescription('This command generates documentation for created MFTF files.') - ->addOption( - "output", - 'o', - InputOption::VALUE_REQUIRED, - 'Output Directory' - )->addOption( - "clean", - 'c', - InputOption::VALUE_NONE, - 'Clean Output Directory' - )->addOption( - "force", - 'f', - InputOption::VALUE_NONE, - 'Force Document Generation For All Action Groups' - ); - } - - /** - * Executes the current command. - * - * @param InputInterface $input - * @param OutputInterface $output - * @return void - * @throws TestFrameworkException - * @throws \Magento\FunctionalTestingFramework\Exceptions\TestReferenceException - * @throws \Magento\FunctionalTestingFramework\Exceptions\XmlException - */ - protected function execute(InputInterface $input, OutputInterface $output) - { - defined('COMMAND') || define('COMMAND', 'generate:docs'); - $config = $input->getOption('output'); - $clean = $input->getOption('clean'); - $force = $input->getOption('force'); - - MftfApplicationConfig::create( - $force, - MftfApplicationConfig::GENERATION_PHASE, - false, - MftfApplicationConfig::LEVEL_NONE, - true - ); - - $allActionGroups = ActionGroupObjectHandler::getInstance()->getAllObjects(); - $docGenerator = new DocGenerator(); - $docGenerator->createDocumentation($allActionGroups, $config, $clean); - - $output->writeln("Generate Docs Command Run"); - - if (empty($config)) { - $output->writeln("Output to ". DocGenerator::DEFAULT_OUTPUT_DIR); - } else { - $output->writeln("Output to ". $config); - } - } -} diff --git a/src/Magento/FunctionalTestingFramework/Console/GenerateSuiteCommand.php b/src/Magento/FunctionalTestingFramework/Console/GenerateSuiteCommand.php index 8da3493aa..102930472 100644 --- a/src/Magento/FunctionalTestingFramework/Console/GenerateSuiteCommand.php +++ b/src/Magento/FunctionalTestingFramework/Console/GenerateSuiteCommand.php @@ -8,7 +8,9 @@ namespace Magento\FunctionalTestingFramework\Console; use Magento\FunctionalTestingFramework\Config\MftfApplicationConfig; +use Magento\FunctionalTestingFramework\Exceptions\FastFailException; use Magento\FunctionalTestingFramework\Suite\SuiteGenerator; +use Magento\FunctionalTestingFramework\Util\GenerationErrorHandler; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; @@ -65,13 +67,34 @@ protected function execute(InputInterface $input, OutputInterface $output) $suites = $input->getArgument('suites'); + $generated = 0; foreach ($suites as $suite) { - SuiteGenerator::getInstance()->generateSuite($suite); - if ($output->isVerbose()) { - $output->writeLn("suite $suite generated"); + try { + SuiteGenerator::getInstance()->generateSuite($suite); + if ($output->isVerbose()) { + $output->writeLn("suite $suite generated"); + } + $generated++; + } catch (FastFailException $e) { + throw $e; + } catch (\Exception $e) { } } - $output->writeLn("Suites Generated"); + if (empty(GenerationErrorHandler::getInstance()->getAllErrors())) { + if ($generated > 0) { + $output->writeln("Suites Generated" . PHP_EOL); + return 0; + } + } else { + GenerationErrorHandler::getInstance()->printErrorSummary(); + if ($generated > 0) { + $output->writeln("Suites Generated (with errors)" . PHP_EOL); + return 1; + } + } + + $output->writeln("No Suite Generated" . PHP_EOL); + return 1; } } diff --git a/src/Magento/FunctionalTestingFramework/Console/GenerateTestFailedCommand.php b/src/Magento/FunctionalTestingFramework/Console/GenerateTestFailedCommand.php new file mode 100644 index 000000000..380270969 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Console/GenerateTestFailedCommand.php @@ -0,0 +1,186 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types = 1); + +namespace Magento\FunctionalTestingFramework\Console; + +use Magento\FunctionalTestingFramework\Config\MftfApplicationConfig; +use Magento\FunctionalTestingFramework\Util\Path\FilePathFormatter; +use Symfony\Component\Console\Input\ArrayInput; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Process\Process; +use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; +use Symfony\Component\Console\Input\InputOption; + +class GenerateTestFailedCommand extends BaseGenerateCommand +{ + /** + * Default Test group to signify not in suite + */ + const DEFAULT_TEST_GROUP = 'default'; + + /** + * Configures the current command. + * + * @return void + */ + protected function configure() + { + $this->setName('generate:failed') + ->setDescription('Generate a set of tests failed'); + + parent::configure(); + } + + /** + * Executes the current command. + * + * @param InputInterface $input + * @param OutputInterface $output + * @return integer + * @throws \Exception + * + * @SuppressWarnings(PHPMD.UnusedLocalVariable) + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + protected function execute(InputInterface $input, OutputInterface $output): int + { + $force = $input->getOption('force'); + $debug = $input->getOption('debug') ?? MftfApplicationConfig::LEVEL_DEVELOPER; // for backward compatibility + $allowSkipped = $input->getOption('allow-skipped'); + $verbose = $output->isVerbose(); + + // Create Mftf Configuration + MftfApplicationConfig::create( + $force, + MftfApplicationConfig::EXECUTION_PHASE, + $verbose, + $debug, + $allowSkipped + ); + + $testsFailedFile = $this->getTestsOutputDir() . self::FAILED_FILE; + $testsReRunFile = $this->getTestsOutputDir() . "rerun_tests"; + $testConfiguration = $this->getFailedTestList($testsFailedFile, $testsReRunFile); + + if ($testConfiguration === null) { + // No failed tests found, no tests generated + $this->removeGeneratedDirectory($output, $verbose); + return 0; + } + + $command = $this->getApplication()->find('generate:tests'); + $args = [ + '--tests' => $testConfiguration, + '--force' => $force, + '--remove' => true, + '--debug' => $debug, + '--allow-skipped' => $allowSkipped, + '-v' => $verbose + ]; + $command->run(new ArrayInput($args), $output); + $output->writeln("Test Failed Generated, now run:failed command"); + return 0; + } + + /** + * Returns a json string of tests that failed on the last run + * + * @return string + * @SuppressWarnings(PHPMD.CyclomaticComplexity) + */ + public function getFailedTestList($testsFailedFile, $testsReRunFile) + { + $failedTestDetails = ['tests' => [], 'suites' => []]; + + $testList = $this->readFailedTestFile($testsFailedFile); + + if (!empty($testList)) { + foreach ($testList as $test) { + if (!empty($test)) { + $this->writeFailedTestToFile($test, $testsReRunFile); + $testInfo = explode(DIRECTORY_SEPARATOR, $test); + $testName = isset($testInfo[count($testInfo) - 1][1]) + ? explode(":", $testInfo[count($testInfo) - 1])[1] + : []; + $suiteName = isset($testInfo[count($testInfo) - 2]) + ? $testInfo[count($testInfo) - 2] + : []; + if ($suiteName === self::DEFAULT_TEST_GROUP) { + array_push($failedTestDetails['tests'], $testName); + } else { + $suiteName = $this->sanitizeSuiteName($suiteName); + $failedTestDetails['suites'] = array_merge_recursive( + $failedTestDetails['suites'], + [$suiteName => [$testName]] + ); + } + } + } + } + if (empty($failedTestDetails['tests']) & empty($failedTestDetails['suites'])) { + return null; + } + if (empty($failedTestDetails['tests'])) { + $failedTestDetails['tests'] = null; + } + if (empty($failedTestDetails['suites'])) { + $failedTestDetails['suites'] = null; + } + $testConfigurationJson = json_encode($failedTestDetails); + return $testConfigurationJson; + } + + /** + * Trim potential suite_parallel_0_G to suite_parallel + * + * @param string $suiteName + * @return string + */ + private function sanitizeSuiteName($suiteName) + { + $suiteNameArray = explode("_", $suiteName); + if (array_pop($suiteNameArray) === 'G') { + if (is_numeric(array_pop($suiteNameArray))) { + $suiteName = implode("_", $suiteNameArray); + } + } + return $suiteName; + } + + /** + * Returns an array of tests read from the failed test file in _output + * + * @param string $filePath + * @return array|boolean + */ + public function readFailedTestFile($filePath) + { + if (realpath($filePath)) { + return file($filePath, FILE_IGNORE_NEW_LINES); + } + return ""; + } + + /** + * Writes the test name to a file if it does not already exist + * + * @param string $test + * @param string $filePath + * @return void + */ + public function writeFailedTestToFile($test, $filePath) + { + if (file_exists($filePath)) { + if (strpos(file_get_contents($filePath), $test) === false) { + file_put_contents($filePath, "\n" . $test, FILE_APPEND); + } + } else { + file_put_contents($filePath, $test . "\n"); + } + } +} diff --git a/src/Magento/FunctionalTestingFramework/Console/GenerateTestsCommand.php b/src/Magento/FunctionalTestingFramework/Console/GenerateTestsCommand.php index 621f29d03..dc5e18f75 100644 --- a/src/Magento/FunctionalTestingFramework/Console/GenerateTestsCommand.php +++ b/src/Magento/FunctionalTestingFramework/Console/GenerateTestsCommand.php @@ -8,19 +8,62 @@ namespace Magento\FunctionalTestingFramework\Console; use Magento\FunctionalTestingFramework\Config\MftfApplicationConfig; +use Magento\FunctionalTestingFramework\Exceptions\FastFailException; use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; +use Magento\FunctionalTestingFramework\Exceptions\TestReferenceException; +use Magento\FunctionalTestingFramework\Exceptions\XmlException; use Magento\FunctionalTestingFramework\Suite\SuiteGenerator; use Magento\FunctionalTestingFramework\Test\Handlers\TestObjectHandler; -use Magento\FunctionalTestingFramework\Util\Manifest\ParallelTestManifest; +use Magento\FunctionalTestingFramework\Test\Objects\ActionObject; +use Magento\FunctionalTestingFramework\Util\GenerationErrorHandler; +use Magento\FunctionalTestingFramework\Util\Logger\LoggingUtil; +use Magento\FunctionalTestingFramework\Util\Manifest\ParallelByTimeTestManifest; use Magento\FunctionalTestingFramework\Util\Manifest\TestManifestFactory; +use Magento\FunctionalTestingFramework\Util\Script\ScriptUtil; +use Magento\FunctionalTestingFramework\Util\Script\TestDependencyUtil; use Magento\FunctionalTestingFramework\Util\TestGenerator; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Component\Finder\Finder; +/** + * @SuppressWarnings(PHPMD) + */ class GenerateTestsCommand extends BaseGenerateCommand { + const PARALLEL_DEFAULT_TIME = 10; + const EXTENDS_REGEX_PATTERN = '/extends=["\']([^\'"]*)/'; + const ACTIONGROUP_REGEX_PATTERN = '/ref=["\']([^\'"]*)/'; + const TEST_DEPENDENCY_FILE_LOCATION = 'dev/tests/_output/test-dependencies.json'; + + /** + * @var ScriptUtil + */ + private $scriptUtil; + + /** + * @var TestDependencyUtil + */ + private $testDependencyUtil; + + /** + * @var array + */ + private $moduleNameToPath; + + /** + * @var array + */ + private $moduleNameToComposerName; + + /** + * @var array + */ + private $flattenedDependencies; + /** * Configures the current command. * @@ -30,22 +73,56 @@ protected function configure() { $this->setName('generate:tests') ->setDescription('Run validation and generate all test files and suites based on xml declarations') + ->addUsage('AdminLoginTest') ->addArgument( 'name', InputArgument::OPTIONAL | InputArgument::IS_ARRAY, 'name(s) of specific tests to generate' - )->addOption("config", 'c', InputOption::VALUE_REQUIRED, 'default, singleRun, or parallel', 'default') - ->addOption( + )->addOption( + "config", + 'c', + InputOption::VALUE_REQUIRED, + 'default, singleRun, or parallel', + 'default' + )->addOption( 'time', 'i', InputOption::VALUE_REQUIRED, - 'Used in combination with a parallel configuration, determines desired group size (in minutes)', - 10 + 'Used in combination with a parallel configuration, determines desired group size (in minutes)' + . PHP_EOL . 'Option "--time" will be the default and the default value is ' + . self::PARALLEL_DEFAULT_TIME + . ' when neither "--time" nor "--groups" is specified' + )->addOption( + 'groups', + 'g', + InputOption::VALUE_REQUIRED, + 'Used in combination with a parallel configuration, determines desired number of groups' + . PHP_EOL . 'Options "--time" and "--groups" are mutually exclusive and only one should be used' )->addOption( 'tests', 't', InputOption::VALUE_REQUIRED, - 'A parameter accepting a JSON string used to determine the test configuration' + 'A parameter accepting a JSON string or JSON file path used to determine the test configuration' + )->addOption( + 'filter', + null, + InputOption::VALUE_IS_ARRAY | InputOption::VALUE_OPTIONAL, + 'Option to filter tests to be generated.' . PHP_EOL + . '<info>Template:</info> <filterName>:<filterValue>' . PHP_EOL + . '<info>Existing filter types:</info> severity.' . PHP_EOL + . '<info>Existing severity values:</info> BLOCKER, CRITICAL, MAJOR, AVERAGE, MINOR.' . PHP_EOL + . '<info>Example:</info> --filter=severity:CRITICAL' + . ' --filter=includeGroup:customer --filter=excludeGroup:customerAnalytics' . PHP_EOL + )->addOption( + 'path', + 'p', + InputOption::VALUE_REQUIRED, + 'path to a test names file.', + )->addOption( + 'log', + 'l', + InputOption::VALUE_REQUIRED, + 'Generate metadata files during test generation.', ); parent::configure(); @@ -56,31 +133,56 @@ protected function configure() * * @param InputInterface $input * @param OutputInterface $output - * @return void + * @return void|integer * @throws TestFrameworkException - * @throws \Magento\FunctionalTestingFramework\Exceptions\TestReferenceException - * @throws \Magento\FunctionalTestingFramework\Exceptions\XmlException + * @throws FastFailException + * @throws XmlException */ protected function execute(InputInterface $input, OutputInterface $output) { + $this->setIOStyle($input, $output); $tests = $input->getArgument('name'); $config = $input->getOption('config'); $json = $input->getOption('tests'); // for backward compatibility $force = $input->getOption('force'); - $time = $input->getOption('time') * 60 * 1000; // convert from minutes to milliseconds + $time = $input->getOption('time'); + //$time = $input->getOption('time') * 60 * 1000; // convert from minutes to milliseconds + $groups = $input->getOption('groups'); $debug = $input->getOption('debug') ?? MftfApplicationConfig::LEVEL_DEVELOPER; // for backward compatibility $remove = $input->getOption('remove'); $verbose = $output->isVerbose(); $allowSkipped = $input->getOption('allow-skipped'); + $log = $input->getOption('log'); + $filters = $input->getOption('filter'); + foreach ($filters as $filter) { + list($filterType, $filterValue) = explode(':', $filter); + $filterList[$filterType][] = $filterValue; + } + + $path = $input->getOption('path'); + // check filepath is given for generate test file + if (!empty($path)) { + $tests = $this->generateTestFileFromPath($path); + } // Set application configuration so we can references the user options in our framework - MftfApplicationConfig::create( - $force, - MftfApplicationConfig::GENERATION_PHASE, - $verbose, - $debug, - $allowSkipped - ); + try { + MftfApplicationConfig::create( + $force, + MftfApplicationConfig::GENERATION_PHASE, + $verbose, + $debug, + $allowSkipped, + $filterList ?? [] + ); + } catch (\Exception $exception) { + $this->ioStyle->error("Test generation failed." . PHP_EOL . $exception->getMessage()); + return 1; + } + + if ($json !== null && is_file($json)) { + $json = file_get_contents($json); + } if (!empty($tests)) { $json = $this->getTestAndSuiteConfiguration($tests); @@ -91,33 +193,81 @@ protected function execute(InputInterface $input, OutputInterface $output) throw new TestFrameworkException("JSON could not be parsed: " . json_last_error_msg()); } - if ($config === 'parallel' && $time <= 0) { - // stop execution if the user has given us an invalid argument for time argument during parallel generation - throw new TestFrameworkException("time option cannot be less than or equal to 0"); + if ($config === 'parallel') { + list($config, $configNumber) = $this->parseConfigParallelOptions($time, $groups); } // Remove previous GENERATED_DIR if --remove option is used if ($remove) { - $this->removeGeneratedDirectory($output, $verbose || - ($debug !== MftfApplicationConfig::LEVEL_NONE)); + $this->removeGeneratedDirectory($output, $verbose); } - $testConfiguration = $this->createTestConfiguration($json, $tests); + try { + $testConfiguration = $this->createTestConfiguration($json, $tests); - // create our manifest file here - $testManifest = TestManifestFactory::makeManifest($config, $testConfiguration['suites']); - TestGenerator::getInstance(null, $testConfiguration['tests'])->createAllTestFiles($testManifest); + // create our manifest file here + $testManifest = TestManifestFactory::makeManifest($config, $testConfiguration['suites']); - if ($config == 'parallel') { - /** @var ParallelTestManifest $testManifest */ - $testManifest->createTestGroups($time); - } + try { + if (empty($tests) || !empty($testConfiguration['tests'])) { + // $testConfiguration['tests'] cannot be empty if $tests is not empty + TestGenerator::getInstance(null, $testConfiguration['tests'])->createAllTestFiles($testManifest); + } elseif (empty($testConfiguration['suites'])) { + throw new FastFailException( + !empty(GenerationErrorHandler::getInstance()->getAllErrors()) + ? + GenerationErrorHandler::getInstance()->getAllErrorMessages() + : + 'Invalid input' + ); + } + } catch (FastFailException $e) { + throw $e; + } catch (\Exception $e) { + } + + if (strpos($config, 'parallel') !== false) { + $testManifest->createTestGroups($configNumber); + } + + SuiteGenerator::getInstance()->generateAllSuites($testManifest); + + $testManifest->generate(); + } catch (\Exception $e) { + if (!empty(GenerationErrorHandler::getInstance()->getAllErrors())) { + GenerationErrorHandler::getInstance()->printErrorSummary(); + } + $message = $e->getMessage() . PHP_EOL; + $message .= !empty($filters) ? 'Filter(s): ' . implode(', ', $filters) . PHP_EOL : ''; + $message .= !empty($tests) ? 'Test name(s): ' . implode(', ', $tests) . PHP_EOL : ''; + $message .= !empty($json) && empty($tests) ? 'Test configuration: ' . $json . PHP_EOL : ''; + $this->ioStyle->note($message); - SuiteGenerator::getInstance()->generateAllSuites($testManifest); + return 1; + } - $testManifest->generate(); + // check test dependencies log command + if (!empty($log)) { + if ($log === "testEntityJson") { + $this->getTestEntityJson($tests); + $output->writeln( + "Test dependencies file created, Located in: " . self::TEST_DEPENDENCY_FILE_LOCATION + ); + } else { + $output->writeln( + "Wrong parameter for log (-l) option, accepted parameter are: testEntityJson" . PHP_EOL + ); + } + } - $output->writeln("Generate Tests Command Run"); + if (empty(GenerationErrorHandler::getInstance()->getAllErrors())) { + $output->writeln("Generate Tests Command Run" . PHP_EOL); + return 0; + } else { + GenerationErrorHandler::getInstance()->printErrorSummary(); + $output->writeln("Generate Tests Command Run (with errors)" . PHP_EOL); + return 1; + } } /** @@ -126,8 +276,8 @@ protected function execute(InputInterface $input, OutputInterface $output) * @param string $json * @param array $tests * @return array - * @throws \Magento\FunctionalTestingFramework\Exceptions\TestReferenceException - * @throws \Magento\FunctionalTestingFramework\Exceptions\XmlException + * @throws FastFailException + * @throws TestFrameworkException */ private function createTestConfiguration( $json, @@ -144,7 +294,20 @@ private function createTestConfiguration( $testObjects = []; foreach ($testConfiguration['tests'] as $test) { - $testObjects[$test] = TestObjectHandler::getInstance()->getObject($test); + try { + $testObjects[$test] = TestObjectHandler::getInstance()->getObject($test); + } catch (FastFailException $e) { + throw $e; + } catch (\Exception $e) { + $message = "Unable to create test object {$test} from test configuration. " . $e->getMessage(); + LoggingUtil::getInstance()->getLogger(self::class)->error($message); + if (MftfApplicationConfig::getConfig()->verboseEnabled() + && MftfApplicationConfig::getConfig()->getPhase() === MftfApplicationConfig::GENERATION_PHASE + ) { + print($message); + } + GenerationErrorHandler::getInstance()->addError('test', $test, $message); + } } $testConfiguration['tests'] = $testObjects; @@ -175,4 +338,264 @@ private function parseTestsConfigJson($json, array $testConfiguration) $jsonTestConfiguration['suites'] = $testConfigArray['suites'] ?? null; return $jsonTestConfiguration; } + + /** + * Parse console command options --time and/or --groups and return config type and config number in an array + * + * @param mixed $time + * @param mixed $groups + * @return array + * @throws FastFailException + */ + private function parseConfigParallelOptions($time, $groups) + { + $config = null; + $configNumber = null; + if ($time !== null && $groups !== null) { + throw new FastFailException( + "'time' and 'groups' options are mutually exclusive. " + . "Only one can be specified for 'config parallel'" + ); + } elseif ($time === null && $groups === null) { + $config = 'parallelByTime'; + $configNumber = self::PARALLEL_DEFAULT_TIME * 60 * 1000; // convert from minutes to milliseconds + } elseif ($time !== null && is_numeric($time)) { + $time = $time * 60 * 1000; // convert from minutes to milliseconds + if (is_int($time) && $time > 0) { + $config = 'parallelByTime'; + $configNumber = $time; + } + } elseif ($groups !== null && is_numeric($groups)) { + $groups = $groups * 1; + if (is_int($groups) && $groups > 0) { + $config = 'parallelByGroup'; + $configNumber = $groups; + } + } + + if ($config && $configNumber) { + return [$config, $configNumber]; + } elseif ($time !== null) { + throw new FastFailException("'time' option must be an integer and greater than 0"); + } else { + throw new FastFailException("'groups' option must be an integer and greater than 0"); + } + } + + /** + * console command options --log and create test dependencies in json file + * @return void + * @throws TestFrameworkException + * @throws XmlException|FastFailException + */ + private function getTestEntityJson(array $tests = []) + { + $testDependencies = $this->getTestDependencies($tests); + $this->array2Json($testDependencies); + } + + /** + * Function responsible for getting test dependencies in array + * @param array $tests + * @return array + * @throws FastFailException + * @throws TestFrameworkException + * @throws XmlException + */ + public function getTestDependencies(array $tests = []): array + { + $this->scriptUtil = new ScriptUtil(); + $this->testDependencyUtil = new TestDependencyUtil(); + $allModules = $this->scriptUtil->getAllModulePaths(); + + if (!class_exists('\Magento\Framework\Component\ComponentRegistrar')) { + throw new TestFrameworkException( + "TEST DEPENDENCY CHECK ABORTED: MFTF must be attached or pointing to Magento codebase." + ); + } + $registrar = new \Magento\Framework\Component\ComponentRegistrar(); + $this->moduleNameToPath = $registrar->getPaths(\Magento\Framework\Component\ComponentRegistrar::MODULE); + $this->moduleNameToComposerName = $this->testDependencyUtil->buildModuleNameToComposerName( + $this->moduleNameToPath + ); + $this->flattenedDependencies = $this->testDependencyUtil->buildComposerDependencyList( + $this->moduleNameToPath, + $this->moduleNameToComposerName + ); + + if (!empty($tests)) { + # specific test dependencies will be generate. + $testXmlFiles = $this->scriptUtil->getModuleXmlFilesByTestNames($tests); + } else { + $filePaths = [ + DIRECTORY_SEPARATOR . 'Test' . DIRECTORY_SEPARATOR + ]; + // These files can contain references to other modules. + $testXmlFiles = $this->scriptUtil->getModuleXmlFilesByScope($allModules, $filePaths[0]); + } + + list($testDependencies, $extendedTestMapping) = $this->findTestDependentModule($testXmlFiles); + return $this->testDependencyUtil->mergeDependenciesForExtendingTests($testDependencies, $extendedTestMapping); + } + + /** + * Finds all test dependencies in given set of files + * @param Finder $files + * @return array + * @throws FastFailException + * @throws XmlException + */ + private function findTestDependentModule(Finder $files): array + { + $testDependencies = []; + $extendedTests = []; + $extendedTestMapping = []; + foreach ($files as $filePath) { + $allEntities = []; + $filePath = $filePath->getPathname(); + $moduleName = $this->testDependencyUtil->getModuleName($filePath, $this->moduleNameToPath); + // Not a module, is either dev/tests/acceptance or loose folder with test materials + if ($moduleName == null) { + continue; + } + + $contents = file_get_contents($filePath); + preg_match_all(ActionObject::ACTION_ATTRIBUTE_VARIABLE_REGEX_PATTERN, $contents, $braceReferences); + preg_match_all(self::ACTIONGROUP_REGEX_PATTERN, $contents, $actionGroupReferences); + preg_match_all(self::EXTENDS_REGEX_PATTERN, $contents, $extendReferences); + + // Remove Duplicates + $braceReferences[0] = array_unique($braceReferences[0]); + $actionGroupReferences[1] = array_unique($actionGroupReferences[1]); + $braceReferences[1] = array_unique($braceReferences[1]); + $braceReferences[2] = array_filter(array_unique($braceReferences[2])); + + // resolve entity references + $allEntities = array_merge( + $allEntities, + $this->scriptUtil->resolveEntityReferences($braceReferences[0], $contents) + ); + + // resolve parameterized references + $allEntities = array_merge( + $allEntities, + $this->scriptUtil->resolveParametrizedReferences($braceReferences[2], $contents) + ); + + // resolve entity by names + $allEntities = array_merge( + $allEntities, + $this->scriptUtil->resolveEntityByNames($actionGroupReferences[1]) + ); + + // resolve entity by names + $allEntities = array_merge( + $allEntities, + $this->scriptUtil->resolveEntityByNames($extendReferences[1]) + ); + $modulesReferencedInTest = $this->testDependencyUtil->getModuleDependenciesFromReferences( + $allEntities, + $this->moduleNameToComposerName, + $this->moduleNameToPath + ); + if (! empty($modulesReferencedInTest)) { + $document = new \DOMDocument(); + $document->loadXML($contents); + $test_file = $document->getElementsByTagName('test')->item(0); + $test_name = $test_file->getAttribute('name'); + + # check any test extends on with this test. + $extended_test = $test_file->getAttribute('extends') ?? ""; + if (!empty($extended_test)) { + $extendedTests[] = $extended_test; + $extendedTestMapping[] = ["child_test_name" =>$test_name, "parent_test_name" =>$extended_test]; + } + + $flattenedDependencyMap = array_values( + array_unique(call_user_func_array('array_merge', array_values($modulesReferencedInTest))) + ); + $suite_name = $this->getSuiteName($test_name); + $full_name = "Magento\AcceptanceTest\_". $suite_name. "\Backend\\".$test_name."Cest.".$test_name; + $dependencyMap = [ + "file_path" => $filePath, + "full_name" => $full_name, + "test_name" => $test_name, + "test_modules" => $flattenedDependencyMap, + ]; + $testDependencies[] = $dependencyMap; + } + } + + if (!empty($extendedTests)) { + list($extendedDependencies, $tempExtendedTestMapping) = $this->getExtendedTestDependencies($extendedTests); + $testDependencies = array_merge($testDependencies, $extendedDependencies); + $extendedTestMapping = array_merge($extendedTestMapping, $tempExtendedTestMapping); + } + + return [$testDependencies, $extendedTestMapping]; + } + + /** + * Finds all extended test dependencies in given set of files + * @param array $extendedTests + * @return array + * @throws FastFailException + * @throws XmlException + */ + private function getExtendedTestDependencies(array $extendedTests): array + { + $testXmlFiles = $this->scriptUtil->getModuleXmlFilesByTestNames($extendedTests); + return $this->findTestDependentModule($testXmlFiles); + } + + /** + * Create json file of test dependencies + * @param array $array + * @return void + */ + private function array2Json(array $array) + { + $file = fopen(self::TEST_DEPENDENCY_FILE_LOCATION, 'w'); + $json = json_encode($array, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT); + fwrite($file, $json); + fclose($file); + } + + /** + * Get suite name. + * @param string $test_name + * @return integer|mixed|string + * @throws FastFailException + */ + private function getSuiteName(string $test_name) + { + $suite_name = json_decode($this->getTestAndSuiteConfiguration([$test_name]), true)["suites"] ?? "default"; + if (is_array($suite_name)) { + $suite_name = array_keys($suite_name)[0]; + } + return $suite_name; + } + + /** + * @param string $path + * @return array + * @throws TestFrameworkException + */ + private function generateTestFileFromPath(string $path): array + { + if (!file_exists($path)) { + throw new TestFrameworkException("Could not find file $path. Check the path and try again."); + } + + $test_names = file($path, FILE_IGNORE_NEW_LINES); + $tests = []; + foreach ($test_names as $test_name) { + if (empty(trim($test_name))) { + continue; + } + $test_name_array = explode(' ', trim($test_name)); + $tests = array_merge($tests, $test_name_array); + } + return $tests; + } } diff --git a/src/Magento/FunctionalTestingFramework/Console/RunManifestCommand.php b/src/Magento/FunctionalTestingFramework/Console/RunManifestCommand.php index e487d16cf..3b75cbb87 100644 --- a/src/Magento/FunctionalTestingFramework/Console/RunManifestCommand.php +++ b/src/Magento/FunctionalTestingFramework/Console/RunManifestCommand.php @@ -11,8 +11,10 @@ use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\StringInput; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Process\Process; +use Magento\FunctionalTestingFramework\Util\Path\FilePathFormatter; class RunManifestCommand extends Command { @@ -31,6 +33,13 @@ class RunManifestCommand extends Command */ private $failedTests = []; + /** + * Path for a failed test + * + * @var string + */ + private $testsFailedFile; + /** * Configure the run:manifest command. * @@ -53,6 +62,14 @@ protected function configure() */ protected function execute(InputInterface $input, OutputInterface $output): int { + $testsOutputDir = FilePathFormatter::format(TESTS_BP) . + "tests" . + DIRECTORY_SEPARATOR . + "_output" . + DIRECTORY_SEPARATOR; + + $this->testsFailedFile = $testsOutputDir . "failed"; + $path = $input->getArgument("path"); if (!file_exists($path)) { @@ -64,12 +81,17 @@ protected function execute(InputInterface $input, OutputInterface $output): int // Delete the Codeception failed file just in case it exists from any previous test runs $this->deleteFailedFile(); - foreach ($manifestFile as $manifestLine) { - if (empty($manifestLine)) { + for ($line = 0; $line < count($manifestFile); $line++) { + if (empty($manifestFile[$line])) { continue; } - $this->runManifestLine($manifestLine, $output); + if ($line === count($manifestFile) - 1) { + $this->runManifestLine($manifestFile[$line], $output, true); + } else { + $this->runManifestLine($manifestFile[$line], $output); + } + $this->aggregateFailed(); } @@ -86,24 +108,38 @@ protected function execute(InputInterface $input, OutputInterface $output): int * * @param string $manifestLine * @param OutputInterface $output + * @param boolean $exit * @return void + * @throws \Exception * - * @SuppressWarnings(PHPMD.UnusedLocalVariable) Need this because of the unused $type variable in the closure + * @SuppressWarnings(PHPMD.UnusedFormalParameter) Need this because of the unused $type variable in the closure */ - private function runManifestLine(string $manifestLine, OutputInterface $output) + private function runManifestLine($manifestLine, $output, $exit = false) { - $codeceptionCommand = realpath(PROJECT_ROOT . "/vendor/bin/codecept") - . " run functional --verbose --steps " - . $manifestLine; - - // run the codecept command in a sub process - $process = new Process($codeceptionCommand); - $process->setWorkingDirectory(TESTS_BP); - $process->setIdleTimeout(600); - $process->setTimeout(0); - $subReturnCode = $process->run(function ($type, $buffer) use ($output) { - $output->write($buffer); - }); + if (getenv('ENABLE_PAUSE') === 'true') { + $codeceptionCommand = BaseGenerateCommand::CODECEPT_RUN_FUNCTIONAL + . '--verbose --steps --debug '; + if (!$exit) { + $codeceptionCommand .= BaseGenerateCommand::CODECEPT_RUN_OPTION_NO_EXIT; + } + $codeceptionCommand .= $manifestLine; + $input = new StringInput($codeceptionCommand); + $command = $this->getApplication()->find(BaseGenerateCommand::CODECEPT_RUN); + $subReturnCode = $command->run($input, $output); + } else { + $codeceptionCommand = realpath(PROJECT_ROOT . "/vendor/bin/codecept") + . " run functional --verbose --steps " . $manifestLine; + + // run the codecept command in a sub process + $process = Process::fromShellCommandline($codeceptionCommand); + $process->setWorkingDirectory(TESTS_BP); + $process->setIdleTimeout(600); + $process->setTimeout(0); + $subReturnCode = $process->run(function ($type, $buffer) use ($output) { + $output->write($buffer); + }); + } + $this->returnCode = max($this->returnCode, $subReturnCode); } @@ -117,8 +153,8 @@ private function runManifestLine(string $manifestLine, OutputInterface $output) */ private function aggregateFailed() { - if (file_exists(RunTestFailedCommand::TESTS_FAILED_FILE)) { - $currentFile = file(RunTestFailedCommand::TESTS_FAILED_FILE, FILE_IGNORE_NEW_LINES); + if (file_exists($this->testsFailedFile)) { + $currentFile = file($this->testsFailedFile, FILE_IGNORE_NEW_LINES); $this->failedTests = array_merge( $this->failedTests, $currentFile @@ -133,8 +169,8 @@ private function aggregateFailed() */ private function deleteFailedFile() { - if (file_exists(RunTestFailedCommand::TESTS_FAILED_FILE)) { - unlink(RunTestFailedCommand::TESTS_FAILED_FILE); + if (file_exists($this->testsFailedFile)) { + unlink($this->testsFailedFile); } } @@ -146,7 +182,7 @@ private function deleteFailedFile() private function writeFailedFile() { foreach ($this->failedTests as $test) { - file_put_contents(RunTestFailedCommand::TESTS_FAILED_FILE, $test . "\n", FILE_APPEND); + file_put_contents($this->testsFailedFile, $test . "\n", FILE_APPEND); } } } diff --git a/src/Magento/FunctionalTestingFramework/Console/RunTestCommand.php b/src/Magento/FunctionalTestingFramework/Console/RunTestCommand.php index 77b8be513..02e721ae3 100644 --- a/src/Magento/FunctionalTestingFramework/Console/RunTestCommand.php +++ b/src/Magento/FunctionalTestingFramework/Console/RunTestCommand.php @@ -8,6 +8,9 @@ namespace Magento\FunctionalTestingFramework\Console; use Magento\FunctionalTestingFramework\Config\MftfApplicationConfig; +use Magento\FunctionalTestingFramework\Test\Handlers\TestObjectHandler; +use Magento\FunctionalTestingFramework\Util\GenerationErrorHandler; +use Magento\FunctionalTestingFramework\Util\Path\FilePathFormatter; use Magento\FunctionalTestingFramework\Util\TestGenerator; use Symfony\Component\Console\Input\ArrayInput; use Symfony\Component\Console\Input\InputArgument; @@ -17,6 +20,9 @@ use Symfony\Component\Process\Process; use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; +/** + * @SuppressWarnings(PHPMD) + */ class RunTestCommand extends BaseGenerateCommand { /** @@ -35,17 +41,27 @@ protected function configure() { $this->setName("run:test") ->setDescription("generation and execution of test(s) defined in xml") + ->addOption( + 'xml', + 'xml', + InputOption::VALUE_NONE, + "creates xml report for executed test" + ) ->addArgument( 'name', - InputArgument::REQUIRED | InputArgument::IS_ARRAY, + InputArgument::OPTIONAL | InputArgument::IS_ARRAY, "name of tests to generate and execute" )->addOption( 'skip-generate', 'k', InputOption::VALUE_NONE, "skip generation and execute existing test" + )->addOption( + 'tests', + 't', + InputOption::VALUE_REQUIRED, + 'A parameter accepting a JSON string or JSON file path used to determine the test configuration' ); - parent::configure(); } @@ -60,6 +76,7 @@ protected function configure() protected function execute(InputInterface $input, OutputInterface $output): int { $tests = $input->getArgument('name'); + $json = $input->getOption('tests'); // for backward compatibility $skipGeneration = $input->getOption('skip-generate'); $force = $input->getOption('force'); $remove = $input->getOption('remove'); @@ -83,7 +100,24 @@ protected function execute(InputInterface $input, OutputInterface $output): int $allowSkipped ); - $testConfiguration = $this->getTestAndSuiteConfiguration($tests); + if ($json !== null) { + if (is_file($json)) { + $testConfiguration = file_get_contents($json); + } else { + $testConfiguration = $json; + } + } + + if (!empty($tests)) { + $testConfiguration = $this->getTestAndSuiteConfiguration($tests); + } + + if ($testConfiguration !== null && !json_decode($testConfiguration)) { + // stop execution if we have failed to properly parse any json passed in by the user + throw new TestFrameworkException("JSON could not be parsed: " . json_last_error_msg()); + } + + $generationErrorCode = 0; if (!$skipGeneration) { $command = $this->getApplication()->find('generate:tests'); @@ -93,22 +127,30 @@ protected function execute(InputInterface $input, OutputInterface $output): int '--remove' => $remove, '--debug' => $debug, '--allow-skipped' => $allowSkipped, - '-v' => $verbose + '-v' => $verbose, + '' ]; $command->run(new ArrayInput($args), $output); + + if (!empty(GenerationErrorHandler::getInstance()->getAllErrors())) { + $generationErrorCode = 1; + } } $testConfigArray = json_decode($testConfiguration, true); if (isset($testConfigArray['tests'])) { - $this->runTests($testConfigArray['tests'], $output); + $this->runTests($testConfigArray['tests'], $output, $input); } if (isset($testConfigArray['suites'])) { - $this->runTestsInSuite($testConfigArray['suites'], $output); + $this->runTestsInSuite($testConfigArray['suites'], $output, $input); } - return $this->returnCode; + // Add all failed tests in 'failed' file + $this->applyAllFailed(); + + return max($this->returnCode, $generationErrorCode); } /** @@ -116,28 +158,51 @@ protected function execute(InputInterface $input, OutputInterface $output): int * * @param array $tests * @param OutputInterface $output + * @param InputInterface $input * @return void * @throws TestFrameworkException + * @throws \Exception */ - private function runTests(array $tests, OutputInterface $output) + private function runTests(array $tests, OutputInterface $output, InputInterface $input) { - $codeceptionCommand = realpath(PROJECT_ROOT . '/vendor/bin/codecept') . ' run functional '; - $testsDirectory = TESTS_MODULE_PATH . - DIRECTORY_SEPARATOR . + $xml = ($input->getOption('xml')) + ? '--xml' + : ""; + if ($this->pauseEnabled()) { + $codeceptionCommand = self::CODECEPT_RUN_FUNCTIONAL; + } else { + $codeceptionCommand = realpath(PROJECT_ROOT . '/vendor/bin/codecept') . ' run functional '; + } + + $testsDirectory = FilePathFormatter::format(TESTS_MODULE_PATH) . TestGenerator::GENERATED_DIR . DIRECTORY_SEPARATOR . TestGenerator::DEFAULT_DIR . DIRECTORY_SEPARATOR ; - foreach ($tests as $test) { - $testName = $test . 'Cest.php'; + for ($i = 0; $i < count($tests); $i++) { + $testName = $tests[$i] . 'Cest.php'; if (!realpath($testsDirectory . $testName)) { throw new TestFrameworkException( $testName . " is not available under " . $testsDirectory ); } - $fullCommand = $codeceptionCommand . $testsDirectory . $testName . ' --verbose --steps'; - $this->returnCode = max($this->returnCode, $this->executeTestCommand($fullCommand, $output)); + + if ($this->pauseEnabled()) { + $fullCommand = $codeceptionCommand . $testsDirectory . $testName . ' --verbose --steps --debug '.$xml; + if ($i !== count($tests) - 1) { + $fullCommand .= self::CODECEPT_RUN_OPTION_NO_EXIT; + } + $this->returnCode = max($this->returnCode, $this->codeceptRunTest($fullCommand, $output)); + } else { + $fullCommand = $codeceptionCommand . $testsDirectory . $testName . ' --verbose --steps '.$xml; + $this->returnCode = max($this->returnCode, $this->executeTestCommand($fullCommand, $output)); + } + if (!empty($xml)) { + $this->movingXMLFileFromSourceToDestination($xml, $testName, $output); + } + // Save failed tests + $this->appendRunFailed(); } } @@ -146,15 +211,42 @@ private function runTests(array $tests, OutputInterface $output) * * @param array $suitesConfig * @param OutputInterface $output + * @param InputInterface $input * @return void + * @throws \Exception */ - private function runTestsInSuite(array $suitesConfig, OutputInterface $output) + private function runTestsInSuite(array $suitesConfig, OutputInterface $output, InputInterface $input) { - $codeceptionCommand = realpath(PROJECT_ROOT . '/vendor/bin/codecept') . ' run functional --verbose --steps '; + $xml = ($input->getOption('xml')) + ? '--xml' + : ""; + if ($this->pauseEnabled()) { + $codeceptionCommand = self::CODECEPT_RUN_FUNCTIONAL . '--verbose --steps --debug '.$xml; + } else { + $codeceptionCommand = realpath(PROJECT_ROOT . '/vendor/bin/codecept') + . ' run functional --verbose --steps '.$xml; + } + + $count = count($suitesConfig); + $index = 0; //for tests in suites, run them as a group to run before and after block foreach (array_keys($suitesConfig) as $suite) { $fullCommand = $codeceptionCommand . " -g {$suite}"; - $this->returnCode = max($this->returnCode, $this->executeTestCommand($fullCommand, $output)); + + $index += 1; + if ($this->pauseEnabled()) { + if ($index !== $count) { + $fullCommand .= self::CODECEPT_RUN_OPTION_NO_EXIT; + } + $this->returnCode = max($this->returnCode, $this->codeceptRunTest($fullCommand, $output)); + } else { + $this->returnCode = max($this->returnCode, $this->executeTestCommand($fullCommand, $output)); + } + if (!empty($xml)) { + $this->movingXMLFileFromSourceToDestination($xml, $suite, $output); + } + // Save failed tests + $this->appendRunFailed(); } } @@ -165,14 +257,15 @@ private function runTestsInSuite(array $suitesConfig, OutputInterface $output) * @param OutputInterface $output * @return integer * - * @SuppressWarnings(PHPMD.UnusedLocalVariable) + * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ private function executeTestCommand(string $command, OutputInterface $output) { - $process = new Process($command); + $process = Process::fromShellCommandline($command); $process->setWorkingDirectory(TESTS_BP); $process->setIdleTimeout(600); $process->setTimeout(0); + return $process->run(function ($type, $buffer) use ($output) { $output->write($buffer); }); diff --git a/src/Magento/FunctionalTestingFramework/Console/RunTestFailedCommand.php b/src/Magento/FunctionalTestingFramework/Console/RunTestFailedCommand.php index 336fd5d87..55834ad9b 100644 --- a/src/Magento/FunctionalTestingFramework/Console/RunTestFailedCommand.php +++ b/src/Magento/FunctionalTestingFramework/Console/RunTestFailedCommand.php @@ -7,35 +7,18 @@ namespace Magento\FunctionalTestingFramework\Console; -use Magento\FunctionalTestingFramework\Config\MftfApplicationConfig; -use Symfony\Component\Console\Input\ArrayInput; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Process\Process; -use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; -use Symfony\Component\Console\Input\InputOption; class RunTestFailedCommand extends BaseGenerateCommand { - /** - * Default Test group to signify not in suite - */ const DEFAULT_TEST_GROUP = 'default'; - const TESTS_OUTPUT_DIR = TESTS_BP . - DIRECTORY_SEPARATOR . - "tests" . - DIRECTORY_SEPARATOR . - "_output" . - DIRECTORY_SEPARATOR; - - const TESTS_FAILED_FILE = self::TESTS_OUTPUT_DIR . "failed"; - const TESTS_RERUN_FILE = self::TESTS_OUTPUT_DIR . "rerun_tests"; - const TESTS_MANIFEST_FILE= TESTS_MODULE_PATH . - DIRECTORY_SEPARATOR . - "_generated" . - DIRECTORY_SEPARATOR . - "testManifest.txt"; + /** + * @var string + */ + private $testsReRunFile = "rerun_tests"; /** * @var array @@ -68,140 +51,108 @@ protected function configure() */ protected function execute(InputInterface $input, OutputInterface $output): int { - $force = $input->getOption('force'); - $debug = $input->getOption('debug') ?? MftfApplicationConfig::LEVEL_DEVELOPER; // for backward compatibility - $allowSkipped = $input->getOption('allow-skipped'); - $verbose = $output->isVerbose(); - - // Create Mftf Configuration - MftfApplicationConfig::create( - $force, - MftfApplicationConfig::EXECUTION_PHASE, - $verbose, - $debug, - $allowSkipped - ); - - $testConfiguration = $this->getFailedTestList(); - - if ($testConfiguration === null) { - // no failed tests found, run is a success + $this->testsFailedFile = $this->getTestsOutputDir() . self::FAILED_FILE; + $this->testsReRunFile = $this->getTestsOutputDir() . "rerun_tests"; + + $failedTests = $this->readFailedTestFile($this->testsFailedFile); + $testManifestList = $this->filterTestsForExecution($failedTests); + + if (empty($testManifestList)) { + // If there is no tests in manifest then we have nothing to execute. return 0; } - - $command = $this->getApplication()->find('generate:tests'); - $args = [ - '--tests' => $testConfiguration, - '--force' => $force, - '--remove' => true, - '--debug' => $debug, - '--allow-skipped' => $allowSkipped, - '-v' => $verbose - ]; - $command->run(new ArrayInput($args), $output); - - $testManifestList = $this->readTestManifestFile(); $returnCode = 0; - foreach ($testManifestList as $testCommand) { - $codeceptionCommand = realpath(PROJECT_ROOT . '/vendor/bin/codecept') . ' run functional '; - $codeceptionCommand .= $testCommand; - - $process = new Process($codeceptionCommand); - $process->setWorkingDirectory(TESTS_BP); - $process->setIdleTimeout(600); - $process->setTimeout(0); - $returnCode = max($returnCode, $process->run( - function ($type, $buffer) use ($output) { - $output->write($buffer); + for ($i = 0; $i < count($testManifestList); $i++) { + if ($this->pauseEnabled()) { + $codeceptionCommand = self::CODECEPT_RUN_FUNCTIONAL . $testManifestList[$i] . ' --debug '; + if ($i !== count($testManifestList) - 1) { + $codeceptionCommand .= self::CODECEPT_RUN_OPTION_NO_EXIT; } - )); - if (file_exists(self::TESTS_FAILED_FILE)) { + $returnCode = $this->codeceptRunTest($codeceptionCommand, $output); + } else { + $codeceptionCommand = realpath(PROJECT_ROOT . '/vendor/bin/codecept') . ' run functional '; + $codeceptionCommand .= $testManifestList[$i]; + + $process = Process::fromShellCommandline($codeceptionCommand); + $process->setWorkingDirectory(TESTS_BP); + $process->setIdleTimeout(600); + $process->setTimeout(0); + $returnCode = max($returnCode, $process->run( + function ($type, $buffer) use ($output) { + $output->write($buffer); + } + )); + $process->__destruct(); + unset($process); + } + + if (file_exists($this->testsFailedFile)) { $this->failedList = array_merge( $this->failedList, - $this->readFailedTestFile(self::TESTS_FAILED_FILE) + $this->readFailedTestFile($this->testsFailedFile) ); } } + foreach ($this->failedList as $test) { - $this->writeFailedTestToFile($test, self::TESTS_FAILED_FILE); + $this->writeFailedTestToFile($test, $this->testsFailedFile); } return $returnCode; } /** - * Returns a json string of tests that failed on the last run + * Returns a list of tests/suites which should have an additional run. * - * @return string + * @param array $failedTests + * @return array */ - private function getFailedTestList() + private function filterTestsForExecution(array $failedTests): array { - $failedTestDetails = ['tests' => [], 'suites' => []]; - - if (realpath(self::TESTS_FAILED_FILE)) { - $testList = $this->readFailedTestFile(self::TESTS_FAILED_FILE); - - foreach ($testList as $test) { - if (!empty($test)) { - $this->writeFailedTestToFile($test, self::TESTS_RERUN_FILE); - $testInfo = explode(DIRECTORY_SEPARATOR, $test); - $testName = explode(":", $testInfo[count($testInfo) - 1])[1]; - $suiteName = $testInfo[count($testInfo) - 2]; - - if ($suiteName == self::DEFAULT_TEST_GROUP) { - array_push($failedTestDetails['tests'], $testName); - } else { - // Trim potential suite_parallel_0 to suite_parallel - $suiteNameArray = explode("_", $suiteName); - if (is_numeric(array_pop($suiteNameArray))) { - $suiteName = implode("_", $suiteNameArray); - } - $failedTestDetails['suites'] = array_merge_recursive( - $failedTestDetails['suites'], - [$suiteName => [$testName]] - ); + $testsOrGroupsToRerun = []; + + foreach ($failedTests as $test) { + if (!empty($test)) { + $this->writeFailedTestToFile($test, $this->testsReRunFile); + $testInfo = explode(DIRECTORY_SEPARATOR, $test); + $suiteName = $testInfo[count($testInfo) - 2]; + list($testPath) = explode(":", $test); + + if ($suiteName === self::DEFAULT_TEST_GROUP) { + $testsOrGroupsToRerun[] = $testPath; + } else { + $group = "-g " . $suiteName; + if (!in_array($group, $testsOrGroupsToRerun)) { + $testsOrGroupsToRerun[] = $group; } } } } - if (empty($failedTestDetails['tests']) & empty($failedTestDetails['suites'])) { - return null; - } - if (empty($failedTestDetails['tests'])) { - $failedTestDetails['tests'] = null; - } - if (empty($failedTestDetails['suites'])) { - $failedTestDetails['suites'] = null; - } - $testConfigurationJson = json_encode($failedTestDetails); - return $testConfigurationJson; - } - /** - * Returns an array of run commands read from the manifest file created post generation - * - * @return array|boolean - */ - private function readTestManifestFile() - { - return file(self::TESTS_MANIFEST_FILE, FILE_IGNORE_NEW_LINES); + return $testsOrGroupsToRerun; } /** * Returns an array of tests read from the failed test file in _output * * @param string $filePath - * @return array|boolean + * @return array */ - private function readFailedTestFile($filePath) + private function readFailedTestFile(string $filePath): array { - return file($filePath, FILE_IGNORE_NEW_LINES); + $data = []; + if (file_exists($filePath)) { + $file = file($filePath, FILE_IGNORE_NEW_LINES); + $data = $file === false ? [] : $file; + } + return $data; } /** * Writes the test name to a file if it does not already exist * * @param string $test + * @param string $filePath * @return void */ private function writeFailedTestToFile($test, $filePath) diff --git a/src/Magento/FunctionalTestingFramework/Console/RunTestGroupCommand.php b/src/Magento/FunctionalTestingFramework/Console/RunTestGroupCommand.php index 7f954c8fe..fefbe08f8 100644 --- a/src/Magento/FunctionalTestingFramework/Console/RunTestGroupCommand.php +++ b/src/Magento/FunctionalTestingFramework/Console/RunTestGroupCommand.php @@ -7,16 +7,15 @@ namespace Magento\FunctionalTestingFramework\Console; -use Magento\FunctionalTestingFramework\Suite\Handlers\SuiteObjectHandler; use Magento\FunctionalTestingFramework\Config\MftfApplicationConfig; -use Magento\FunctionalTestingFramework\Test\Handlers\TestObjectHandler; +use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; +use Magento\FunctionalTestingFramework\Util\GenerationErrorHandler; use Symfony\Component\Console\Input\ArrayInput; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Process\Process; -use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; class RunTestGroupCommand extends BaseGenerateCommand { @@ -28,7 +27,15 @@ class RunTestGroupCommand extends BaseGenerateCommand protected function configure() { $this->setName('run:group') - ->setDescription('Execute a set of tests referenced via group annotations') + ->setDescription( + 'Execute a set of tests referenced via group annotations' + ) + ->addOption( + 'xml', + 'xml', + InputOption::VALUE_NONE, + "creates xml report for executed group" + ) ->addOption( 'skip-generate', 'k', @@ -51,10 +58,15 @@ protected function configure() * @return integer * @throws \Exception * - * @SuppressWarnings(PHPMD.UnusedLocalVariable) + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * @SuppressWarnings(PHPMD.CyclomaticComplexity) + * @SuppressWarnings(PHPMD.NPathComplexity) */ protected function execute(InputInterface $input, OutputInterface $output): int { + $xml = ($input->getOption('xml')) + ? '--xml' + : ""; $skipGeneration = $input->getOption('skip-generate'); $force = $input->getOption('force'); $groups = $input->getArgument('groups'); @@ -79,6 +91,8 @@ protected function execute(InputInterface $input, OutputInterface $output): int $allowSkipped ); + $generationErrorCode = 0; + if (!$skipGeneration) { $testConfiguration = $this->getGroupAndSuiteConfiguration($groups); $command = $this->getApplication()->find('generate:tests'); @@ -92,51 +106,57 @@ protected function execute(InputInterface $input, OutputInterface $output): int ]; $command->run(new ArrayInput($args), $output); - } - - $codeceptionCommand = realpath(PROJECT_ROOT . '/vendor/bin/codecept') . ' run functional --verbose --steps'; - foreach ($groups as $group) { - $codeceptionCommand .= " -g {$group}"; + if (!empty(GenerationErrorHandler::getInstance()->getAllErrors())) { + $generationErrorCode = 1; + } } - $process = new Process($codeceptionCommand); - $process->setWorkingDirectory(TESTS_BP); - $process->setIdleTimeout(600); - $process->setTimeout(0); + if ($this->pauseEnabled()) { + $commandString = self::CODECEPT_RUN_FUNCTIONAL . '--verbose --steps --debug '.$xml; + } else { + $commandString = realpath( + PROJECT_ROOT . '/vendor/bin/codecept' + ) . ' run functional --verbose --steps '.$xml; + } - return $process->run( - function ($type, $buffer) use ($output) { - $output->write($buffer); + $exitCode = -1; + $returnCodes = []; + for ($i = 0; $i < count($groups); $i++) { + $codeceptionCommandString = $commandString . ' -g ' . $groups[$i]; + + if ($this->pauseEnabled()) { + if ($i !== count($groups) - 1) { + $codeceptionCommandString .= self::CODECEPT_RUN_OPTION_NO_EXIT; + } + $returnCodes[] = $this->codeceptRunTest($codeceptionCommandString, $output); + } else { + $process = Process::fromShellCommandline($codeceptionCommandString); + $process->setWorkingDirectory(TESTS_BP); + $process->setIdleTimeout(600); + $process->setTimeout(0); + $returnCodes[] = $process->run( + function ($type, $buffer) use ($output) { + $output->write($buffer); + } + ); } - ); - } + if (!empty($xml)) { + $this->movingXMLFileFromSourceToDestination($xml, $groups[$i].'_'.'group', $output); + } + // Save failed tests + $this->appendRunFailed(); + } - /** - * Returns a json string to be used as an argument for generation of a group or suite - * - * @param array $groups - * @return string - * @throws \Magento\FunctionalTestingFramework\Exceptions\XmlException - */ - private function getGroupAndSuiteConfiguration(array $groups) - { - $testConfiguration['tests'] = []; - $testConfiguration['suites'] = null; - $availableSuites = SuiteObjectHandler::getInstance()->getAllObjects(); + // Add all failed tests in 'failed' file + $this->applyAllFailed(); - foreach ($groups as $group) { - if (array_key_exists($group, $availableSuites)) { - $testConfiguration['suites'][$group] = []; + foreach ($returnCodes as $returnCode) { + if ($returnCode !== 0) { + return $returnCode; } - - $testConfiguration['tests'] = array_merge( - $testConfiguration['tests'], - array_keys(TestObjectHandler::getInstance()->getTestsByGroup($group)) - ); + $exitCode = 0; } - - $testConfigurationJson = json_encode($testConfiguration); - return $testConfigurationJson; + return max($exitCode, $generationErrorCode); } } diff --git a/src/Magento/FunctionalTestingFramework/Console/SetupEnvCommand.php b/src/Magento/FunctionalTestingFramework/Console/SetupEnvCommand.php index 4d1ce0334..258e905d5 100644 --- a/src/Magento/FunctionalTestingFramework/Console/SetupEnvCommand.php +++ b/src/Magento/FunctionalTestingFramework/Console/SetupEnvCommand.php @@ -7,6 +7,8 @@ namespace Magento\FunctionalTestingFramework\Console; +use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; +use Magento\FunctionalTestingFramework\Util\Path\FilePathFormatter; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Exception\InvalidOptionException; @@ -16,6 +18,8 @@ class SetupEnvCommand extends Command { + private const SUCCESS_EXIT_CODE = 0; + /** * Env processor manages .env files. * @@ -27,12 +31,13 @@ class SetupEnvCommand extends Command * Configures the current command. * * @return void + * @throws TestFrameworkException */ protected function configure() { $this->setName('setup:env') ->setDescription("Generate .env file."); - $this->envProcessor = new EnvProcessor(TESTS_BP . DIRECTORY_SEPARATOR . '.env'); + $this->envProcessor = new EnvProcessor(FilePathFormatter::format(TESTS_BP) . '.env'); $env = $this->envProcessor->getEnv(); foreach ($env as $key => $value) { $this->addOption($key, null, InputOption::VALUE_REQUIRED, '', $value); @@ -44,10 +49,10 @@ protected function configure() * * @param InputInterface $input * @param OutputInterface $output - * @return void + * @return integer * @throws \Symfony\Component\Console\Exception\LogicException */ - protected function execute(InputInterface $input, OutputInterface $output) + protected function execute(InputInterface $input, OutputInterface $output): int { $config = $this->envProcessor->getEnv(); $userEnv = []; @@ -59,5 +64,7 @@ protected function execute(InputInterface $input, OutputInterface $output) } $this->envProcessor->putEnvFile($userEnv); $output->writeln(".env configuration successfully applied."); + + return self::SUCCESS_EXIT_CODE; } } diff --git a/src/Magento/FunctionalTestingFramework/Console/StaticChecksCommand.php b/src/Magento/FunctionalTestingFramework/Console/StaticChecksCommand.php index 9757f494e..806508ef8 100644 --- a/src/Magento/FunctionalTestingFramework/Console/StaticChecksCommand.php +++ b/src/Magento/FunctionalTestingFramework/Console/StaticChecksCommand.php @@ -8,22 +8,47 @@ namespace Magento\FunctionalTestingFramework\Console; +use Magento\FunctionalTestingFramework\StaticCheck\StaticCheckInterface; use Magento\FunctionalTestingFramework\StaticCheck\StaticChecksList; -use Magento\FunctionalTestingFramework\StaticCheck\StaticCheckListInterface; use Magento\FunctionalTestingFramework\Util\Logger\LoggingUtil; use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Exception; +use Symfony\Component\Console\Style\SymfonyStyle; class StaticChecksCommand extends Command { /** - * Pool of static check scripts to run + * Associative array containing static ruleset properties. * - * @var StaticCheckListInterface + * @var array */ - private $staticChecksList; + private $ruleSet; + + /** + * Pool of all existing static check objects + * + * @var StaticCheckInterface[] + */ + private $allStaticCheckObjects; + + /** + * Static checks to run + * + * @var StaticCheckInterface[] + */ + private $staticCheckObjects; + + /** + * Console output style + * + * @var SymfonyStyle + */ + protected $ioStyle; /** * Configures the current command. @@ -32,13 +57,28 @@ class StaticChecksCommand extends Command */ protected function configure() { + $list = new StaticChecksList(); + $this->allStaticCheckObjects = $list->getStaticChecks(); + $staticCheckNames = implode(', ', array_keys($this->allStaticCheckObjects)); + $description = 'This command will run all static checks on xml test materials. ' + . 'Available static check scripts are:' . PHP_EOL . $staticCheckNames; $this->setName('static-checks') - ->setDescription('This command will run all static checks on xml test materials.'); - $this->staticChecksList = new StaticChecksList(); + ->setDescription($description) + ->addArgument( + 'names', + InputArgument::OPTIONAL | InputArgument::IS_ARRAY, + 'name(s) of specific static check script(s) to run' + )->addOption( + 'path', + 'p', + InputOption::VALUE_OPTIONAL, + 'Path to a MFTF test module to run "deprecatedEntityUsage" static check script. ' . PHP_EOL + . 'Option is ignored by other static check scripts.' . PHP_EOL + ); } /** - * + * Run required static check scripts * * @param InputInterface $input * @param OutputInterface $output @@ -47,23 +87,126 @@ protected function configure() */ protected function execute(InputInterface $input, OutputInterface $output) { - $staticCheckObjects = $this->staticChecksList->getStaticChecks(); + $this->ioStyle = new SymfonyStyle($input, $output); + try { + $this->validateInput($input); + } catch (InvalidArgumentException $e) { + LoggingUtil::getInstance()->getLogger(StaticChecksCommand::class)->error($e->getMessage()); + $this->ioStyle->error($e->getMessage() . ' Please fix input argument(s) or option(s) and rerun.'); + return 1; + } + $cmdFailed = false; $errors = []; + foreach ($this->staticCheckObjects as $name => $staticCheck) { + LoggingUtil::getInstance()->getLogger(get_class($staticCheck))->info( + 'Running static check script for: ' . $name . PHP_EOL + ); - foreach ($staticCheckObjects as $staticCheck) { - $staticCheck->execute($input); + $this->ioStyle->text(PHP_EOL . 'Running static check script for: ' . $name . PHP_EOL); + $start = microtime(true); + try { + $staticCheck->execute($input); + } catch (Exception $e) { + $cmdFailed = true; + LoggingUtil::getInstance()->getLogger(get_class($staticCheck))->error($e->getMessage() . PHP_EOL); + $this->ioStyle->error($e->getMessage()); + } + $end = microtime(true); + $errors += $staticCheck->getErrors(); $staticOutput = $staticCheck->getOutput(); LoggingUtil::getInstance()->getLogger(get_class($staticCheck))->info($staticOutput); - $output->writeln($staticOutput); - $errors += $staticCheck->getErrors(); - } + $this->ioStyle->text($staticOutput); - if (empty($errors)) { + $this->ioStyle->text('Total execution time is ' . (string)($end - $start) . ' seconds.' . PHP_EOL); + } + if (!$cmdFailed && empty($errors)) { return 0; } else { return 1; } } + + /** + * Validate input arguments + * + * @param InputInterface $input + * @return void + * @throws InvalidArgumentException + */ + private function validateInput(InputInterface $input) + { + $this->staticCheckObjects = []; + $requiredChecksNames = $input->getArgument('names'); + // Build list of static check names to run. + if (empty($requiredChecksNames)) { + $this->parseRulesetJson(); + $requiredChecksNames = $this->ruleSet['tests'] ?? null; + } + if (empty($requiredChecksNames)) { + $this->staticCheckObjects = $this->allStaticCheckObjects; + } else { + $this->validateTestNames($requiredChecksNames); + } + + if ($input->getOption('path')) { + if ((count($this->staticCheckObjects) !== 1) + || !in_array( + array_keys($this->staticCheckObjects)[0], + [ + StaticChecksList::DEPRECATED_ENTITY_USAGE_CHECK_NAME, + StaticChecksList::PAUSE_ACTION_USAGE_CHECK_NAME + ] + ) + ) + throw new InvalidArgumentException( + '--path option is not supported for the command."' + ); + } + } + + /** + * Validates that all passed in static-check names match an existing static check + * @param string[] $requiredChecksNames + * @return void + */ + private function validateTestNames($requiredChecksNames) + { + $invalidCheckNames = []; + for ($index = 0; $index < count($requiredChecksNames); $index++) { + if (in_array($requiredChecksNames[$index], array_keys($this->allStaticCheckObjects))) { + $this->staticCheckObjects[$requiredChecksNames[$index]] = + $this->allStaticCheckObjects[$requiredChecksNames[$index]]; + } else { + $invalidCheckNames[] = $requiredChecksNames[$index]; + } + } + + if (!empty($invalidCheckNames)) { + throw new InvalidArgumentException( + 'Invalid static check script(s): ' . implode(', ', $invalidCheckNames) . '.' + ); + } + } + + /** + * Parses and sets local ruleSet. If not found, simply returns and lets script continue. + * @return void; + */ + private function parseRulesetJson() + { + $pathAddition = "/dev/tests/acceptance/"; + // MFTF is both NOT attached and no MAGENTO_BP defined in .env + if (MAGENTO_BP === FW_BP) { + $pathAddition = "/dev/"; + } + $pathToRuleset = MAGENTO_BP . $pathAddition . "staticRuleset.json"; + if (!file_exists($pathToRuleset)) { + $this->ioStyle->text("No ruleset under $pathToRuleset" . PHP_EOL); + return; + } + $this->ioStyle->text("Using ruleset under $pathToRuleset" . PHP_EOL); + $this->ruleSet = json_decode(file_get_contents($pathToRuleset), true); + } } diff --git a/src/Magento/FunctionalTestingFramework/Console/UpgradeTestsCommand.php b/src/Magento/FunctionalTestingFramework/Console/UpgradeTestsCommand.php index 8e24290b5..06284605d 100644 --- a/src/Magento/FunctionalTestingFramework/Console/UpgradeTestsCommand.php +++ b/src/Magento/FunctionalTestingFramework/Console/UpgradeTestsCommand.php @@ -17,6 +17,8 @@ class UpgradeTestsCommand extends Command { + private const SUCCESS_EXIT_CODE = 0; + /** * Pool of upgrade scripts to run * @@ -32,8 +34,12 @@ class UpgradeTestsCommand extends Command protected function configure() { $this->setName('upgrade:tests') - ->setDescription('This command will upgrade all tests in the provided path according to new MFTF Major version requirements.') - ->addArgument('path', InputArgument::REQUIRED, 'path to MFTF tests to upgrade'); + ->setDescription( + 'This command will upgrade MFTF tests according to new MFTF Major version requirements. ' + . 'It will upgrade MFTF tests in specific path when "path" argument is specified, otherwise it will ' + . 'upgrade all MFTF tests installed.' + ) + ->addArgument('path', InputArgument::OPTIONAL, 'path to MFTF tests to upgrade'); $this->upgradeScriptsList = new UpgradeScriptList(); } @@ -42,17 +48,20 @@ protected function configure() * * @param InputInterface $input * @param OutputInterface $output - * @return int|null|void + * @return int * @throws \Exception */ - protected function execute(InputInterface $input, OutputInterface $output) + protected function execute(InputInterface $input, OutputInterface $output): int { /** @var \Magento\FunctionalTestingFramework\Upgrade\UpgradeInterface[] $upgradeScriptObjects */ $upgradeScriptObjects = $this->upgradeScriptsList->getUpgradeScripts(); - foreach ($upgradeScriptObjects as $upgradeScriptObject) { - $upgradeOutput = $upgradeScriptObject->execute($input); + foreach ($upgradeScriptObjects as $scriptName => $upgradeScriptObject) { + $output->writeln('Running upgrade script: ' . $scriptName . PHP_EOL); + $upgradeOutput = $upgradeScriptObject->execute($input, $output); LoggingUtil::getInstance()->getLogger(get_class($upgradeScriptObject))->info($upgradeOutput); - $output->writeln($upgradeOutput); + $output->writeln($upgradeOutput . PHP_EOL); } + + return self::SUCCESS_EXIT_CODE; } } diff --git a/src/Magento/FunctionalTestingFramework/Data/Argument/Interpreter/ArrayType.php b/src/Magento/FunctionalTestingFramework/Data/Argument/Interpreter/ArrayType.php index 566c09912..b292236c6 100644 --- a/src/Magento/FunctionalTestingFramework/Data/Argument/Interpreter/ArrayType.php +++ b/src/Magento/FunctionalTestingFramework/Data/Argument/Interpreter/ArrayType.php @@ -98,7 +98,7 @@ private function compareItems($firstItemKey, $secondItemKey, $indexedItems) $secondValue = intval($secondItem['sortOrder']); } - if ($firstValue == $secondValue) { + if ($firstValue === $secondValue) { // These keys reflect initial relative position of items. // Allows stable sort for items with equal 'sortOrder' return $firstItemKey < $secondItemKey ? -1 : 1; diff --git a/src/Magento/FunctionalTestingFramework/DataGenerator/Config/Dom.php b/src/Magento/FunctionalTestingFramework/DataGenerator/Config/Dom.php index d4b8f7c8f..830abe6c9 100644 --- a/src/Magento/FunctionalTestingFramework/DataGenerator/Config/Dom.php +++ b/src/Magento/FunctionalTestingFramework/DataGenerator/Config/Dom.php @@ -81,6 +81,13 @@ public function initDom($xml, $filename = null) } } + $itemNodes = $dom->getElementsByTagName('item'); + /** @var \DOMElement $itemNode */ + foreach ($itemNodes as $itemKey => $itemNode) { + if ($itemNode->hasAttribute("name") === false) { + $itemNode->setAttribute("name", (string)$itemKey); + } + } return $dom; } } diff --git a/src/Magento/FunctionalTestingFramework/DataGenerator/Config/Reader/Filesystem.php b/src/Magento/FunctionalTestingFramework/DataGenerator/Config/Reader/Filesystem.php deleted file mode 100644 index 4f21fabea..000000000 --- a/src/Magento/FunctionalTestingFramework/DataGenerator/Config/Reader/Filesystem.php +++ /dev/null @@ -1,81 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -namespace Magento\FunctionalTestingFramework\DataGenerator\Config\Reader; - -/** - * Filesystem configuration loader. Loads configuration from XML files, split by scopes. - */ -class Filesystem extends \Magento\FunctionalTestingFramework\Config\Reader\Filesystem -{ - /** - * An array of paths which do have a key for merging but instead are value based nodes which can only be appended - * - * @var array - */ - private $mergeablePaths; - - /** - * Constructor - * - * @param \Magento\FunctionalTestingFramework\Config\FileResolverInterface $fileResolver - * @param \Magento\FunctionalTestingFramework\Config\ConverterInterface $converter - * @param \Magento\FunctionalTestingFramework\Config\SchemaLocatorInterface $schemaLocator - * @param \Magento\FunctionalTestingFramework\Config\ValidationStateInterface $validationState - * @param string $fileName - * @param array $idAttributes - * @param array $mergeablePaths - * @param string $domDocumentClass - * @param string $defaultScope - */ - public function __construct( - \Magento\FunctionalTestingFramework\Config\FileResolverInterface $fileResolver, - \Magento\FunctionalTestingFramework\Config\ConverterInterface $converter, - \Magento\FunctionalTestingFramework\Config\SchemaLocatorInterface $schemaLocator, - \Magento\FunctionalTestingFramework\Config\ValidationStateInterface $validationState, - $fileName, - $idAttributes = [], - $mergeablePaths = [], - $domDocumentClass = \Magento\FunctionalTestingFramework\Config\Dom::class, - $defaultScope = 'global' - ) { - $this->fileResolver = $fileResolver; - $this->converter = $converter; - $this->fileName = $fileName; - $this->idAttributes = array_replace($this->idAttributes, $idAttributes); - $this->mergeablePaths = $mergeablePaths; - $this->validationState = $validationState; - $this->schemaFile = $schemaLocator->getSchema(); - $this->perFileSchema = $schemaLocator->getPerFileSchema() && $validationState->isValidationRequired() - ? $schemaLocator->getPerFileSchema() : null; - $this->domDocumentClass = $domDocumentClass; - $this->defaultScope = $defaultScope; - } - - /** - * Return newly created instance of a config merger. Overridden to include new arg in mergerClass. - * - * @param string $mergerClass - * @param string $initialContents - * @return \Magento\FunctionalTestingFramework\Config\Dom - * @throws \UnexpectedValueException - */ - protected function createConfigMerger($mergerClass, $initialContents) - { - $result = new $mergerClass( - $initialContents, - $this->idAttributes, - $this->mergeablePaths, - null, - $this->perFileSchema - ); - if (!$result instanceof \Magento\FunctionalTestingFramework\Config\Dom) { - throw new \UnexpectedValueException( - "Instance of the DOM config merger is expected, got {$mergerClass} instead." - ); - } - return $result; - } -} diff --git a/src/Magento/FunctionalTestingFramework/DataGenerator/Handlers/CredentialStore.php b/src/Magento/FunctionalTestingFramework/DataGenerator/Handlers/CredentialStore.php index 0514cfe57..0347ddaa8 100644 --- a/src/Magento/FunctionalTestingFramework/DataGenerator/Handlers/CredentialStore.php +++ b/src/Magento/FunctionalTestingFramework/DataGenerator/Handlers/CredentialStore.php @@ -6,22 +6,37 @@ namespace Magento\FunctionalTestingFramework\DataGenerator\Handlers; +use Magento\FunctionalTestingFramework\Exceptions\Collector\ExceptionCollector; use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; +use Magento\FunctionalTestingFramework\DataGenerator\Handlers\SecretStorage\BaseStorage; use Magento\FunctionalTestingFramework\DataGenerator\Handlers\SecretStorage\FileStorage; use Magento\FunctionalTestingFramework\DataGenerator\Handlers\SecretStorage\VaultStorage; +use Magento\FunctionalTestingFramework\DataGenerator\Handlers\SecretStorage\AwsSecretsManagerStorage; +use Magento\FunctionalTestingFramework\Util\Path\UrlFormatter; class CredentialStore { const ARRAY_KEY_FOR_VAULT = 'vault'; const ARRAY_KEY_FOR_FILE = 'file'; + const ARRAY_KEY_FOR_AWS_SECRETS_MANAGER = 'aws'; + + const CREDENTIAL_STORAGE_INFO = 'You need to configure at least one of these options: ' + . '.credentials file, HashiCorp Vault or AWS Secrets Manager correctly'; /** * Credential storage array * - * @var array + * @var BaseStorage[] */ private $credStorage = []; + /** + * Boolean to indicate if credential storage have been initialized + * + * @var boolean + */ + private $initialized; + /** * Singleton instance * @@ -29,15 +44,21 @@ class CredentialStore */ private static $INSTANCE = null; + /** + * Exception contexts + * + * @var ExceptionCollector + */ + private $exceptionContexts; + /** * Static singleton getter for CredentialStore Instance * * @return CredentialStore - * @throws TestFrameworkException */ public static function getInstance() { - if (self::$INSTANCE == null) { + if (self::$INSTANCE === null) { self::$INSTANCE = new CredentialStore(); } @@ -46,35 +67,11 @@ public static function getInstance() /** * CredentialStore constructor - * - * @throws TestFrameworkException */ private function __construct() { - // Initialize file storage - try { - $this->credStorage[self::ARRAY_KEY_FOR_FILE] = new FileStorage(); - } catch (TestFrameworkException $e) { - } - - // Initialize vault storage - $cvAddress = getenv('CREDENTIAL_VAULT_ADDRESS'); - $cvSecretPath = getenv('CREDENTIAL_VAULT_SECRET_BASE_PATH'); - if ($cvAddress !== false && $cvSecretPath !== false) { - try { - $this->credStorage[self::ARRAY_KEY_FOR_VAULT] = new VaultStorage( - rtrim($cvAddress, '/'), - '/' . trim($cvSecretPath, '/') - ); - } catch (TestFrameworkException $e) { - } - } - - if (empty($this->credStorage)) { - throw new TestFrameworkException( - "No credential storage is properly configured. Please configure vault or .credentials file." - ); - } + $this->initialized = false; + $this->exceptionContexts = new ExceptionCollector(); } /** @@ -86,8 +83,11 @@ private function __construct() */ public function getSecret($key) { - // Get secret data from storage according to the order they are stored - // File storage is preferred over vault storage to allow local secret value overriding remote secret value + // Initialize credential storage if it's not been done + $this->initializeCredentialStorage(); + + // Get secret data from storage according to the order they are stored which follows this precedence: + // FileStorage > VaultStorage > AwsSecretsManagerStorage foreach ($this->credStorage as $storage) { $value = $storage->getEncryptedValue($key); if (null !== $value) { @@ -95,9 +95,12 @@ public function getSecret($key) } } + $exceptionContexts = $this->getExceptionContexts(); + $this->resetExceptionContext(); throw new TestFrameworkException( - "\"{$key}\" not defined in vault or .credentials file, " - . "please provide a value in order to use this secret in a test." + "{$key} not found. " . self::CREDENTIAL_STORAGE_INFO + . " and ensure key, value exists to use _CREDS in tests." + . $exceptionContexts ); } @@ -105,27 +108,189 @@ public function getSecret($key) * Return decrypted input value * * @param string $value - * @return string + * @return string|false The decrypted string on success or false on failure + * @throws TestFrameworkException */ public function decryptSecretValue($value) { - // Loop through storage to decrypt value - foreach ($this->credStorage as $storage) { - return $storage->getDecryptedValue($value); - } + // Initialize credential storage if it's not been done + $this->initializeCredentialStorage(); + + // Decrypt secret value + return BaseStorage::getDecryptedValue($value); } /** * Return decrypted values for all occurrences from input string * * @param string $string - * @return mixed + * @return string|false The decrypted string on success or false on failure + * @throws TestFrameworkException */ public function decryptAllSecretsInString($string) { - // Loop through storage to decrypt all occurrences from input string - foreach ($this->credStorage as $storage) { - return $storage->getAllDecryptedValuesInString($string); + // Initialize credential storage if it's not been done + $this->initializeCredentialStorage(); + + // Decrypt all secret values in string + return BaseStorage::getAllDecryptedValuesInString($string); + } + + /** + * Setter for exception contexts + * + * @param string $type + * @param string $context + * @return void + */ + public function setExceptionContexts($type, $context) + { + $typeArray = [self::ARRAY_KEY_FOR_FILE, self::ARRAY_KEY_FOR_VAULT, self::ARRAY_KEY_FOR_AWS_SECRETS_MANAGER]; + if (in_array($type, $typeArray) && !empty($context)) { + $this->exceptionContexts->addError($type, $context); + } + } + + /** + * Return collected exception contexts + * + * @return string + */ + private function getExceptionContexts() + { + // Gather all exceptions collected + $exceptionMessage = "\n"; + foreach ($this->exceptionContexts->getErrors() as $type => $exceptions) { + $exceptionMessage .= "\nException from "; + if ($type === self::ARRAY_KEY_FOR_FILE) { + $exceptionMessage .= "File Storage: \n"; + } + if ($type === self::ARRAY_KEY_FOR_VAULT) { + $exceptionMessage .= "Vault Storage: \n"; + } + if ($type === self::ARRAY_KEY_FOR_AWS_SECRETS_MANAGER) { + $exceptionMessage .= "AWS Secrets Manager Storage: \n"; + } + + if (is_array($exceptions)) { + $exceptionMessage .= implode("\n", $exceptions) . "\n"; + } else { + $exceptionMessage .= $exceptions . "\n"; + } + } + return $exceptionMessage; + } + + /** + * Reset exception contexts to empty array + * + * @return void + */ + private function resetExceptionContext() + { + $this->exceptionContexts->reset(); + } + + /** + * Initialize all available credential storage + * + * @return void + * @throws TestFrameworkException + */ + private function initializeCredentialStorage() + { + if (!$this->initialized) { + // Initialize credential storage by defined order of precedence as the following + $this->initializeFileStorage(); + $this->initializeVaultStorage(); + $this->initializeAwsSecretsManagerStorage(); + $this->initialized = true; + } + + if (empty($this->credStorage)) { + throw new TestFrameworkException( + 'Invalid Credential Storage. ' . self::CREDENTIAL_STORAGE_INFO + . '.' . $this->getExceptionContexts() + ); + } + $this->resetExceptionContext(); + } + + /** + * Initialize file storage + * + * @return void + */ + private function initializeFileStorage(): void + { + // Initialize file storage + try { + $fileStorage = new FileStorage(); + $fileStorage->initialize(); + $this->credStorage[self::ARRAY_KEY_FOR_FILE] = $fileStorage; + } catch (TestFrameworkException $e) { + // Print error message in console + print_r($e->getMessage()); + // Save to exception context for Allure report + $this->setExceptionContexts(self::ARRAY_KEY_FOR_FILE, $e->getMessage()); + } + } + + /** + * Initialize Vault storage + * + * @return void + */ + private function initializeVaultStorage() + { + // Initialize vault storage + $cvAddress = getenv('CREDENTIAL_VAULT_ADDRESS'); + $cvSecretPath = getenv('CREDENTIAL_VAULT_SECRET_BASE_PATH'); + if ($cvAddress !== false && $cvSecretPath !== false) { + try { + $this->credStorage[self::ARRAY_KEY_FOR_VAULT] = new VaultStorage( + UrlFormatter::format($cvAddress, false), + '/' . trim($cvSecretPath, '/') + ); + } catch (TestFrameworkException $e) { + // Print error message in console + print_r($e->getMessage()); + // Save to exception context for Allure report + $this->setExceptionContexts(self::ARRAY_KEY_FOR_VAULT, $e->getMessage()); + } + } + } + + /** + * Initialize AWS Secrets Manager storage + * + * @return void + */ + private function initializeAwsSecretsManagerStorage() + { + // Initialize AWS Secrets Manager storage + $awsRegion = getenv('CREDENTIAL_AWS_SECRETS_MANAGER_REGION'); + $awsProfile = getenv('CREDENTIAL_AWS_SECRETS_MANAGER_PROFILE'); + $awsId = getenv('CREDENTIAL_AWS_ACCOUNT_ID'); + if (!empty($awsRegion)) { + if (empty($awsProfile)) { + $awsProfile = null; + } + if (empty($awsId)) { + $awsId = null; + } + try { + $this->credStorage[self::ARRAY_KEY_FOR_AWS_SECRETS_MANAGER] = new AwsSecretsManagerStorage( + $awsRegion, + $awsProfile, + $awsId + ); + } catch (TestFrameworkException $e) { + // Print error message in console + print_r($e->getMessage()); + // Save to exception context for Allure report + $this->setExceptionContexts(self::ARRAY_KEY_FOR_AWS_SECRETS_MANAGER, $e->getMessage()); + } } } } diff --git a/src/Magento/FunctionalTestingFramework/DataGenerator/Handlers/DataObjectHandler.php b/src/Magento/FunctionalTestingFramework/DataGenerator/Handlers/DataObjectHandler.php index 95a2b8b93..da7888388 100644 --- a/src/Magento/FunctionalTestingFramework/DataGenerator/Handlers/DataObjectHandler.php +++ b/src/Magento/FunctionalTestingFramework/DataGenerator/Handlers/DataObjectHandler.php @@ -13,6 +13,8 @@ use Magento\FunctionalTestingFramework\ObjectManager\ObjectHandlerInterface; use Magento\FunctionalTestingFramework\ObjectManagerFactory; use Magento\FunctionalTestingFramework\DataGenerator\Util\DataExtensionUtil; +use Magento\FunctionalTestingFramework\Util\Logger\LoggingUtil; +use Magento\FunctionalTestingFramework\Util\Validation\NameValidationUtil; class DataObjectHandler implements ObjectHandlerInterface { @@ -57,6 +59,20 @@ class DataObjectHandler implements ObjectHandlerInterface */ private $extendUtil; + /** + * Validates and keeps track of entity name violations. + * + * @var NameValidationUtil + */ + private $entityNameValidator; + + /** + * Validates and keeps track of entity key violations. + * + * @var NameValidationUtil + */ + private $entityKeyValidator; + /** * Constructor */ @@ -67,6 +83,8 @@ private function __construct() if (!$parserOutput) { return; } + $this->entityNameValidator = new NameValidationUtil(); + $this->entityKeyValidator = new NameValidationUtil(); $this->entityDataObjects = $this->processParserOutput($parserOutput); $this->extendUtil = new DataExtensionUtil(); } @@ -119,6 +137,7 @@ public function getAllObjects() * @param string[] $parserOutput Primitive array output from the Magento parser. * @return EntityDataObject[] * @throws XmlException + * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ private function processParserOutput($parserOutput) { @@ -130,13 +149,19 @@ private function processParserOutput($parserOutput) throw new XmlException(sprintf(self::DATA_NAME_ERROR_MSG, $name)); } + $filename = $rawEntity[self::_FILENAME] ?? null; + $this->entityNameValidator->validatePascalCase( + $name, + NameValidationUtil::DATA_ENTITY_NAME, + $filename + ); $type = $rawEntity[self::_TYPE] ?? null; $data = []; + $deprecated = null; $linkedEntities = []; $uniquenessData = []; $vars = []; $parentEntity = null; - $filename = $rawEntity[self::_FILENAME] ?? null; if (array_key_exists(self::_DATA, $rawEntity)) { $data = $this->processDataElements($rawEntity); @@ -163,6 +188,14 @@ private function processParserOutput($parserOutput) $parentEntity = $rawEntity[self::_EXTENDS]; } + if (array_key_exists(self::OBJ_DEPRECATED, $rawEntity)) { + $deprecated = $rawEntity[self::OBJ_DEPRECATED]; + LoggingUtil::getInstance()->getLogger(self::class)->deprecation( + "The data entity '{$name}' is deprecated.", + ["fileName" => $filename, "deprecatedMessage" => $deprecated] + ); + } + $entityDataObject = new EntityDataObject( $name, $type, @@ -171,12 +204,14 @@ private function processParserOutput($parserOutput) $uniquenessData, $vars, $parentEntity, - $filename + $filename, + $deprecated ); $entityDataObjects[$entityDataObject->getName()] = $entityDataObject; } - + $this->entityNameValidator->summarize(NameValidationUtil::DATA_ENTITY_NAME); + $this->entityKeyValidator->summarize(NameValidationUtil::DATA_ENTITY_KEY); return $entityDataObjects; } @@ -191,8 +226,8 @@ private function processParserOutput($parserOutput) private function processArray($arrayItems, $data, $key) { $items = []; - foreach ($arrayItems as $item) { - $items[] = $item[self::_VALUE]; + foreach ($arrayItems as $key => $item) { + $items[$key] = $item[self::_VALUE]; } return array_merge($items, $data[$key] ?? []); @@ -208,7 +243,14 @@ private function processDataElements($entityData) { $dataValues = []; foreach ($entityData[self::_DATA] as $dataElement) { - $dataElementKey = strtolower($dataElement[self::_KEY]); + $originalDataElementKey = $dataElement[self::_KEY]; + $filename = $entityData[self::_FILENAME] ?? null; + $this->entityKeyValidator->validateCamelCase( + $originalDataElementKey, + NameValidationUtil::DATA_ENTITY_KEY, + $filename + ); + $dataElementKey = strtolower($originalDataElementKey); $dataElementValue = $dataElement[self::_VALUE] ?? ""; $dataValues[$dataElementKey] = $dataElementValue; } @@ -277,8 +319,8 @@ private function processVarElements($entityData) */ private function extendDataObject($dataObject) { - if ($dataObject->getParentName() != null) { - if ($dataObject->getParentName() == $dataObject->getName()) { + if ($dataObject->getParentName() !== null) { + if ($dataObject->getParentName() === $dataObject->getName()) { throw new TestFrameworkException("Mftf Data can not extend from itself: " . $dataObject->getName()); } return $this->extendUtil->extendEntity($dataObject); diff --git a/src/Magento/FunctionalTestingFramework/DataGenerator/Handlers/OperationDefinitionObjectHandler.php b/src/Magento/FunctionalTestingFramework/DataGenerator/Handlers/OperationDefinitionObjectHandler.php index b768d9bd8..eb4651b3e 100644 --- a/src/Magento/FunctionalTestingFramework/DataGenerator/Handlers/OperationDefinitionObjectHandler.php +++ b/src/Magento/FunctionalTestingFramework/DataGenerator/Handlers/OperationDefinitionObjectHandler.php @@ -11,6 +11,8 @@ use Magento\FunctionalTestingFramework\DataGenerator\Util\OperationElementExtractor; use Magento\FunctionalTestingFramework\ObjectManager\ObjectHandlerInterface; use Magento\FunctionalTestingFramework\ObjectManagerFactory; +use Magento\FunctionalTestingFramework\Util\Logger\LoggingUtil; +use Magento\FunctionalTestingFramework\Util\Validation\NameValidationUtil; class OperationDefinitionObjectHandler implements ObjectHandlerInterface { @@ -128,16 +130,21 @@ public function getOperationDefinition($operation, $dataType) * @return void * @throws \Exception * - * @SuppressWarnings(PHPMD.NPathComplexity) - * @SuppressWarnings(PHPMD.UnusedPrivateMethod) - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + * @SuppressWarnings(PHPMD) */ private function initialize() { $objectManager = ObjectManagerFactory::getObjectManager(); $parser = $objectManager->create(OperationDefinitionParser::class); $parserOutput = $parser->readOperationMetadata()[OperationDefinitionObjectHandler::ENTITY_OPERATION_ROOT_TAG]; + + $operationNameValidator = new NameValidationUtil(); foreach ($parserOutput as $dataDefName => $opDefArray) { + $operationNameValidator->validatePascalCase( + $dataDefName, + NameValidationUtil::METADATA_OPERATION_NAME + ); + $operation = $opDefArray[OperationDefinitionObjectHandler::ENTITY_OPERATION_TYPE]; $dataType = $opDefArray[OperationDefinitionObjectHandler::ENTITY_OPERATION_DATA_TYPE]; $url = $opDefArray[OperationDefinitionObjectHandler::ENTITY_OPERATION_URL] ?? null; @@ -145,6 +152,7 @@ private function initialize() $auth = $opDefArray[OperationDefinitionObjectHandler::ENTITY_OPERATION_AUTH] ?? null; $successRegex = $opDefArray[OperationDefinitionObjectHandler::ENTITY_OPERATION_SUCCESS_REGEX] ?? null; $returnRegex = $opDefArray[OperationDefinitionObjectHandler::ENTITY_OPERATION_RETURN_REGEX] ?? null; + $deprecated = $opDefArray[ObjectHandlerInterface::OBJ_DEPRECATED] ?? null; $returnIndex = $opDefArray[OperationDefinitionObjectHandler::ENTITY_OPERATION_RETURN_INDEX] ?? 0; $contentType = $opDefArray[OperationDefinitionObjectHandler::ENTITY_OPERATION_CONTENT_TYPE][0]['value'] ?? null; @@ -205,6 +213,13 @@ private function initialize() } } + if ($deprecated !== null) { + LoggingUtil::getInstance()->getLogger(self::class)->deprecation( + $message = "The operation {$dataDefName} is deprecated.", + ["operationType" => $operation, "deprecatedMessage" => $deprecated] + ); + } + $this->operationDefinitionObjects[$operation . $dataType] = new OperationDefinitionObject( $dataDefName, $operation, @@ -219,9 +234,11 @@ private function initialize() $removeBackend, $successRegex, $returnRegex, - $returnIndex + $returnIndex, + $deprecated ); } + $operationNameValidator->summarize(NameValidationUtil::METADATA_OPERATION_NAME); } /** diff --git a/src/Magento/FunctionalTestingFramework/DataGenerator/Handlers/PersistedObjectHandler.php b/src/Magento/FunctionalTestingFramework/DataGenerator/Handlers/PersistedObjectHandler.php index 42286564f..e9f905a6b 100644 --- a/src/Magento/FunctionalTestingFramework/DataGenerator/Handlers/PersistedObjectHandler.php +++ b/src/Magento/FunctionalTestingFramework/DataGenerator/Handlers/PersistedObjectHandler.php @@ -55,7 +55,6 @@ private function __construct() * Return the singleton instance of this class. Initialize it if needed. * * @return PersistedObjectHandler - * @throws \Exception */ public static function getInstance() { @@ -87,17 +86,18 @@ public function createEntity( foreach ($dependentObjectKeys as $objectKey) { $retrievedDependentObjects[] = $this->retrieveEntity($objectKey, $scope); } - - foreach ($overrideFields as $index => $field) { - try { - $overrideFields[$index] = CredentialStore::getInstance()->decryptAllSecretsInString($field); - } catch (TestFrameworkException $e) { - //do not rethrow if Credentials are not defined - $overrideFields[$index] = $field; - } - } $retrievedEntity = DataObjectHandler::getInstance()->getObject($entity); + + if ($retrievedEntity === null) { + throw new TestReferenceException( + "Entity \"" . $entity . "\" does not exist." . + "\nException occurred executing action at StepKey \"" . $key . "\"" + ); + } + + $overrideFields = $this->resolveOverrideFields($overrideFields); + $persistedObject = new DataPersistenceHandler( $retrievedEntity, $retrievedDependentObjects, @@ -106,9 +106,9 @@ public function createEntity( $persistedObject->createEntity($storeCode); - if ($scope == self::TEST_SCOPE) { + if ($scope === self::TEST_SCOPE) { $this->testObjects[$key] = $persistedObject; - } elseif ($scope == self::HOOK_SCOPE) { + } elseif ($scope === self::HOOK_SCOPE) { $this->hookObjects[$key] = $persistedObject; } else { $this->suiteObjects[$key] = $persistedObject; @@ -170,9 +170,9 @@ public function getEntity($key, $scope, $entity, $dependentObjectKeys = [], $sto ); $persistedObject->getEntity($index, $storeCode); - if ($scope == self::TEST_SCOPE) { + if ($scope === self::TEST_SCOPE) { $this->testObjects[$key] = $persistedObject; - } elseif ($scope == self::HOOK_SCOPE) { + } elseif ($scope === self::HOOK_SCOPE) { $this->hookObjects[$key] = $persistedObject; } else { $this->suiteObjects[$key] = $persistedObject; @@ -193,8 +193,9 @@ public function retrieveEntityField($stepKey, $field, $scope) $warnMsg = "Undefined field {$field} in entity object with a stepKey of {$stepKey}\n"; $warnMsg .= "Please fix the invalid reference. This will result in fatal error in next major release."; //TODO: change this to throw an exception in next major release - LoggingUtil::getInstance()->getLogger(PersistedObjectHandler::class)->warn($warnMsg); - if (MftfApplicationConfig::getConfig()->getPhase() !== MftfApplicationConfig::UNIT_TEST_PHASE) { + LoggingUtil::getInstance()->getLogger(PersistedObjectHandler::class)->warning($warnMsg); + if (MftfApplicationConfig::getConfig()->verboseEnabled() + && MftfApplicationConfig::getConfig()->getPhase() !== MftfApplicationConfig::UNIT_TEST_PHASE) { print("\n$warnMsg\n"); } } @@ -213,7 +214,7 @@ private function retrieveEntity($stepKey, $scope) // Assume TEST_SCOPE is default $entityArrays = [$this->testObjects, $this->hookObjects, $this->suiteObjects]; - if ($scope == self::HOOK_SCOPE) { + if ($scope === self::HOOK_SCOPE) { $entityArrays[0] = $this->hookObjects; $entityArrays[1] = $this->testObjects; } @@ -253,4 +254,29 @@ public function clearSuiteObjects() { $this->suiteObjects = []; } + + /** + * Resolve secret values in $overrideFields + * + * @param array $overrideFields + * @return array + */ + private function resolveOverrideFields($overrideFields) + { + foreach ($overrideFields as $index => $field) { + if (is_array($field)) { + $overrideFields[$index] = $this->resolveOverrideFields($field); + } elseif (is_string($field)) { + try { + $decrptedField = CredentialStore::getInstance()->decryptAllSecretsInString($field); + if ($decrptedField !== false) { + $overrideFields[$index] = $decrptedField; + } + } catch (TestFrameworkException $e) { + //catch exception if Credentials are not defined + } + } + } + return $overrideFields; + } } diff --git a/src/Magento/FunctionalTestingFramework/DataGenerator/Handlers/SecretStorage/AwsSecretsManagerStorage.php b/src/Magento/FunctionalTestingFramework/DataGenerator/Handlers/SecretStorage/AwsSecretsManagerStorage.php new file mode 100644 index 000000000..6bd1ff144 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/DataGenerator/Handlers/SecretStorage/AwsSecretsManagerStorage.php @@ -0,0 +1,212 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\FunctionalTestingFramework\DataGenerator\Handlers\SecretStorage; + +use Magento\FunctionalTestingFramework\Config\MftfApplicationConfig; +use Magento\FunctionalTestingFramework\DataGenerator\Handlers\CredentialStore; +use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; +use Magento\FunctionalTestingFramework\Util\Logger\LoggingUtil; +use Aws\SecretsManager\SecretsManagerClient; +use Aws\Exception\AwsException; +use Aws\Result; +use InvalidArgumentException; +use Exception; + +class AwsSecretsManagerStorage extends BaseStorage +{ + /** + * Mftf project path + */ + const MFTF_PATH = 'mftf'; + + /** + * AWS Secrets Manager partial ARN + */ + const AWS_SM_PARTIAL_ARN = 'arn:aws:secretsmanager:'; + + /** + * AWS Secrets Manager version + * + * Last tested version '2017-10-17' + */ + const LATEST_VERSION = 'latest'; + + /** + * SecretsManagerClient client + * + * @var SecretsManagerClient + */ + private $client = null; + + /** + * AWS account id + * + * @var string + */ + private $awsAccountId; + + /** + * AWS account region + * + * @var string + */ + private $region; + + /** + * AwsSecretsManagerStorage constructor + * + * @param string $region + * @param string $profile + * @param string $accountId + * @throws TestFrameworkException + * @throws InvalidArgumentException + */ + public function __construct($region, $profile = null, $accountId = null) + { + parent::__construct(); + $this->createAwsSecretsManagerClient($region, $profile); + $this->region = $region; + $this->awsAccountId = $accountId; + } + + /** + * Returns the value of a secret based on corresponding key + * + * @param string $key + * @return string|null + * @throws Exception + */ + public function getEncryptedValue($key) + { + // Check if secret is in cached array + if (null !== ($value = parent::getEncryptedValue($key))) { + return $value; + } + + if (MftfApplicationConfig::getConfig()->verboseEnabled()) { + LoggingUtil::getInstance()->getLogger(AwsSecretsManagerStorage::class)->debug( + "Retrieving value for key name {$key} from AWS Secrets Manager" + ); + } + + $reValue = null; + try { + // Split vendor/key to construct secret id + list($vendor, $key) = explode('/', trim($key, '/'), 2); + // If AWS account id is specified, create and use full ARN, otherwise use partial ARN as secret id + $secretId = ''; + if (!empty($this->awsAccountId)) { + $secretId = self::AWS_SM_PARTIAL_ARN . $this->region . ':' . $this->awsAccountId . ':secret:'; + } + $secretId .= self::MFTF_PATH + . '/' + . $vendor + . '/' + . $key; + // Read value by id from AWS Secrets Manager, and parse the result + $value = $this->parseAwsSecretResult( + $this->client->getSecretValue(['SecretId' => $secretId]), + $key + ); + // Encrypt value for return + $reValue = openssl_encrypt($value, parent::ENCRYPTION_ALGO, parent::$encodedKey, 0, parent::$iv); + parent::$cachedSecretData[$key] = $reValue; + } catch (AwsException $e) { + $errMessage = "\nAWS Exception:\n" . $e->getAwsErrorMessage() + . "\nUnable to read value for key {$key} from AWS Secrets Manager\n"; + // Print error message in console + print_r($errMessage); + // Add error message in mftf log if verbose is enable + if (MftfApplicationConfig::getConfig()->verboseEnabled()) { + LoggingUtil::getInstance()->getLogger(AwsSecretsManagerStorage::class)->debug($errMessage); + } + // Save to exception context for Allure report + CredentialStore::getInstance()->setExceptionContexts( + CredentialStore::ARRAY_KEY_FOR_AWS_SECRETS_MANAGER, + $errMessage + ); + } catch (\Exception $e) { + $errMessage = "\nException:\n" . $e->getMessage() + . "\nUnable to read value for key {$key} from AWS Secrets Manager\n"; + // Print error message in console + print_r($errMessage); + // Add error message in mftf log if verbose is enable + if (MftfApplicationConfig::getConfig()->verboseEnabled()) { + LoggingUtil::getInstance()->getLogger(AwsSecretsManagerStorage::class)->debug($errMessage); + } + // Save to exception context for Allure report + CredentialStore::getInstance()->setExceptionContexts( + CredentialStore::ARRAY_KEY_FOR_AWS_SECRETS_MANAGER, + $errMessage + ); + } + return $reValue; + } + + /** + * Parse AWS result object and return secret for key + * + * @param Result $awsResult + * @param string $key + * @return string + * @throws TestFrameworkException + */ + private function parseAwsSecretResult($awsResult, $key) + { + // Return secret from the associated KMS CMK + if (isset($awsResult['SecretString'])) { + $rawSecret = $awsResult['SecretString']; + } else { + throw new TestFrameworkException( + "'SecretString' field is not set in AWS Result. Error parsing result from AWS Secrets Manager" + ); + } + + // Secrets are saved as JSON structures of key/value pairs if using AWS Secrets Manager console, and + // Secrets are saved as plain text if using AWS CLI. We need to handle both cases. + $secret = json_decode($rawSecret, true); + if (isset($secret[$key])) { + return $secret[$key]; + } elseif (is_string($rawSecret)) { + return $rawSecret; + } + throw new TestFrameworkException( + "$key not found or value is not string . Error parsing result from AWS Secrets Manager" + ); + } + + /** + * Create Aws Secrets Manager client + * + * @param string $region + * @param string $profile + * @return void + * @throws TestFrameworkException + * @throws InvalidArgumentException + */ + private function createAwsSecretsManagerClient($region, $profile) + { + if (null !== $this->client) { + return; + } + + $options = [ + 'region' => $region, + 'version' => self::LATEST_VERSION, + ]; + + if (!empty($profile)) { + $options['profile'] = $profile; + } + + // Create AWS Secrets Manager client + $this->client = new SecretsManagerClient($options); + if ($this->client === null) { + throw new TestFrameworkException("Unable to create AWS Secrets Manager client"); + } + } +} diff --git a/src/Magento/FunctionalTestingFramework/DataGenerator/Handlers/SecretStorage/BaseStorage.php b/src/Magento/FunctionalTestingFramework/DataGenerator/Handlers/SecretStorage/BaseStorage.php index cb892a545..6eb7fa7e8 100644 --- a/src/Magento/FunctionalTestingFramework/DataGenerator/Handlers/SecretStorage/BaseStorage.php +++ b/src/Magento/FunctionalTestingFramework/DataGenerator/Handlers/SecretStorage/BaseStorage.php @@ -58,30 +58,38 @@ public function getEncryptedValue($key) /** * Takes a value encrypted at runtime and decrypts it using the object's initial vector + * return the decrypted string on success or false on failure * * @param string $value - * @return string + * @return string|false The decrypted string on success or false on failure */ - public function getDecryptedValue($value) + public static function getDecryptedValue($value) { return openssl_decrypt($value, self::ENCRYPTION_ALGO, self::$encodedKey, 0, self::$iv); } /** * Takes a string that contains encrypted data at runtime and decrypts each value + * return false if no decryption happens or a failure occurs * * @param string $string - * @return mixed + * @return string|false The decrypted string on success or false on failure */ - public function getAllDecryptedValuesInString($string) + public static function getAllDecryptedValuesInString($string) { - $newString = $string; + $decrypted = false; foreach (self::$cachedSecretData as $key => $secretValue) { - if (strpos($newString, $secretValue) !== false) { + if (strpos($string, $secretValue) !== false) { $decryptedValue = self::getDecryptedValue($secretValue); - $newString = str_replace($secretValue, $decryptedValue, $newString); + if ($decryptedValue === false) { + return false; + } + if (!$decrypted) { + $decrypted = true; + } + $string = str_replace($secretValue, $decryptedValue, $string); } } - return $newString; + return $decrypted ? $string : false; } } diff --git a/src/Magento/FunctionalTestingFramework/DataGenerator/Handlers/SecretStorage/FileStorage.php b/src/Magento/FunctionalTestingFramework/DataGenerator/Handlers/SecretStorage/FileStorage.php index 064610c79..48087e890 100644 --- a/src/Magento/FunctionalTestingFramework/DataGenerator/Handlers/SecretStorage/FileStorage.php +++ b/src/Magento/FunctionalTestingFramework/DataGenerator/Handlers/SecretStorage/FileStorage.php @@ -6,10 +6,10 @@ namespace Magento\FunctionalTestingFramework\DataGenerator\Handlers\SecretStorage; -use Magento\FunctionalTestingFramework\Console\BuildProjectCommand; -use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; use Magento\FunctionalTestingFramework\Config\MftfApplicationConfig; +use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; use Magento\FunctionalTestingFramework\Util\Logger\LoggingUtil; +use Magento\FunctionalTestingFramework\Util\Path\FilePathFormatter; class FileStorage extends BaseStorage { @@ -21,14 +21,17 @@ class FileStorage extends BaseStorage private $secretData = []; /** - * FileStorage constructor + * Initialize secret data value which represents encrypted credentials + * + * @return void * @throws TestFrameworkException */ - public function __construct() + public function initialize(): void { - parent::__construct(); - $creds = $this->readInCredentialsFile(); - $this->secretData = $this->encryptCredFileContents($creds); + if (!$this->secretData) { + $creds = $this->readInCredentialsFile(); + $this->secretData = $this->encryptCredFileContents($creds); + } } /** @@ -36,10 +39,12 @@ public function __construct() * * @param string $key * @return string|null + * @throws TestFrameworkException */ - public function getEncryptedValue($key) + public function getEncryptedValue($key): ?string { - $value = null; + $this->initialize(); + // Check if secret is in cached array if (null !== ($value = parent::getEncryptedValue($key))) { return $value; @@ -71,7 +76,7 @@ private function readInCredentialsFile() $credsFilePath = str_replace( '.credentials.example', '.credentials', - BuildProjectCommand::CREDENTIALS_FILE_PATH + FilePathFormatter::format(TESTS_BP) . '.credentials.example' ); if (!file_exists($credsFilePath)) { @@ -88,6 +93,7 @@ private function readInCredentialsFile() * * @param array $credContents * @return array + * @throws TestFrameworkException */ private function encryptCredFileContents($credContents) { @@ -95,6 +101,10 @@ private function encryptCredFileContents($credContents) foreach ($credContents as $credValue) { if (substr($credValue, 0, 1) === '#' || empty($credValue)) { continue; + } elseif (strpos($credValue, "=") === false) { + throw new TestFrameworkException( + $credValue . " not configured correctly in .credentials file" + ); } list($key, $value) = explode("=", $credValue, 2); diff --git a/src/Magento/FunctionalTestingFramework/DataGenerator/Handlers/SecretStorage/VaultStorage.php b/src/Magento/FunctionalTestingFramework/DataGenerator/Handlers/SecretStorage/VaultStorage.php index 6a9e9f0cf..7e307b30c 100644 --- a/src/Magento/FunctionalTestingFramework/DataGenerator/Handlers/SecretStorage/VaultStorage.php +++ b/src/Magento/FunctionalTestingFramework/DataGenerator/Handlers/SecretStorage/VaultStorage.php @@ -6,11 +6,15 @@ namespace Magento\FunctionalTestingFramework\DataGenerator\Handlers\SecretStorage; +use Laminas\Diactoros\RequestFactory; +use Laminas\Diactoros\StreamFactory; +use Laminas\Diactoros\Uri; +use GuzzleHttp\Client as GuzzleClient; use Magento\FunctionalTestingFramework\Config\MftfApplicationConfig; +use Magento\FunctionalTestingFramework\DataGenerator\Handlers\CredentialStore; use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; use Magento\FunctionalTestingFramework\Util\Logger\LoggingUtil; use Vault\Client; -use VaultTransports\Guzzle6Transport; class VaultStorage extends BaseStorage { @@ -67,7 +71,7 @@ class VaultStorage extends BaseStorage private $secretBasePath; /** - * CredentialVault constructor + * VaultStorage constructor * * @param string $baseUrl * @param string $secretBasePath @@ -77,8 +81,17 @@ public function __construct($baseUrl, $secretBasePath) { parent::__construct(); if (null === $this->client) { - // Creating the client using Guzzle6 Transport and passing a custom url - $this->client = new Client(new Guzzle6Transport(['base_uri' => $baseUrl])); + // client configuration and override http errors settings + $this->client = new Client( + new Uri($baseUrl), + new GuzzleClient([ + 'timeout' => 15, + 'base_uri' => $baseUrl, + 'http_errors' => false + ]), + new RequestFactory(), + new StreamFactory() + ); $this->secretBasePath = $secretBasePath; } $this->readVaultTokenFromFileSystem(); @@ -123,10 +136,14 @@ public function getEncryptedValue($key) $reValue = openssl_encrypt($value, parent::ENCRYPTION_ALGO, parent::$encodedKey, 0, parent::$iv); parent::$cachedSecretData[$key] = $reValue; } catch (\Exception $e) { + $errMessage = "\nUnable to read secret for key name {$key} from vault." . $e->getMessage(); + // Print error message in console + print_r($errMessage); + // Save to exception context for Allure report + CredentialStore::getInstance()->setExceptionContexts('vault', $errMessage); + // Add error message in mftf log if verbose is enable if (MftfApplicationConfig::getConfig()->verboseEnabled()) { - LoggingUtil::getInstance()->getLogger(VaultStorage::class)->debug( - "Unable to read secret for key name {$key} from vault" - ); + LoggingUtil::getInstance()->getLogger(VaultStorage::class)->debug($errMessage); } } return $reValue; @@ -149,6 +166,13 @@ private function authenticated() return true; } } catch (\Exception $e) { + // Print error message in console + print_r($e->getMessage()); + // Save to exception context for Allure report + CredentialStore::getInstance()->setExceptionContexts( + CredentialStore::ARRAY_KEY_FOR_VAULT, + $e->getMessage() + ); } return false; } diff --git a/src/Magento/FunctionalTestingFramework/DataGenerator/Handlers/SecretStorage/VaultTokenAuthStrategy.php b/src/Magento/FunctionalTestingFramework/DataGenerator/Handlers/SecretStorage/VaultTokenAuthStrategy.php index 716344ca1..0f071216d 100644 --- a/src/Magento/FunctionalTestingFramework/DataGenerator/Handlers/SecretStorage/VaultTokenAuthStrategy.php +++ b/src/Magento/FunctionalTestingFramework/DataGenerator/Handlers/SecretStorage/VaultTokenAuthStrategy.php @@ -36,7 +36,7 @@ public function __construct($token) * @return Auth * @throws TestFrameworkException */ - public function authenticate() + public function authenticate(): ?Auth { try { return new Auth(['clientToken' => $this->token]); diff --git a/src/Magento/FunctionalTestingFramework/DataGenerator/Objects/EntityDataObject.php b/src/Magento/FunctionalTestingFramework/DataGenerator/Objects/EntityDataObject.php index 47a966f84..817fb6ec4 100644 --- a/src/Magento/FunctionalTestingFramework/DataGenerator/Objects/EntityDataObject.php +++ b/src/Magento/FunctionalTestingFramework/DataGenerator/Objects/EntityDataObject.php @@ -80,17 +80,25 @@ class EntityDataObject */ private $filename; + /** + * Deprecated message. + * + * @var string + */ + private $deprecated; + /** * Constructor * - * @param string $name - * @param string $type - * @param string[] $data - * @param string[] $linkedEntities - * @param string[] $uniquenessData - * @param string[] $vars - * @param string $parentEntity - * @param string $filename + * @param string $name + * @param string $type + * @param string[] $data + * @param string[] $linkedEntities + * @param string[] $uniquenessData + * @param string[] $vars + * @param string $parentEntity + * @param string $filename + * @param string|null $deprecated */ public function __construct( $name, @@ -100,7 +108,8 @@ public function __construct( $uniquenessData, $vars = [], $parentEntity = null, - $filename = null + $filename = null, + $deprecated = null ) { $this->name = $name; $this->type = $type; @@ -113,6 +122,17 @@ public function __construct( $this->vars = $vars; $this->parentEntity = $parentEntity; $this->filename = $filename; + $this->deprecated = $deprecated; + } + + /** + * Getter for the deprecated attr of the section. + * + * @return string + */ + public function getDeprecated() + { + return $this->deprecated; } /** @@ -213,7 +233,7 @@ private function resolveDataReferences($name, $uniquenessFormat) $this->data[$name_lower], $this->name . '.' . $name ); - if (null === $uniquenessData || $uniquenessFormat == self::NO_UNIQUE_PROCESS) { + if (null === $uniquenessData || $uniquenessFormat === self::NO_UNIQUE_PROCESS) { return $this->data[$name_lower]; } return $this->formatUniqueData($name_lower, $uniquenessData, $uniquenessFormat); @@ -256,7 +276,7 @@ private function formatUniqueData($name, $uniqueData, $uniqueDataFormat) switch ($uniqueDataFormat) { case self::SUITE_UNIQUE_VALUE: $this->checkUniquenessFunctionExists(self::SUITE_UNIQUE_FUNCTION, $uniqueDataFormat); - if ($uniqueData == 'prefix') { + if ($uniqueData === 'prefix') { return msqs($this->getName()) . $this->data[$name]; } else { // $uniData == 'suffix' return $this->data[$name] . msqs($this->getName()); @@ -264,21 +284,21 @@ private function formatUniqueData($name, $uniqueData, $uniqueDataFormat) break; case self::CEST_UNIQUE_VALUE: $this->checkUniquenessFunctionExists(self::CEST_UNIQUE_FUNCTION, $uniqueDataFormat); - if ($uniqueData == 'prefix') { + if ($uniqueData === 'prefix') { return msq($this->getName()) . $this->data[$name]; } else { // $uniqueData == 'suffix' return $this->data[$name] . msq($this->getName()); } break; case self::SUITE_UNIQUE_NOTATION: - if ($uniqueData == 'prefix') { + if ($uniqueData === 'prefix') { return self::SUITE_UNIQUE_FUNCTION . '("' . $this->getName() . '")' . $this->data[$name]; } else { // $uniqueData == 'suffix' return $this->data[$name] . self::SUITE_UNIQUE_FUNCTION . '("' . $this->getName() . '")'; } break; case self::CEST_UNIQUE_NOTATION: - if ($uniqueData == 'prefix') { + if ($uniqueData === 'prefix') { return self::CEST_UNIQUE_FUNCTION . '("' . $this->getName() . '")' . $this->data[$name]; } else { // $uniqueData == 'suffix' return $this->data[$name] . self::CEST_UNIQUE_FUNCTION . '("' . $this->getName() . '")'; @@ -338,7 +358,7 @@ public function getLinkedEntitiesOfType($type) $groupedArray = []; foreach ($this->linkedEntities as $entityName => $entityType) { - if ($entityType == $type) { + if ($entityType === $type) { $groupedArray[] = $entityName; } } diff --git a/src/Magento/FunctionalTestingFramework/DataGenerator/Objects/OperationDefinitionObject.php b/src/Magento/FunctionalTestingFramework/DataGenerator/Objects/OperationDefinitionObject.php index 721a9dcc9..ff84c0959 100644 --- a/src/Magento/FunctionalTestingFramework/DataGenerator/Objects/OperationDefinitionObject.php +++ b/src/Magento/FunctionalTestingFramework/DataGenerator/Objects/OperationDefinitionObject.php @@ -6,8 +6,11 @@ namespace Magento\FunctionalTestingFramework\DataGenerator\Objects; +use Magento\FunctionalTestingFramework\Util\Logger\LoggingUtil; + /** * Class OperationDefinitionObject + * @SuppressWarnings(PHPMD) */ class OperationDefinitionObject { @@ -117,22 +120,30 @@ class OperationDefinitionObject */ private $removeBackend; + /** + * Deprecated message. + * + * @var string + */ + private $deprecated; + /** * OperationDefinitionObject constructor. - * @param string $name - * @param string $operation - * @param string $dataType - * @param string $apiMethod - * @param string $apiUri - * @param string $auth - * @param array $headers - * @param array $params - * @param array $metaData - * @param string $contentType - * @param boolean $removeBackend - * @param string $successRegex - * @param string $returnRegex - * @param string $returnIndex + * @param string $name + * @param string $operation + * @param string $dataType + * @param string $apiMethod + * @param string $apiUri + * @param string $auth + * @param array $headers + * @param array $params + * @param array $metaData + * @param string $contentType + * @param boolean $removeBackend + * @param string $successRegex + * @param string $returnRegex + * @param string $returnIndex + * @param string|null $deprecated * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( @@ -149,7 +160,8 @@ public function __construct( $removeBackend, $successRegex = null, $returnRegex = null, - $returnIndex = null + $returnIndex = null, + $deprecated = null ) { $this->name = $name; $this->operation = $operation; @@ -164,6 +176,7 @@ public function __construct( $this->returnRegex = $returnRegex; $this->returnIndex = $returnIndex; $this->removeBackend = $removeBackend; + $this->deprecated = $deprecated; $this->apiUrl = null; if (!empty($contentType)) { @@ -176,6 +189,16 @@ public function __construct( $this->headers[] = self::HTTP_CONTENT_TYPE_HEADER . ': ' . $this->contentType; } + /** + * Getter for the deprecated attr of the section + * + * @return string + */ + public function getDeprecated() + { + return $this->deprecated; + } + /** * Getter for data's data type * @@ -320,4 +343,20 @@ public function addQueryParams() $this->apiUrl = $this->apiUrl . $paramName . "=" . $paramValue; } } + + /** + * Function to log a referenced deprecated operation at runtime. + * + * @return void + */ + public function logDeprecated() + { + if ($this->deprecated !== null) { + LoggingUtil::getInstance()->getLogger(self::class)->deprecation( + $message = "The operation {$this->name} is deprecated.", + ["operationType" => $this->operation, "deprecatedMessage" => $this->deprecated], + true + ); + } + } } diff --git a/src/Magento/FunctionalTestingFramework/DataGenerator/Persist/Curl/AbstractExecutor.php b/src/Magento/FunctionalTestingFramework/DataGenerator/Persist/Curl/AbstractExecutor.php deleted file mode 100644 index b6c3f29ec..000000000 --- a/src/Magento/FunctionalTestingFramework/DataGenerator/Persist/Curl/AbstractExecutor.php +++ /dev/null @@ -1,31 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ - -namespace Magento\FunctionalTestingFramework\DataGenerator\Persist\Curl; - -use Magento\FunctionalTestingFramework\Util\Protocol\CurlInterface; - -/** - * Abstract Curl executor. - */ -abstract class AbstractExecutor implements CurlInterface -{ - /** - * Returns Magento base URL. Used as a fallback for other services (eg. WebApi, Backend) - * - * @var string - */ - protected static $baseUrl = null; - - /** - * Returns base URL for Magento instance - * @return string - */ - public function getBaseUrl(): string - { - return getenv('MAGENTO_BASE_URL'); - } -} diff --git a/src/Magento/FunctionalTestingFramework/DataGenerator/Persist/CurlHandler.php b/src/Magento/FunctionalTestingFramework/DataGenerator/Persist/CurlHandler.php index 691ce3606..89055b83f 100644 --- a/src/Magento/FunctionalTestingFramework/DataGenerator/Persist/CurlHandler.php +++ b/src/Magento/FunctionalTestingFramework/DataGenerator/Persist/CurlHandler.php @@ -6,15 +6,15 @@ namespace Magento\FunctionalTestingFramework\DataGenerator\Persist; use Magento\FunctionalTestingFramework\Allure\AllureHelper; -use Magento\FunctionalTestingFramework\DataGenerator\Persist\Curl\AdminExecutor; -use Magento\FunctionalTestingFramework\DataGenerator\Persist\Curl\FrontendExecutor; -use Magento\FunctionalTestingFramework\DataGenerator\Persist\Curl\WebapiExecutor; -use Magento\FunctionalTestingFramework\DataGenerator\Persist\Curl\WebapiNoAuthExecutor; +use Magento\FunctionalTestingFramework\DataTransport\AdminFormExecutor; +use Magento\FunctionalTestingFramework\DataTransport\FrontendFormExecutor; +use Magento\FunctionalTestingFramework\DataTransport\WebApiExecutor; +use Magento\FunctionalTestingFramework\DataTransport\WebApiNoAuthExecutor; use Magento\FunctionalTestingFramework\DataGenerator\Handlers\OperationDefinitionObjectHandler; use Magento\FunctionalTestingFramework\DataGenerator\Objects\EntityDataObject; use Magento\FunctionalTestingFramework\DataGenerator\Objects\OperationDefinitionObject; use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; -use Magento\FunctionalTestingFramework\Util\Protocol\CurlInterface; +use Magento\FunctionalTestingFramework\DataTransport\Protocol\CurlInterface; /** * Class CurlHandler @@ -123,6 +123,9 @@ public function executeRequest($dependentEntities) $returnRegex = $this->operationDefinition->getReturnRegex(); $returnIndex = $this->operationDefinition->getReturnIndex(); $method = $this->operationDefinition->getApiMethod(); + $this->operationDefinition->logDeprecated(); + AllureHelper::addAttachmentToCurrentStep($apiUrl, 'API Endpoint'); + AllureHelper::addAttachmentToCurrentStep(json_encode($headers, JSON_PRETTY_PRINT), 'Request Headers'); $operationDataResolver = new OperationDataArrayResolver($dependentEntities); $this->requestData = $operationDataResolver->resolveOperationDataArray( @@ -132,19 +135,21 @@ public function executeRequest($dependentEntities) false ); + AllureHelper::addAttachmentToCurrentStep(json_encode($this->requestData, JSON_PRETTY_PRINT), 'Request Body'); + if (($contentType === 'application/json') && ($authorization === 'adminOauth')) { $this->isJson = true; - $executor = new WebapiExecutor($this->storeCode); + $executor = new WebApiExecutor($this->storeCode); } elseif ($authorization === 'adminFormKey') { - $executor = new AdminExecutor($this->operationDefinition->removeUrlBackend()); + $executor = new AdminFormExecutor($this->operationDefinition->removeUrlBackend()); } elseif ($authorization === 'customerFormKey') { - $executor = new FrontendExecutor( + $executor = new FrontendFormExecutor( $this->requestData['customer_email'], $this->requestData['customer_password'] ); } elseif ($authorization === 'anonymous') { $this->isJson = true; - $executor = new WebapiNoAuthExecutor($this->storeCode); + $executor = new WebApiNoAuthExecutor($this->storeCode); } if (!$executor) { @@ -167,8 +172,7 @@ public function executeRequest($dependentEntities) $response = $executor->read($successRegex, $returnRegex, $returnIndex); $executor->close(); - AllureHelper::addAttachmentToLastStep(json_encode($this->requestData, JSON_PRETTY_PRINT), 'Request Body'); - AllureHelper::addAttachmentToLastStep( + AllureHelper::addAttachmentToCurrentStep( json_encode(json_decode($response, true), JSON_PRETTY_PRINT+JSON_UNESCAPED_UNICODE+JSON_UNESCAPED_SLASHES), 'Response Data' ); @@ -227,7 +231,7 @@ private function resolveUrlReference($urlIn, $entityObjects) foreach ($entityObjects as $entityObject) { $param = null; - if ($paramEntityParent === "" || $entityObject->getType() == $paramEntityParent) { + if ($paramEntityParent === "" || $entityObject->getType() === $paramEntityParent) { $param = $entityObject->getDataByName( $dataItem, EntityDataObject::CEST_UNIQUE_VALUE diff --git a/src/Magento/FunctionalTestingFramework/DataGenerator/Persist/DataPersistenceHandler.php b/src/Magento/FunctionalTestingFramework/DataGenerator/Persist/DataPersistenceHandler.php index dcb9d158a..fe4a10186 100644 --- a/src/Magento/FunctionalTestingFramework/DataGenerator/Persist/DataPersistenceHandler.php +++ b/src/Magento/FunctionalTestingFramework/DataGenerator/Persist/DataPersistenceHandler.php @@ -9,6 +9,7 @@ use Magento\FunctionalTestingFramework\DataGenerator\Objects\EntityDataObject; use Magento\FunctionalTestingFramework\DataGenerator\Handlers\DataObjectHandler; use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; +use Magento\FunctionalTestingFramework\ObjectManagerFactory; /** * Class DataPersistenceHandler @@ -59,7 +60,10 @@ public function __construct($entityObject, $dependentObjects = [], $customFields array_merge($entityObject->getAllData(), $customFields), $entityObject->getLinkedEntities(), $this->stripCustomFieldsFromUniquenessData($entityObject->getUniquenessData(), $customFields), - $entityObject->getVarReferences() + $entityObject->getVarReferences(), + $entityObject->getParentName(), + $entityObject->getFilename(), + $entityObject->getDeprecated() ); } else { $this->entityObject = clone $entityObject; @@ -83,7 +87,10 @@ public function createEntity($storeCode = null) if (!empty($storeCode)) { $this->storeCode = $storeCode; } - $curlHandler = new CurlHandler('create', $this->entityObject, $this->storeCode); + $curlHandler = ObjectManagerFactory::getObjectManager()->create( + CurlHandler::class, + ['operation' => 'create', 'entityObject' => $this->entityObject, 'storeCode' => $this->storeCode] + ); $result = $curlHandler->executeRequest($this->dependentObjects); $this->setCreatedObject( $result, @@ -108,7 +115,10 @@ public function updateEntity($updateDataName, $updateDependentObjects = []) $this->dependentObjects[] = $dependentObject->getCreatedObject(); } $updateEntityObject = DataObjectHandler::getInstance()->getObject($updateDataName); - $curlHandler = new CurlHandler('update', $updateEntityObject, $this->storeCode); + $curlHandler = ObjectManagerFactory::getObjectManager()->create( + CurlHandler::class, + ['operation' => 'update', 'entityObject' => $updateEntityObject, 'storeCode' => $this->storeCode] + ); $result = $curlHandler->executeRequest(array_merge($this->dependentObjects, [$this->createdObject])); $this->setCreatedObject( $result, @@ -131,7 +141,10 @@ public function getEntity($index = null, $storeCode = null) if (!empty($storeCode)) { $this->storeCode = $storeCode; } - $curlHandler = new CurlHandler('get', $this->entityObject, $this->storeCode); + $curlHandler = ObjectManagerFactory::getObjectManager()->create( + CurlHandler::class, + ['operation' => 'get', 'entityObject' => $this->entityObject, 'storeCode' => $this->storeCode] + ); $result = $curlHandler->executeRequest($this->dependentObjects); $this->setCreatedObject( $result, @@ -149,7 +162,11 @@ public function getEntity($index = null, $storeCode = null) */ public function deleteEntity() { - $curlHandler = new CurlHandler('delete', $this->createdObject, $this->storeCode); + $curlHandler = ObjectManagerFactory::getObjectManager()->create( + CurlHandler::class, + ['operation' => 'delete', 'entityObject' => $this->createdObject, 'storeCode' => $this->storeCode] + ); + $curlHandler->executeRequest($this->dependentObjects); } diff --git a/src/Magento/FunctionalTestingFramework/DataGenerator/Persist/OperationDataArrayResolver.php b/src/Magento/FunctionalTestingFramework/DataGenerator/Persist/OperationDataArrayResolver.php index 60b6e73c2..3266f5a24 100644 --- a/src/Magento/FunctionalTestingFramework/DataGenerator/Persist/OperationDataArrayResolver.php +++ b/src/Magento/FunctionalTestingFramework/DataGenerator/Persist/OperationDataArrayResolver.php @@ -78,7 +78,7 @@ public function resolveOperationDataArray($entityObject, $operationMetadata, $op self::incrementSequence($entityObject->getName()); foreach ($operationMetadata as $operationElement) { - if ($operationElement->getType() == OperationElementExtractor::OPERATION_OBJECT_OBJ_NAME) { + if ($operationElement->getType() === OperationElementExtractor::OPERATION_OBJECT_OBJ_NAME) { $entityObj = $this->resolveOperationObjectAndEntityData($entityObject, $operationElement->getValue()); if (null === $entityObj && $operationElement->isRequired()) { throw new \Exception(sprintf( @@ -189,13 +189,13 @@ private function resolvePrimitiveReference($entityObject, $operationKey, $operat EntityDataObject::CEST_UNIQUE_VALUE ); - if ($elementData == null && $entityObject->getVarReference($operationKey) != null) { + if ($elementData === null && $entityObject->getVarReference($operationKey) !== null) { list($type, $field) = explode( DataObjectHandler::_SEPARATOR, $entityObject->getVarReference($operationKey) ); - if ($operationElementType == OperationDefinitionObjectHandler::ENTITY_OPERATION_ARRAY) { + if ($operationElementType === OperationDefinitionObjectHandler::ENTITY_OPERATION_ARRAY) { $elementDatas = []; $entities = $this->getDependentEntitiesOfType($type); foreach ($entities as $entity) { @@ -224,7 +224,7 @@ private function getDependentEntitiesOfType($type) $entitiesOfType = []; foreach ($this->dependentEntities as $dependentEntity) { - if ($dependentEntity->getType() == $type) { + if ($dependentEntity->getType() === $type) { $entitiesOfType[] = $dependentEntity; } } @@ -244,11 +244,12 @@ private function getDependentEntitiesOfType($type) */ private function resolveOperationObjectAndEntityData($entityObject, $operationElementValue) { - if ($operationElementValue != $entityObject->getType()) { - // if we have a mismatch attempt to retrieve linked data and return just the first linkage + if ($operationElementValue !== $entityObject->getType()) { + // if we have a mismatch attempt to retrieve linked data and return just the last linkage + // this enables overwriting of required entity fields $linkName = $entityObject->getLinkedEntitiesOfType($operationElementValue); if (!empty($linkName)) { - $linkName = $linkName[0]; + $linkName = array_pop($linkName); return DataObjectHandler::getInstance()->getObject($linkName); } return null; @@ -274,7 +275,7 @@ private function resolveNonPrimitiveElement($entityName, $operationElement, $ope // in array case if (!is_array($operationElement->getValue()) && !empty($operationElement->getNestedOperationElement($operationElement->getValue())) - && $operationElement->getType() == OperationDefinitionObjectHandler::ENTITY_OPERATION_ARRAY + && $operationElement->getType() === OperationDefinitionObjectHandler::ENTITY_OPERATION_ARRAY ) { $operationSubArray = $this->resolveOperationDataArray( $linkedEntityObj, @@ -402,7 +403,7 @@ private function resolvePrimitiveReferenceElement($entityObject, $operationEleme // If data was defined at all, attempt to put it into operation data array // If data was not defined, and element is required, throw exception // If no data is defined, don't input defaults per primitive into operation data array - if ($elementData != null) { + if ($elementData !== null && $elementData !== '') { if (array_key_exists($operationElement->getKey(), $entityObject->getUniquenessData())) { $uniqueData = $entityObject->getUniquenessDataByName($operationElement->getKey()); if ($uniqueData === 'suffix') { @@ -448,7 +449,7 @@ private function resolveNonPrimitiveReferenceElement($entityObject, $operation, $entityNamesOfType = $entityObject->getLinkedEntitiesOfType($operationElementType); // If an element is required by metadata, but was not provided in the entity, throw an exception - if ($operationElement->isRequired() && $entityNamesOfType == null) { + if ($operationElement->isRequired() && $entityNamesOfType === null) { throw new \Exception(sprintf( self::EXCEPTION_REQUIRED_DATA, $operationElement->getType(), @@ -475,7 +476,7 @@ private function resolveNonPrimitiveReferenceElement($entityObject, $operation, } } - if ($operationElement->getType() == OperationDefinitionObjectHandler::ENTITY_OPERATION_ARRAY) { + if ($operationElement->getType() === OperationDefinitionObjectHandler::ENTITY_OPERATION_ARRAY) { $operationDataArray[$operationElement->getKey()][] = $operationDataSubArray; } else { $operationDataArray[$operationElement->getKey()] = $operationDataSubArray; diff --git a/src/Magento/FunctionalTestingFramework/DataGenerator/Util/DataExtensionUtil.php b/src/Magento/FunctionalTestingFramework/DataGenerator/Util/DataExtensionUtil.php index 2766b39a0..d803be9a2 100644 --- a/src/Magento/FunctionalTestingFramework/DataGenerator/Util/DataExtensionUtil.php +++ b/src/Magento/FunctionalTestingFramework/DataGenerator/Util/DataExtensionUtil.php @@ -32,7 +32,7 @@ public function extendEntity($entityObject) { // Check to see if the parent entity exists $parentEntity = DataObjectHandler::getInstance()->getObject($entityObject->getParentName()); - if ($parentEntity == null) { + if ($parentEntity === null) { throw new XmlException( "Parent Entity " . $entityObject->getParentName() . @@ -52,8 +52,8 @@ public function extendEntity($entityObject) PHP_EOL ); } - if (MftfApplicationConfig::getConfig()->verboseEnabled() && - MftfApplicationConfig::getConfig()->getPhase() !== MftfApplicationConfig::UNIT_TEST_PHASE) { + if (MftfApplicationConfig::getConfig()->verboseEnabled() + && MftfApplicationConfig::getConfig()->getPhase() !== MftfApplicationConfig::UNIT_TEST_PHASE) { print("Extending Data: " . $parentEntity->getName() . " => " . $entityObject->getName() . PHP_EOL); } @@ -62,7 +62,7 @@ public function extendEntity($entityObject) // Get all data for both parent and child and merge $referencedData = $parentEntity->getAllData(); - $newData = array_merge($referencedData, $entityObject->getAllData()); + $newData = array_extend($referencedData, $entityObject->getAllData()); // Get all linked references for both parent and child and merge $referencedLinks = $parentEntity->getLinkedEntities(); @@ -93,7 +93,8 @@ public function extendEntity($entityObject) $newUniqueReferences, $newVarReferences, $entityObject->getParentName(), - $entityObject->getFilename() + $entityObject->getFilename(), + $entityObject->getDeprecated() ); return $extendedEntity; } diff --git a/src/Magento/FunctionalTestingFramework/DataGenerator/Util/OperationElementExtractor.php b/src/Magento/FunctionalTestingFramework/DataGenerator/Util/OperationElementExtractor.php index c86e27972..ed23307c9 100644 --- a/src/Magento/FunctionalTestingFramework/DataGenerator/Util/OperationElementExtractor.php +++ b/src/Magento/FunctionalTestingFramework/DataGenerator/Util/OperationElementExtractor.php @@ -113,9 +113,9 @@ private function extractOperationArray(&$operationArrayData, $operationArrayArra { foreach ($operationArrayArray as $operationFieldType) { $operationElementValue = []; - if (isset($operationFieldType[OperationDefinitionObjectHandler::ENTITY_OPERATION_ARRAY_VALUE])) { - foreach ($operationFieldType[OperationDefinitionObjectHandler::ENTITY_OPERATION_ARRAY_VALUE] as - $operationFieldValue) { + $entityValueKey = OperationDefinitionObjectHandler::ENTITY_OPERATION_ARRAY_VALUE; + if (isset($operationFieldType[$entityValueKey])) { + foreach ($operationFieldType[$entityValueKey] as $operationFieldValue) { $operationElementValue[] = $operationFieldValue[OperationElementExtractor::OPERATION_OBJECT_ARRAY_VALUE] ?? null; } diff --git a/src/Magento/FunctionalTestingFramework/DataGenerator/Util/RuntimeDataReferenceResolver.php b/src/Magento/FunctionalTestingFramework/DataGenerator/Util/RuntimeDataReferenceResolver.php index a09afd01a..220d4ab7f 100644 --- a/src/Magento/FunctionalTestingFramework/DataGenerator/Util/RuntimeDataReferenceResolver.php +++ b/src/Magento/FunctionalTestingFramework/DataGenerator/Util/RuntimeDataReferenceResolver.php @@ -9,6 +9,7 @@ use Magento\FunctionalTestingFramework\DataGenerator\Handlers\CredentialStore; use Magento\FunctionalTestingFramework\DataGenerator\Handlers\DataObjectHandler; use Magento\FunctionalTestingFramework\Exceptions\TestReferenceException; +use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; use Magento\FunctionalTestingFramework\Test\Objects\ActionObject; /** @@ -23,7 +24,7 @@ class RuntimeDataReferenceResolver implements DataReferenceResolverInterface * @param string $originalDataEntity * @return array|false|string|null * @throws TestReferenceException - * @throws \Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException + * @throws TestFrameworkException */ public function getDataReference(string $data, string $originalDataEntity) { @@ -43,6 +44,9 @@ public function getDataReference(string $data, string $originalDataEntity) case ActionObject::__CREDS: $value = CredentialStore::getInstance()->getSecret($var); $result = CredentialStore::getInstance()->decryptSecretValue($value); + if ($result === false) { + throw new TestFrameworkException("\nFailed to decrypt value {$value}\n"); + } $result = str_replace($matches['reference'], $result, $data); break; default: diff --git a/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataOperation.xsd b/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataOperation.xsd index 8b59ccae0..c2682e737 100644 --- a/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataOperation.xsd +++ b/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataOperation.xsd @@ -22,6 +22,13 @@ </xs:choice> <xs:attribute type="xs:string" name="name"/> <xs:attribute type="xs:string" name="dataType" use="required"/> + <xs:attribute type="xs:string" name="deprecated"> + <xs:annotation> + <xs:documentation> + Message and flag which shows that entity is deprecated. + </xs:documentation> + </xs:annotation> + </xs:attribute> <xs:attribute type="operationEnum" name="type" use="required"/> <xs:attribute type="xs:string" name="url"/> <xs:attribute type="authEnum" name="auth"/> @@ -102,4 +109,4 @@ <xs:enumeration value="DELETE" /> </xs:restriction> </xs:simpleType> -</xs:schema> \ No newline at end of file +</xs:schema> diff --git a/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd b/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd index aec157309..923f7c48f 100644 --- a/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd +++ b/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd @@ -69,6 +69,14 @@ </xs:documentation> </xs:annotation> </xs:attribute> + <xs:attribute type="xs:string" name="deprecated"> + <xs:annotation> + <xs:documentation> + Message and flag which shows that entity is deprecated. + </xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attribute type="xs:string" name="type"> <xs:annotation> <xs:documentation> @@ -110,17 +118,32 @@ <xs:complexType name="arrayType"> <xs:sequence> - <xs:element name="item" type="xs:string" maxOccurs="unbounded" minOccurs="0"> - <xs:annotation> - <xs:documentation> - Individual piece of data to be passed in as part of the parrent array type. - </xs:documentation> - </xs:annotation> + <xs:element name="item" type="itemType" maxOccurs="unbounded" minOccurs="0"> + <xs:annotation> + <xs:documentation> + Individual piece of data to be passed in as part of the parrent array type. + </xs:documentation> + </xs:annotation> </xs:element> </xs:sequence> <xs:attribute type="xs:string" name="key" use="required"/> </xs:complexType> + + <xs:complexType name="itemType"> + <xs:simpleContent> + <xs:extension base="xs:string"> + <xs:attribute type="xs:string" name="name" use="optional"> + <xs:annotation> + <xs:documentation> + Key attribute of item pair. + </xs:documentation> + </xs:annotation> + </xs:attribute> + </xs:extension> + </xs:simpleContent> + </xs:complexType> + <xs:complexType name="requiredEntityType"> <xs:simpleContent> <xs:extension base="xs:string"> diff --git a/src/Magento/FunctionalTestingFramework/DataGenerator/Persist/Curl/AdminExecutor.php b/src/Magento/FunctionalTestingFramework/DataTransport/AdminFormExecutor.php similarity index 76% rename from src/Magento/FunctionalTestingFramework/DataGenerator/Persist/Curl/AdminExecutor.php rename to src/Magento/FunctionalTestingFramework/DataTransport/AdminFormExecutor.php index 4e11f5cec..5639ee8d7 100644 --- a/src/Magento/FunctionalTestingFramework/DataGenerator/Persist/Curl/AdminExecutor.php +++ b/src/Magento/FunctionalTestingFramework/DataTransport/AdminFormExecutor.php @@ -4,16 +4,19 @@ * See COPYING.txt for license details. */ -namespace Magento\FunctionalTestingFramework\DataGenerator\Persist\Curl; +namespace Magento\FunctionalTestingFramework\DataTransport; -use Magento\FunctionalTestingFramework\Util\Protocol\CurlInterface; -use Magento\FunctionalTestingFramework\Util\Protocol\CurlTransport; +use Magento\FunctionalTestingFramework\Util\MftfGlobals; +use Magento\FunctionalTestingFramework\DataTransport\Protocol\CurlInterface; +use Magento\FunctionalTestingFramework\DataTransport\Protocol\CurlTransport; use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; +use Magento\FunctionalTestingFramework\DataTransport\Auth\Tfa\OTP; +use Magento\FunctionalTestingFramework\DataTransport\Auth\Tfa; /** * Curl executor for requests to Admin. */ -class AdminExecutor extends AbstractExecutor implements CurlInterface +class AdminFormExecutor implements CurlInterface { /** * Curl transport protocol. @@ -56,16 +59,6 @@ public function __construct($removeBackend) $this->authorize(); } - /** - * Returns base URL for Magento backend instance - * @return string - */ - public function getBaseUrl(): string - { - $backendHost = getenv('MAGENTO_BACKEND_BASE_URL') ?: parent::getBaseUrl(); - return $backendHost . getenv('MAGENTO_BACKEND_NAME') . '/'; - } - /** * Authorize admin on backend. * @@ -75,11 +68,11 @@ public function getBaseUrl(): string private function authorize() { // Perform GET to backend url so form_key is set - $this->transport->write($this->getBaseUrl(), [], CurlInterface::GET); + $this->transport->write(MftfGlobals::getBackendBaseUrl(), [], CurlInterface::GET); $this->read(); // Authenticate admin user - $authUrl = $this->getBaseUrl() . 'admin/auth/login/'; + $authUrl = MftfGlobals::getBackendBaseUrl() . 'admin/auth/login/'; $data = [ 'login[username]' => getenv('MAGENTO_ADMIN_USERNAME'), 'login[password]' => getenv('MAGENTO_ADMIN_PASSWORD'), @@ -87,9 +80,25 @@ private function authorize() ]; $this->transport->write($authUrl, $data, CurlInterface::POST); $response = $this->read(); + if (strpos($response, 'login-form')) { throw new TestFrameworkException('Admin user authentication failed!'); } + + // Get OTP + if (Tfa::isEnabled()) { + $authUrl = MftfGlobals::getBackendBaseUrl() . Tfa::getProviderAdminFormEndpoint('google'); + $data = [ + 'tfa_code' => OTP::getOTP(), + 'form_key' => $this->formKey, + ]; + $this->transport->write($authUrl, $data, CurlInterface::POST); + $response = json_decode($this->read()); + + if (!$response->success) { + throw new TestFrameworkException('Admin user 2FA authentication failed!'); + } + } } /** @@ -118,10 +127,11 @@ private function setFormKey() public function write($url, $data = [], $method = CurlInterface::POST, $headers = []) { $url = ltrim($url, "/"); - $apiUrl = $this->getBaseUrl() . $url; + $apiUrl = MftfGlobals::getBackendBaseUrl() . $url; if ($this->removeBackend) { - $apiUrl = parent::getBaseUrl() . $url; + //TODO + //Cannot find usage. Do we need this? } if ($this->formKey) { diff --git a/src/Magento/FunctionalTestingFramework/DataTransport/Auth/Tfa.php b/src/Magento/FunctionalTestingFramework/DataTransport/Auth/Tfa.php new file mode 100644 index 000000000..04f1971b7 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/DataTransport/Auth/Tfa.php @@ -0,0 +1,108 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\FunctionalTestingFramework\DataTransport\Auth; + +use Magento\FunctionalTestingFramework\Util\MftfGlobals; +use Magento\FunctionalTestingFramework\DataTransport\Protocol\CurlInterface; +use Magento\FunctionalTestingFramework\DataTransport\Protocol\CurlTransport; +use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; + +/** + * Class Tfa (i.e. 2FA) + */ +class Tfa +{ + const WEB_API_AUTH_GOOGLE = 'V1/tfa/provider/google/authenticate'; + const ADMIN_FORM_AUTH_GOOGLE = 'tfa/google/authpost/?isAjax=true'; + const TFA_SCHEMA = 'schema?services=twoFactorAuthAdminTokenServiceV1'; + + /** + * If 2FA is enabled + * + * @var boolean|null + */ + private static $tfaEnabled = null; + + /** Rest request headers + * + * @var string[] + */ + private static $headers = [ + 'Accept: application/json', + 'Content-Type: application/json', + ]; + + /** + * 2FA provider web API authentication endpoints + * + * @var string[] + */ + private static $providerWebApiAuthEndpoints = [ + 'google' => self::WEB_API_AUTH_GOOGLE, + ]; + + /** + * 2FA provider admin form authentication endpoints + * + * @var string[] + */ + private static $providerAdminFormAuthEndpoints = [ + 'google' => self::ADMIN_FORM_AUTH_GOOGLE, + ]; + + /** + * Check if 2FA is enabled for Magento instance under test + * + * @return boolean + * @throws TestFrameworkException + */ + public static function isEnabled() + { + if (self::$tfaEnabled !== null) { + return self::$tfaEnabled; + } + + $schemaUrl = MftfGlobals::getWebApiBaseUrl() . self::TFA_SCHEMA; + $transport = new CurlTransport(); + try { + $transport->write($schemaUrl, [], CurlInterface::GET, self::$headers); + $response = $transport->read(); + $transport->close(); + $schema = json_decode($response, true); + if (isset($schema['definitions'], $schema['paths'])) { + return true; + } + } catch (TestFrameworkException $e) { + $transport->close(); + } + return false; + } + + /** + * Return provider's 2FA web API authentication endpoint + * + * @param string $name + * @return string|null + */ + public static function getProviderWebApiAuthEndpoint($name) + { + // Currently only support Google Authenticator + return self::$providerWebApiAuthEndpoints[$name] ?? null; + } + + /** + * Return 2FA provider's admin form authentication endpoint + * + * @param string $name + * @return string|null + */ + public static function getProviderAdminFormEndpoint($name) + { + // Currently only support Google Authenticator + return self::$providerAdminFormAuthEndpoints[$name] ?? null; + } +} diff --git a/src/Magento/FunctionalTestingFramework/DataTransport/Auth/Tfa/OTP.php b/src/Magento/FunctionalTestingFramework/DataTransport/Auth/Tfa/OTP.php new file mode 100644 index 000000000..32c1f6b81 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/DataTransport/Auth/Tfa/OTP.php @@ -0,0 +1,66 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\FunctionalTestingFramework\DataTransport\Auth\Tfa; + +use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; +use OTPHP\TOTP; +use Magento\FunctionalTestingFramework\DataGenerator\Handlers\CredentialStore; + +/** + * Class OTP + */ +class OTP +{ + const OTP_SHARED_SECRET_PATH = 'magento/tfa/OTP_SHARED_SECRET'; + + /** + * TOTP object + * + * @var TOTP[] + */ + private static $totps = []; + + /** + * Return OTP for custom secret stored in `magento/tfa/OTP_SHARED_SECRET` + * + * @param string|null $path + * @return string + * @throws TestFrameworkException + */ + public static function getOTP($path = null) + { + if ($path === null) { + $path = self::OTP_SHARED_SECRET_PATH; + } + return self::create($path)->now(); + } + + /** + * Create TOTP object + * + * @param string $path + * @return TOTP + * @throws TestFrameworkException + */ + private static function create($path) + { + if (!isset(self::$totps[$path])) { + try { + // Get shared secret from Credential storage + $encryptedSecret = CredentialStore::getInstance()->getSecret($path); + $secret = CredentialStore::getInstance()->decryptSecretValue($encryptedSecret); + } catch (TestFrameworkException $e) { + throw new TestFrameworkException('Unable to get OTP' . PHP_EOL . $e->getMessage()); + } + + self::$totps[$path] = TOTP::create($secret); + self::$totps[$path]->setIssuer('MFTF'); + self::$totps[$path]->setLabel('MFTF Testing'); + } + return self::$totps[$path]; + } +} diff --git a/src/Magento/FunctionalTestingFramework/DataTransport/Auth/WebApiAuth.php b/src/Magento/FunctionalTestingFramework/DataTransport/Auth/WebApiAuth.php new file mode 100644 index 000000000..bc4e52571 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/DataTransport/Auth/WebApiAuth.php @@ -0,0 +1,145 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\FunctionalTestingFramework\DataTransport\Auth; + +use Magento\FunctionalTestingFramework\Exceptions\FastFailException; +use Magento\FunctionalTestingFramework\Util\MftfGlobals; +use Magento\FunctionalTestingFramework\DataTransport\Protocol\CurlInterface; +use Magento\FunctionalTestingFramework\DataTransport\Protocol\CurlTransport; +use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; +use Magento\FunctionalTestingFramework\DataTransport\Auth\Tfa\OTP; + +/** + * Class WebApiAuth + */ +class WebApiAuth +{ + const PATH_ADMIN_AUTH = 'V1/integration/admin/token'; + + /** Rest request headers + * + * @var string[] + */ + private static $headers = [ + 'Accept: application/json', + 'Content-Type: application/json', + ]; + + /** + * Tokens for admin users + * + * @var string[] + */ + private static $adminAuthTokens = []; + + /** + * Timestamps of when admin user tokens were created. They need to be refreshed every ~4 hours + * + * @var int[] + */ + private static $adminAuthTokenTimestamps = []; + + /** + * Return the API token for an admin user + * Use MAGENTO_ADMIN_USERNAME and MAGENTO_ADMIN_PASSWORD when $username and/or $password is/are omitted + * + * @param string $username + * @param string $password + * @return string + * @throws FastFailException + * + * @SuppressWarnings(PHPMD.CyclomaticComplexity) + */ + public static function getAdminToken($username = null, $password = null) + { + $login = $username ?? getenv('MAGENTO_ADMIN_USERNAME'); + $password = $password ?? getenv('MAGENTO_ADMIN_PASSWORD'); + if (!$login || !$password) { + $message = 'Cannot retrieve API token without credentials. Please fill out .env.'; + $context = [ + 'MAGENTO_BASE_URL' => getenv('MAGENTO_BASE_URL'), + 'MAGENTO_BACKEND_BASE_URL' => getenv('MAGENTO_BACKEND_BASE_URL'), + 'MAGENTO_ADMIN_USERNAME' => getenv('MAGENTO_ADMIN_USERNAME'), + 'MAGENTO_ADMIN_PASSWORD' => getenv('MAGENTO_ADMIN_PASSWORD'), + ]; + throw new FastFailException($message, $context); + } + + if (self::hasExistingToken($login)) { + return self::$adminAuthTokens[$login]; + } + + try { + $authUrl = MftfGlobals::getWebApiBaseUrl() . self::PATH_ADMIN_AUTH; + + $data = [ + 'username' => $login, + 'password' => $password + ]; + + if (Tfa::isEnabled()) { + $authUrl = MftfGlobals::getWebApiBaseUrl() . Tfa::getProviderWebApiAuthEndpoint('google'); + $data['otp'] = OTP::getOTP(); + } + + $transport = new CurlTransport(); + $transport->write( + $authUrl, + json_encode($data, JSON_PRETTY_PRINT), + CurlInterface::POST, + self::$headers + ); + } catch (TestFrameworkException $e) { + $message = "Cannot retrieve API token with credentials. Please check configurations in .env.\n"; + throw new FastFailException($message . $e->getMessage(), $e->getContext()); + } + + try { + $response = $transport->read(); + $transport->close(); + $token = json_decode($response); + if ($token !== null) { + self::$adminAuthTokens[$login] = $token; + self::$adminAuthTokenTimestamps[$login] = time(); + return $token; + } + $errMessage = "Invalid response: {$response}"; + } catch (TestFrameworkException $e) { + $transport->close(); + $errMessage = $e->getMessage(); + } + + $message = 'Cannot retrieve API token with credentials.'; + try { + // No exception will ever throw from here + $message .= Tfa::isEnabled() ? ' and 2FA settings:' : ':' . PHP_EOL; + } catch (TestFrameworkException $e) { + } + $message .= $errMessage; + $context = ['url' => $authUrl]; + throw new FastFailException($message, $context); + } + + /** + * Is there an existing WebAPI admin token for this login? + * + * @param string $login + * @return boolean + */ + private static function hasExistingToken(string $login) + { + if (!isset(self::$adminAuthTokens[$login])) { + return false; + } + + $tokenLifetime = getenv('MAGENTO_ADMIN_WEBAPI_TOKEN_LIFETIME'); + + $isTokenExpired = $tokenLifetime && time() - self::$adminAuthTokenTimestamps[$login] > $tokenLifetime; + + return !$isTokenExpired; + } +} diff --git a/src/Magento/FunctionalTestingFramework/DataGenerator/Persist/Curl/FrontendExecutor.php b/src/Magento/FunctionalTestingFramework/DataTransport/FrontendFormExecutor.php similarity index 90% rename from src/Magento/FunctionalTestingFramework/DataGenerator/Persist/Curl/FrontendExecutor.php rename to src/Magento/FunctionalTestingFramework/DataTransport/FrontendFormExecutor.php index 799cd6d5c..0cf69ec94 100644 --- a/src/Magento/FunctionalTestingFramework/DataGenerator/Persist/Curl/FrontendExecutor.php +++ b/src/Magento/FunctionalTestingFramework/DataTransport/FrontendFormExecutor.php @@ -4,16 +4,17 @@ * See COPYING.txt for license details. */ -namespace Magento\FunctionalTestingFramework\DataGenerator\Persist\Curl; +namespace Magento\FunctionalTestingFramework\DataTransport; -use Magento\FunctionalTestingFramework\Util\Protocol\CurlInterface; -use Magento\FunctionalTestingFramework\Util\Protocol\CurlTransport; +use Magento\FunctionalTestingFramework\Util\MftfGlobals; +use Magento\FunctionalTestingFramework\DataTransport\Protocol\CurlInterface; +use Magento\FunctionalTestingFramework\DataTransport\Protocol\CurlTransport; use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; /** * Curl executor for requests to Frontend. */ -class FrontendExecutor extends AbstractExecutor implements CurlInterface +class FrontendFormExecutor implements CurlInterface { /** * Curl transport protocol. @@ -58,7 +59,7 @@ class FrontendExecutor extends AbstractExecutor implements CurlInterface private $customerPassword; /** - * FrontendExecutor constructor. + * FrontendFormExecutor constructor. * * @param string $customerEmail * @param string $customerPassWord @@ -81,11 +82,11 @@ public function __construct($customerEmail, $customerPassWord) */ private function authorize() { - $url = $this->getBaseUrl() . 'customer/account/login/'; + $url = MftfGlobals::getBaseUrl() . 'customer/account/login/'; $this->transport->write($url, [], CurlInterface::GET); $this->read(); - $url = $this->getBaseUrl() . 'customer/account/loginPost/'; + $url = MftfGlobals::getBaseUrl() . 'customer/account/loginPost/'; $data = [ 'login[username]' => $this->customerEmail, 'login[password]' => $this->customerPassword, @@ -143,7 +144,7 @@ public function write($url, $data = [], $method = CurlInterface::POST, $headers if (isset($data['customer_password'])) { unset($data['customer_password']); } - $apiUrl = $this->getBaseUrl() . $url; + $apiUrl = MftfGlobals::getBaseUrl() . $url; if ($this->formKey) { $data['form_key'] = $this->formKey; } else { diff --git a/src/Magento/FunctionalTestingFramework/Util/Protocol/CurlInterface.php b/src/Magento/FunctionalTestingFramework/DataTransport/Protocol/CurlInterface.php similarity index 94% rename from src/Magento/FunctionalTestingFramework/Util/Protocol/CurlInterface.php rename to src/Magento/FunctionalTestingFramework/DataTransport/Protocol/CurlInterface.php index a2d6bc344..d15dbb18b 100644 --- a/src/Magento/FunctionalTestingFramework/Util/Protocol/CurlInterface.php +++ b/src/Magento/FunctionalTestingFramework/DataTransport/Protocol/CurlInterface.php @@ -4,7 +4,7 @@ * See COPYING.txt for license details. */ -namespace Magento\FunctionalTestingFramework\Util\Protocol; +namespace Magento\FunctionalTestingFramework\DataTransport\Protocol; /** * Curl protocol interface. diff --git a/src/Magento/FunctionalTestingFramework/Util/Protocol/CurlTransport.php b/src/Magento/FunctionalTestingFramework/DataTransport/Protocol/CurlTransport.php similarity index 90% rename from src/Magento/FunctionalTestingFramework/Util/Protocol/CurlTransport.php rename to src/Magento/FunctionalTestingFramework/DataTransport/Protocol/CurlTransport.php index 16c716e22..6d064feb5 100644 --- a/src/Magento/FunctionalTestingFramework/Util/Protocol/CurlTransport.php +++ b/src/Magento/FunctionalTestingFramework/DataTransport/Protocol/CurlTransport.php @@ -4,7 +4,7 @@ * See COPYING.txt for license details. */ -namespace Magento\FunctionalTestingFramework\Util\Protocol; +namespace Magento\FunctionalTestingFramework\DataTransport\Protocol; use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; @@ -132,18 +132,18 @@ public function write($url, $body = [], $method = CurlInterface::POST, $headers CURLOPT_RETURNTRANSFER => true, CURLOPT_FOLLOWLOCATION => true, CURLOPT_COOKIEFILE => '', - CURLOPT_HTTPHEADER => $headers, + CURLOPT_HTTPHEADER => is_object($headers) ? (array) $headers : $headers, CURLOPT_SSL_VERIFYPEER => false, CURLOPT_SSL_VERIFYHOST => false, ]; switch ($method) { case CurlInterface::POST: $options[CURLOPT_POST] = true; - $options[CURLOPT_POSTFIELDS] = $body; + $options[CURLOPT_POSTFIELDS] = is_object($body) ? (array) $body : $body; break; case CurlInterface::PUT: $options[CURLOPT_CUSTOMREQUEST] = self::PUT; - $options[CURLOPT_POSTFIELDS] = $body; + $options[CURLOPT_POSTFIELDS] = is_object($body) ? (array) $body : $body; break; case CurlInterface::DELETE: $options[CURLOPT_CUSTOMREQUEST] = self::DELETE; @@ -189,7 +189,10 @@ public function read($successRegex = null, $returnRegex = null, $returnIndex = n */ public function close() { - curl_close($this->getResource()); + if (version_compare(PHP_VERSION, '8.0') < 0) { + // this function no longer has an effect in PHP 8.0, but it's required in earlier versions + curl_close($this->getResource()); + } $this->resource = null; } @@ -271,7 +274,11 @@ public function multiRequest(array $urls, array $options = []) $result[$key] = curl_multi_getcontent($handle); curl_multi_remove_handle($multiHandle, $handle); } - curl_multi_close($multiHandle); + if (version_compare(PHP_VERSION, '8.0') < 0) { + // this function no longer has an effect in PHP 8.0, but it's required in earlier versions + curl_multi_close($multiHandle); + } + return $result; } diff --git a/src/Magento/FunctionalTestingFramework/DataGenerator/Persist/Curl/WebapiExecutor.php b/src/Magento/FunctionalTestingFramework/DataTransport/WebApiExecutor.php similarity index 50% rename from src/Magento/FunctionalTestingFramework/DataGenerator/Persist/Curl/WebapiExecutor.php rename to src/Magento/FunctionalTestingFramework/DataTransport/WebApiExecutor.php index 281eaa24d..3a4b627bd 100644 --- a/src/Magento/FunctionalTestingFramework/DataGenerator/Persist/Curl/WebapiExecutor.php +++ b/src/Magento/FunctionalTestingFramework/DataTransport/WebApiExecutor.php @@ -4,28 +4,31 @@ * See COPYING.txt for license details. */ -namespace Magento\FunctionalTestingFramework\DataGenerator\Persist\Curl; +namespace Magento\FunctionalTestingFramework\DataTransport; +use Magento\FunctionalTestingFramework\Exceptions\FastFailException; use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; -use Magento\FunctionalTestingFramework\Util\Protocol\CurlInterface; -use Magento\FunctionalTestingFramework\Util\Protocol\CurlTransport; +use Magento\FunctionalTestingFramework\Util\MftfGlobals; +use Magento\FunctionalTestingFramework\DataTransport\Protocol\CurlInterface; +use Magento\FunctionalTestingFramework\DataTransport\Protocol\CurlTransport; +use Magento\FunctionalTestingFramework\DataTransport\Auth\WebApiAuth; /** * Curl executor for Magento Web Api requests. */ -class WebapiExecutor extends AbstractExecutor implements CurlInterface +class WebApiExecutor implements CurlInterface { /** - * Curl transport protocol. + * Curl transport protocol * * @var CurlTransport */ private $transport; /** - * Api headers. + * Rest request headers * - * @var array + * @var string[] */ private $headers = [ 'Accept: application/json', @@ -33,85 +36,37 @@ class WebapiExecutor extends AbstractExecutor implements CurlInterface ]; /** - * Response data. - * - * @var string - */ - private $response; - - /** - * Admin authentication url. - */ - const ADMIN_AUTH_URL = '/V1/integration/admin/token'; - - /** - * Store code in api request. + * Store code in API request * * @var string */ private $storeCode; /** - * Admin user auth token. - * - * @var string - */ - private $authToken; - - /** - * WebapiExecutor Constructor. + * WebApiExecutor Constructor * * @param string $storeCode - * @throws TestFrameworkException + * @throws FastFailException */ public function __construct($storeCode = null) { $this->storeCode = $storeCode; - $this->authToken = null; $this->transport = new CurlTransport(); $this->authorize(); } /** - * Returns base URL for Magento Web API instance - * @return string - */ - public function getBaseUrl(): string - { - $baseUrl = parent::getBaseUrl(); - - $webapiHost = getenv('MAGENTO_RESTAPI_SERVER_HOST'); - $webapiPort = getenv("MAGENTO_RESTAPI_SERVER_PORT"); - $webapiProtocol = getenv("MAGENTO_RESTAPI_SERVER_PROTOCOL"); - - if ($webapiHost) { - $baseUrl = sprintf('%s://%s/', $webapiProtocol, $webapiHost); - } - - if ($webapiPort) { - $baseUrl = rtrim($baseUrl, '/') . ':' . $webapiPort . '/'; - } - - return $baseUrl; - } - - /** - * Acquire and store the authorization token needed for REST requests. + * Acquire and store the authorization token needed for REST requests * * @return void - * @throws TestFrameworkException + * @throws FastFailException */ protected function authorize() { - $authUrl = $this->getFormattedUrl(self::ADMIN_AUTH_URL); - $authCreds = [ - 'username' => getenv('MAGENTO_ADMIN_USERNAME'), - 'password' => getenv('MAGENTO_ADMIN_PASSWORD') - ]; - - $this->transport->write($authUrl, json_encode($authCreds), CurlInterface::POST, $this->headers); - $this->authToken = str_replace('"', "", $this->read()); - $this->headers = array_merge(['Authorization: Bearer ' . $this->authToken], $this->headers); + $this->headers = array_merge( + ['Authorization: Bearer ' . WebApiAuth::getAdminToken()], + $this->headers + ); } /** @@ -145,8 +100,7 @@ public function write($url, $data = [], $method = CurlInterface::POST, $headers */ public function read($successRegex = null, $returnRegex = null, $returnIndex = null) { - $this->response = $this->transport->read(); - return $this->response; + return $this->transport->read(); } /** @@ -172,28 +126,19 @@ public function close() } /** - * Builds and returns URL for request, appending storeCode if needed. + * Builds and returns URL for request, appending storeCode if needed + * * @param string $resource * @return string + * @throws TestFrameworkException */ - public function getFormattedUrl($resource) + protected function getFormattedUrl($resource) { - $urlResult = $this->getBaseUrl() . 'rest/'; - if ($this->storeCode != null) { - $urlResult .= $this->storeCode . "/"; + $urlResult = MftfGlobals::getWebApiBaseUrl(); + if ($this->storeCode !== null) { + $urlResult .= $this->storeCode . '/'; } - $urlResult .= trim($resource, "/"); + $urlResult .= trim($resource, '/'); return $urlResult; } - - /** - * Return admin auth token. - * - * @throws TestFrameworkException - * @return string - */ - public function getAuthToken() - { - return $this->authToken; - } } diff --git a/src/Magento/FunctionalTestingFramework/DataGenerator/Persist/Curl/WebapiNoAuthExecutor.php b/src/Magento/FunctionalTestingFramework/DataTransport/WebApiNoAuthExecutor.php similarity index 74% rename from src/Magento/FunctionalTestingFramework/DataGenerator/Persist/Curl/WebapiNoAuthExecutor.php rename to src/Magento/FunctionalTestingFramework/DataTransport/WebApiNoAuthExecutor.php index c54bc60b9..066c676f2 100644 --- a/src/Magento/FunctionalTestingFramework/DataGenerator/Persist/Curl/WebapiNoAuthExecutor.php +++ b/src/Magento/FunctionalTestingFramework/DataTransport/WebApiNoAuthExecutor.php @@ -4,12 +4,12 @@ * See COPYING.txt for license details. */ -namespace Magento\FunctionalTestingFramework\DataGenerator\Persist\Curl; +namespace Magento\FunctionalTestingFramework\DataTransport; /** * Curl executor for Magento Web Api requests that do not require authorization. */ -class WebapiNoAuthExecutor extends WebapiExecutor +class WebApiNoAuthExecutor extends WebApiExecutor { /** * No authorization is needed and just return. diff --git a/src/Magento/FunctionalTestingFramework/Exceptions/Collector/ExceptionCollector.php b/src/Magento/FunctionalTestingFramework/Exceptions/Collector/ExceptionCollector.php index c1e023ef4..f64b35446 100644 --- a/src/Magento/FunctionalTestingFramework/Exceptions/Collector/ExceptionCollector.php +++ b/src/Magento/FunctionalTestingFramework/Exceptions/Collector/ExceptionCollector.php @@ -43,6 +43,26 @@ public function throwException() throw new \Exception("\n" . $errorMsg); } + /** + * Return all errors + * + * @return array + */ + public function getErrors() + { + return $this->errors ?? []; + } + + /** + * Reset error to empty array + * + * @return void + */ + public function reset() + { + $this->errors = []; + } + /** * If there are multiple exceptions for a single file, the function flattens the array so they can be printed * as separate messages. diff --git a/src/Magento/FunctionalTestingFramework/Exceptions/FastFailException.php b/src/Magento/FunctionalTestingFramework/Exceptions/FastFailException.php new file mode 100644 index 000000000..70d84dff2 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Exceptions/FastFailException.php @@ -0,0 +1,54 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\FunctionalTestingFramework\Exceptions; + +use Magento\FunctionalTestingFramework\Util\Logger\LoggingUtil; + +/** + * Class FastFailException + * + * This exception type should not be caught and should allow fast fail of current execution + */ +class FastFailException extends \Exception +{ + /** + * Exception context + * + * @var array + */ + protected $context; + + /** + * FastFailException constructor + * + * @param string $message + * @param array $context + * + * @SuppressWarnings(PHPMD.UnusedLocalVariable) + */ + public function __construct($message, $context = []) + { + list($childClass, $callingClass) = debug_backtrace(false, 2); + LoggingUtil::getInstance()->getLogger($callingClass['class'])->error( + $message, + $context + ); + + $this->context = $context; + parent::__construct($message); + } + + /** + * Return exception context + * + * @return array + */ + public function getContext() + { + return $this->context; + } +} diff --git a/src/Magento/FunctionalTestingFramework/Exceptions/TestFrameworkException.php b/src/Magento/FunctionalTestingFramework/Exceptions/TestFrameworkException.php index 5e9e594c0..a82eddef0 100644 --- a/src/Magento/FunctionalTestingFramework/Exceptions/TestFrameworkException.php +++ b/src/Magento/FunctionalTestingFramework/Exceptions/TestFrameworkException.php @@ -13,6 +13,13 @@ */ class TestFrameworkException extends \Exception { + /** + * Exception context + * + * @var array + */ + protected $context; + /** * TestFrameworkException constructor. * @param string $message @@ -27,6 +34,17 @@ public function __construct($message, $context = []) $context ); + $this->context = $context; parent::__construct($message); } + + /** + * Return exception context + * + * @return array + */ + public function getContext() + { + return $this->context; + } } diff --git a/src/Magento/FunctionalTestingFramework/Extension/BrowserLogUtil.php b/src/Magento/FunctionalTestingFramework/Extension/BrowserLogUtil.php new file mode 100644 index 000000000..ca9d2be9e --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Extension/BrowserLogUtil.php @@ -0,0 +1,84 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\FunctionalTestingFramework\Extension; + +/** + * Class BrowserLogUtil + * @package Magento\FunctionalTestingFramework\Extension + */ +class BrowserLogUtil +{ + const LOG_TYPE_BROWSER = "browser"; + const ERROR_TYPE_JAVASCRIPT = "javascript"; + + /** + * Loops throw errors in log and logs them to allure. Uses Module to set the error itself + * + * @param array $log + * @param \Codeception\Module\WebDriver $module + * @param \Codeception\Event\StepEvent $stepEvent + * @return void + */ + public static function logErrors($log, $module, $stepEvent) + { + $jsErrors = self::getLogsOfType($log, self::ERROR_TYPE_JAVASCRIPT); + foreach ($jsErrors as $entry) { + self::logError(self::ERROR_TYPE_JAVASCRIPT, $stepEvent, $entry); + //Set javascript error in MagentoWebDriver internal array + $module->setJsError("ERROR({$entry["level"]}) - " . $entry["message"]); + } + } + + /** + * Loops through given log and returns entries of the given type. + * + * @param array $log + * @param string $type + * @return array + */ + public static function getLogsOfType($log, $type) + { + $errors = []; + foreach ($log as $entry) { + if (array_key_exists("source", $entry) && $entry["source"] === $type) { + $errors[] = $entry; + } + } + return $errors; + } + + /** + * Loops through given log and filters entries of the given type. + * + * @param array $log + * @param string $type + * @return array + */ + public static function filterLogsOfType($log, $type) + { + $errors = []; + foreach ($log as $entry) { + if (array_key_exists("source", $entry) && $entry["source"] !== $type) { + $errors[] = $entry; + } + } + return $errors; + } + + /** + * Logs errors to console/report. + * @param string $type + * @param \Codeception\Event\StepEvent $stepEvent + * @param array $entry + * @return void + */ + private static function logError($type, $stepEvent, $entry) + { + //TODO Add to overall log + $stepEvent->getTest()->getScenario()->comment("{$type} ERROR({$entry["level"]}) - " . $entry["message"]); + } +} diff --git a/src/Magento/FunctionalTestingFramework/Extension/ErrorLogger.php b/src/Magento/FunctionalTestingFramework/Extension/ErrorLogger.php deleted file mode 100644 index b0621df1b..000000000 --- a/src/Magento/FunctionalTestingFramework/Extension/ErrorLogger.php +++ /dev/null @@ -1,76 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ - -namespace Magento\FunctionalTestingFramework\Extension; - -/** - * Class ErrorLogger - * @package Magento\FunctionalTestingFramework\Extension - */ -class ErrorLogger -{ - /** - * Error Logger Instance - * @var ErrorLogger - */ - private static $errorLogger; - - /** - * Singleton method to return ErrorLogger. - * @return ErrorLogger - */ - public static function getInstance() - { - if (!self::$errorLogger) { - self::$errorLogger = new ErrorLogger(); - } - - return self::$errorLogger; - } - - /** - * ErrorLogger constructor. - */ - private function __construct() - { - // private constructor - } - - /** - * Loops through stepEvent for browser log entries - * - * @param \Magento\FunctionalTestingFramework\Module\MagentoWebDriver $module - * @param \Codeception\Event\StepEvent $stepEvent - * @return void - */ - public function logErrors($module, $stepEvent) - { - //Types available should be "server", "browser", "driver". Only care about browser at the moment. - if (in_array("browser", $module->webDriver->manage()->getAvailableLogTypes())) { - $browserLogEntries = $module->webDriver->manage()->getLog("browser"); - foreach ($browserLogEntries as $entry) { - if (array_key_exists("source", $entry) && $entry["source"] === "javascript") { - $this->logError("javascript", $stepEvent, $entry); - //Set javascript error in MagentoWebDriver internal array - $module->setJsError("ERROR({$entry["level"]}) - " . $entry["message"]); - } - } - } - } - - /** - * Logs errors to console/report. - * @param string $type - * @param \Codeception\Event\StepEvent $stepEvent - * @param array $entry - * @return void - */ - private function logError($type, $stepEvent, $entry) - { - //TODO Add to overall log - $stepEvent->getTest()->getScenario()->comment("{$type} ERROR({$entry["level"]}) - " . $entry["message"]); - } -} diff --git a/src/Magento/FunctionalTestingFramework/Extension/PageReadinessExtension.php b/src/Magento/FunctionalTestingFramework/Extension/PageReadinessExtension.php deleted file mode 100644 index 2dfcbec1a..000000000 --- a/src/Magento/FunctionalTestingFramework/Extension/PageReadinessExtension.php +++ /dev/null @@ -1,240 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ - -namespace Magento\FunctionalTestingFramework\Extension; - -use Codeception\Event\StepEvent; -use Codeception\Event\TestEvent; -use Codeception\Step; -use Facebook\WebDriver\Exception\UnexpectedAlertOpenException; -use Magento\FunctionalTestingFramework\Extension\ReadinessMetrics\AbstractMetricCheck; -use Facebook\WebDriver\Exception\TimeOutException; -use Magento\FunctionalTestingFramework\Util\Logger\LoggingUtil; -use Magento\FunctionalTestingFramework\Config\MftfApplicationConfig; -use Monolog\Logger; - -/** - * Class PageReadinessExtension - */ -class PageReadinessExtension extends BaseExtension -{ - /** - * List of action types that should bypass metric checks - * shouldSkipCheck() also checks for the 'Comment' step type, which doesn't follow the $step->getAction() pattern - * - * @var array - */ - private $ignoredActions = [ - 'saveScreenshot', - 'skipReadinessCheck', - 'wait' - ]; - - /** - * @var Logger - */ - private $logger; - - /** - * Logger verbosity - * - * @var boolean - */ - private $verbose; - - /** - * Array of readiness metrics, initialized during beforeTest event - * - * @var AbstractMetricCheck[] - */ - private $readinessMetrics; - - /** - * The name of the active test - * - * @var string - */ - private $testName; - - /** - * Initialize local vars - * - * @return void - * @throws \Exception - */ - public function _initialize() - { - $this->logger = LoggingUtil::getInstance()->getLogger(get_class($this)); - $this->verbose = MftfApplicationConfig::getConfig()->verboseEnabled(); - parent::_initialize(); - } - - /** - * Initialize the readiness metrics for the test - * - * @param TestEvent $e - * @return void - * @throws \Exception - */ - public function beforeTest(TestEvent $e) - { - parent::beforeTest($e); - if (isset($this->config['resetFailureThreshold'])) { - $failThreshold = intval($this->config['resetFailureThreshold']); - } else { - $failThreshold = 3; - } - - $this->testName = $e->getTest()->getMetadata()->getName(); - - $this->getDriver()->_setConfig(['skipReadiness' => false]); - - $metrics = []; - foreach ($this->config['readinessMetrics'] as $metricClass) { - $metrics[] = new $metricClass($this, $failThreshold); - } - - $this->readinessMetrics = $metrics; - } - - /** - * Waits for busy page flags to disappear before executing a step - * - * @param StepEvent $e - * @return void - * @throws \Exception - */ - public function beforeStep(StepEvent $e) - { - $step = $e->getStep(); - $manualSkip = $this->getDriver()->_getConfig()['skipReadiness']; - if ($this->shouldSkipCheck($step, $manualSkip)) { - return; - } - - $this->resetMetricTracker($step); - - $metrics = $this->readinessMetrics; - - $this->waitForReadiness($metrics); - - /** @var AbstractMetricCheck $metric */ - foreach ($metrics as $metric) { - $metric->finalizeForStep($step); - } - } - - /** - * Check if page has changed, if so reset metric tracking - * - * @param Step $step - * @return void - */ - private function resetMetricTracker($step) - { - if ($this->pageChanged($step)) { - $this->logDebug( - 'Page URI changed; resetting readiness metric failure tracking', - [ - 'step' => $step->__toString(), - 'newUri' => $this->getUri() - ] - ); - /** @var AbstractMetricCheck $metric */ - foreach ($this->readinessMetrics as $metric) { - $metric->resetTracker(); - } - } - } - - /** - * Wait for page readiness. - * @param array $metrics - * @return void - * @throws \Codeception\Exception\ModuleRequireException - * @throws \Facebook\WebDriver\Exception\NoSuchElementException - */ - private function waitForReadiness($metrics) - { - // todo: Implement step parameter to override global timeout configuration - if (isset($this->config['timeout'])) { - $timeout = intval($this->config['timeout']); - } else { - $timeout = $this->getDriver()->_getConfig()['pageload_timeout']; - } - - try { - $this->getDriver()->webDriver->wait($timeout)->until( - function () use ($metrics) { - $passing = true; - - /** @var AbstractMetricCheck $metric */ - foreach ($metrics as $metric) { - try { - if (!$metric->runCheck()) { - $passing = false; - } - } catch (UnexpectedAlertOpenException $exception) { - } - } - return $passing; - } - ); - } catch (TimeoutException $exception) { - } - } - - /** - * Gets the name of the active test - * - * @return string - */ - public function getTestName() - { - return $this->testName; - } - - /** - * Should the given step bypass the readiness checks - * todo: Implement step parameter to bypass specific metrics (or all) instead of basing on action type - * - * @param Step $step - * @param boolean $manualSkip - * @return boolean - */ - private function shouldSkipCheck($step, $manualSkip) - { - if ($step instanceof Step\Comment || in_array($step->getAction(), $this->ignoredActions) || $manualSkip) { - return true; - } - return false; - } - - /** - * If verbose, log the given message to logger->debug including test context information - * - * @param string $message - * @param array $context - * @return void - * @SuppressWarnings(PHPMD) - */ - private function logDebug($message, $context = []) - { - if ($this->verbose) { - $logContext = [ - 'test' => $this->testName, - 'uri' => $this->getUri() - ]; - foreach ($this->readinessMetrics as $metric) { - $logContext[$metric->getName()] = $metric->getStoredValue(); - $logContext[$metric->getName() . '.failCount'] = $metric->getFailureCount(); - } - $context = array_merge($logContext, $context); - //TODO REMOVE THIS LINE, UNCOMMENT LOGGER - //$this->logger->info($message, $context); - } - } -} diff --git a/src/Magento/FunctionalTestingFramework/Extension/ReadinessMetrics/AbstractMetricCheck.php b/src/Magento/FunctionalTestingFramework/Extension/ReadinessMetrics/AbstractMetricCheck.php deleted file mode 100644 index 8b611283d..000000000 --- a/src/Magento/FunctionalTestingFramework/Extension/ReadinessMetrics/AbstractMetricCheck.php +++ /dev/null @@ -1,364 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ - -namespace Magento\FunctionalTestingFramework\Extension\ReadinessMetrics; - -use Codeception\Exception\ModuleRequireException; -use Codeception\Module\WebDriver; -use Codeception\Step; -use Facebook\WebDriver\Exception\UnexpectedAlertOpenException; -use Magento\FunctionalTestingFramework\Extension\PageReadinessExtension; -use Magento\FunctionalTestingFramework\Util\Logger\LoggingUtil; -use Magento\FunctionalTestingFramework\Config\MftfApplicationConfig; -use Monolog\Logger; - -/** - * Class AbstractMetricCheck - */ -abstract class AbstractMetricCheck -{ - /** - * Extension being used to verify this metric passes before test metrics - * - * @var PageReadinessExtension - */ - protected $extension; - - /** - * Current state of the value the metric tracks - * - * @var mixed; - */ - protected $currentValue; - - /** - * Most recent saved state of the value the metric tracks - * Updated when the metric passes or is finalized - * - * @var mixed; - */ - protected $storedValue; - - /** - * Current count of sequential identical failures - * - * @var integer; - */ - protected $failCount; - - /** - * Number of sequential identical failures before force-resetting the metric - * - * @var integer - */ - protected $resetFailureThreshold; - - /** - * @var Logger - */ - protected $logger; - - /** - * @var boolean - */ - protected $verbose; - - /** - * Constructor, called from the beforeTest event - * - * @param PageReadinessExtension $extension - * @param integer $resetFailureThreshold - * @throws \Exception - */ - public function __construct($extension, $resetFailureThreshold) - { - $this->extension = $extension; - $this->logger = LoggingUtil::getInstance()->getLogger(get_class($this)); - $this->verbose = MftfApplicationConfig::getConfig()->verboseEnabled(); - - // If the clearFailureOnPage() method is overridden, use the configured failure threshold - // If not, the default clearFailureOnPage() method does nothing so don't worry about resetting failures - $reflector = new \ReflectionMethod($this, 'clearFailureOnPage'); - if ($reflector->getDeclaringClass()->getName() === get_class($this)) { - $this->resetFailureThreshold = $resetFailureThreshold; - } else { - $this->resetFailureThreshold = -1; - } - - $this->resetTracker(); - } - - /** - * Does the given value pass the readiness metric - * - * @param mixed $value - * @return boolean - */ - abstract protected function doesMetricPass($value); - - /** - * Retrieve the active value for the metric to check from the page - * - * @return mixed - * @throws UnexpectedAlertOpenException - */ - abstract protected function fetchValueFromPage(); - - /** - * Override this method to reset the actual state of the page to make the metric pass - * This method is called when too many identical failures were encountered in a row - * - * @return void - */ - protected function clearFailureOnPage() - { - return; - } - - /** - * Get the base class name of the metric implementation - * - * @return string - */ - public function getName() - { - $clazz = get_class($this); - $namespaceBreak = strrpos($clazz, '\\'); - if ($namespaceBreak !== false) { - $clazz = substr($clazz, $namespaceBreak + 1); - } - return $clazz; - } - - /** - * Fetches a new value for the metric and checks if it passes, clearing the failure tracking if so - * - * Even on a success, the readiness check will continue to be run until all metrics pass at the same time in order - * to catch cases where a slow request of one metric can trigger calls for other metrics that were previously - * thought ready - * - * @return boolean - * @throws UnexpectedAlertOpenException - */ - public function runCheck() - { - if ($this->doesMetricPass($this->getCurrentValue(true))) { - $this->setTracker($this->getCurrentValue(), 0); - return true; - } - - return false; - } - - /** - * Update the state of the metric including tracked failure state and checking if a failing value is stuck and - * needs to be reset so future checks can be accurate - * - * Called when the readiness check is finished (either all metrics pass or the check has timed out) - * - * @param Step $step - * @return void - */ - public function finalizeForStep($step) - { - try { - $currentValue = $this->getCurrentValue(); - } catch (UnexpectedAlertOpenException $exception) { - $this->debugLog( - 'An alert is open, bypassing javascript-based metric check', - ['step' => $step->__toString()] - ); - return; - } - - if ($this->doesMetricPass($currentValue)) { - $this->setTracker($currentValue, 0); - } else { - // If failure happened on the same value as before, increment the fail count, otherwise set at 1 - if (!isset($this->storedValue) || $currentValue !== $this->getStoredValue()) { - $failCount = 1; - } else { - $failCount = $this->getFailureCount() + 1; - } - $this->setTracker($currentValue, $failCount); - - $this->errorLog('Failed readiness check', ['step' => $step->__toString()]); - - if ($this->resetFailureThreshold >= 0 && $failCount >= $this->resetFailureThreshold) { - $this->debugLog( - 'Too many failures, assuming metric is stuck and resetting state', - ['step' => $step->__toString()] - ); - $this->resetMetric(); - } - } - } - - /** - * Helper function to retrieve the driver being used to run the test - * - * @return WebDriver - * @throws ModuleRequireException - */ - protected function getDriver() - { - return $this->extension->getDriver(); - } - - /** - * Helper function to execute javascript code, see WebDriver::executeJs for more information - * - * @param string $script - * @param array $arguments - * @return mixed - * @throws ModuleRequireException - */ - protected function executeJs($script, $arguments = []) - { - return $this->extension->getDriver()->executeJS($script, $arguments); - } - - /** - * Gets the current state of the given variable - * Fetches an updated value if not known or $refresh is true - * - * @param boolean $refresh - * @return mixed - * @throws UnexpectedAlertOpenException - */ - private function getCurrentValue($refresh = false) - { - if ($refresh) { - unset($this->currentValue); - } - if (!isset($this->currentValue)) { - $this->currentValue = $this->fetchValueFromPage(); - } - return $this->currentValue; - } - - /** - * Returns the value of the given variable for the previous check - * - * @return mixed - */ - public function getStoredValue() - { - return $this->storedValue ?? null; - } - - /** - * The current count of sequential identical failures - * Used to detect potentially stuck metrics - * - * @return integer - */ - public function getFailureCount() - { - return $this->failCount; - } - - /** - * Update the state of the page to pass the metric and clear the saved failure state - * Called when a failure is found to be stuck - * - * @return void - */ - private function resetMetric() - { - $this->clearFailureOnPage(); - $this->resetTracker(); - } - - /** - * Tracks the most recent value and the number of identical failures in a row - * - * @param mixed $value - * @param integer $failCount - * @return void - */ - public function setTracker($value, $failCount) - { - unset($this->currentValue); - $this->storedValue = $value; - $this->failCount = $failCount; - } - - /** - * Resets the tracked metric values on a new page or stuck failure - * - * @return void - */ - public function resetTracker() - { - unset($this->currentValue); - unset($this->storedValue); - $this->failCount = 0; - } - - /** - * Log the given message to logger->error including context information - * - * @param string $message - * @param array $context - * @return void - * @SuppressWarnings(PHPMD) - */ - protected function errorLog($message, $context = []) - { - $context = array_merge($this->getLogContext(), $context); - //TODO REMOVE THIS LINE, UNCOMMENT LOGGER - //$this->logger->error($message, $context); - } - - /** - * Log the given message to logger->info including context information - * - * @param string $message - * @param array $context - * @return void - * @SuppressWarnings(PHPMD) - */ - protected function infoLog($message, $context = []) - { - $context = array_merge($this->getLogContext(), $context); - //TODO REMOVE THIS LINE, UNCOMMENT LOGGER - //$this->logger->info($message, $context); - } - - /** - * If verbose, log the given message to logger->debug including context information - * - * @param string $message - * @param array $context - * @return void - * @SuppressWarnings(PHPMD) - */ - protected function debugLog($message, $context = []) - { - if ($this->verbose) { - $context = array_merge($this->getLogContext(), $context); - //TODO REMOVE THIS LINE, UNCOMMENT LOGGER - //$this->logger->debug($message, $context); - } - } - - /** - * Base context information to include in all log messages: test name, current URI, metric state - * Reports most recent stored value, not current value, so call setTracker() first to update - * - * @return array - */ - private function getLogContext() - { - return [ - 'test' => $this->extension->getTestName(), - 'uri' => $this->extension->getUri(), - $this->getName() => $this->getStoredValue(), - $this->getName() . '.failCount' => $this->getFailureCount() - ]; - } -} diff --git a/src/Magento/FunctionalTestingFramework/Extension/ReadinessMetrics/DocumentReadyState.php b/src/Magento/FunctionalTestingFramework/Extension/ReadinessMetrics/DocumentReadyState.php deleted file mode 100644 index 26cf91aa7..000000000 --- a/src/Magento/FunctionalTestingFramework/Extension/ReadinessMetrics/DocumentReadyState.php +++ /dev/null @@ -1,43 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ - -namespace Magento\FunctionalTestingFramework\Extension\ReadinessMetrics; - -/** - * Class DocumentReadyState - */ - -use Facebook\WebDriver\Exception\UnexpectedAlertOpenException; - -/** - * Class DocumentReadyState - * - * Looks for document.readyState == 'complete' before passing the readiness check - */ -class DocumentReadyState extends AbstractMetricCheck -{ - /** - * Metric passes when document.readyState == 'complete' - * - * @param string $value - * @return boolean - */ - protected function doesMetricPass($value) - { - return $value === 'complete'; - } - - /** - * Retrieve document.readyState - * - * @return string - * @throws UnexpectedAlertOpenException - */ - protected function fetchValueFromPage() - { - return $this->executeJS('return document.readyState;'); - } -} diff --git a/src/Magento/FunctionalTestingFramework/Extension/ReadinessMetrics/JQueryAjaxRequests.php b/src/Magento/FunctionalTestingFramework/Extension/ReadinessMetrics/JQueryAjaxRequests.php deleted file mode 100644 index c005923d3..000000000 --- a/src/Magento/FunctionalTestingFramework/Extension/ReadinessMetrics/JQueryAjaxRequests.php +++ /dev/null @@ -1,58 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ - -namespace Magento\FunctionalTestingFramework\Extension\ReadinessMetrics; - -use Facebook\WebDriver\Exception\UnexpectedAlertOpenException; - -/** - * Class JQueryAjaxRequests - * - * Looks for all active jQuery ajax requests to finish before passing the readiness check - */ -class JQueryAjaxRequests extends AbstractMetricCheck -{ - /** - * Metric passes once there are no remaining active requests - * - * @param integer $value - * @return boolean - */ - protected function doesMetricPass($value) - { - return $value == 0; - } - - /** - * Grabs the number of active jQuery ajax requests if available - * - * @return integer - * @throws UnexpectedAlertOpenException - */ - protected function fetchValueFromPage() - { - return intval( - $this->executeJS( - 'if (!!window.jQuery) { - return window.jQuery.active; - } - return 0;' - ) - ); - } - - /** - * Active request count can get stuck above zero if an exception is thrown during a callback, causing the - * ajax handler method to fail before decrementing the request count - * - * @return void - * @throws UnexpectedAlertOpenException - */ - protected function clearFailureOnPage() - { - $this->executeJS('if (!!window.jQuery) { window.jQuery.active = 0; };'); - } -} diff --git a/src/Magento/FunctionalTestingFramework/Extension/ReadinessMetrics/MagentoLoadingMasks.php b/src/Magento/FunctionalTestingFramework/Extension/ReadinessMetrics/MagentoLoadingMasks.php deleted file mode 100644 index 4f15524ba..000000000 --- a/src/Magento/FunctionalTestingFramework/Extension/ReadinessMetrics/MagentoLoadingMasks.php +++ /dev/null @@ -1,54 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ - -namespace Magento\FunctionalTestingFramework\Extension\ReadinessMetrics; - -use Facebook\WebDriver\Exception\NoSuchElementException; -use Facebook\WebDriver\Exception\StaleElementReferenceException; -use Magento\FunctionalTestingFramework\Module\MagentoWebDriver; -use WebDriverBy; - -/** - * Class MagentoLoadingMasks - * - * Looks for all loading masks to disappear before passing the readiness check - */ -class MagentoLoadingMasks extends AbstractMetricCheck -{ - /** - * Metric passes once all loading masks are absent or invisible - * - * @param string|null $value - * @return boolean - */ - protected function doesMetricPass($value) - { - return $value === null; - } - - /** - * Get the locator and ID for the first active loading mask or null if there are none visible - * - * @return string|null - */ - protected function fetchValueFromPage() - { - foreach (MagentoWebDriver::$loadingMasksLocators as $maskLocator) { - $driverLocator = WebDriverBy::xpath($maskLocator); - $maskElements = $this->getDriver()->webDriver->findElements($driverLocator); - foreach ($maskElements as $element) { - try { - if ($element->isDisplayed()) { - return "$maskLocator : " . $element ->getID(); - } - } catch (NoSuchElementException $e) { - } catch (StaleElementReferenceException $e) { - } - } - } - return null; - } -} diff --git a/src/Magento/FunctionalTestingFramework/Extension/ReadinessMetrics/PrototypeAjaxRequests.php b/src/Magento/FunctionalTestingFramework/Extension/ReadinessMetrics/PrototypeAjaxRequests.php deleted file mode 100644 index 2fc8f70cb..000000000 --- a/src/Magento/FunctionalTestingFramework/Extension/ReadinessMetrics/PrototypeAjaxRequests.php +++ /dev/null @@ -1,58 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ - -namespace Magento\FunctionalTestingFramework\Extension\ReadinessMetrics; - -use Facebook\WebDriver\Exception\UnexpectedAlertOpenException; - -/** - * Class PrototypeAjaxRequests - * - * Looks for all active prototype ajax requests to finish before passing the readiness check - */ -class PrototypeAjaxRequests extends AbstractMetricCheck -{ - /** - * Metric passes once there are no remaining active requests - * - * @param integer $value - * @return boolean - */ - protected function doesMetricPass($value) - { - return $value == 0; - } - - /** - * Grabs the number of active prototype ajax requests if available - * - * @return integer - * @throws UnexpectedAlertOpenException - */ - protected function fetchValueFromPage() - { - return intval( - $this->executeJS( - 'if (!!window.Prototype) { - return window.Ajax.activeRequestCount; - } - return 0;' - ) - ); - } - - /** - * Active request count can get stuck above zero if an exception is thrown during a callback, causing the - * ajax handler method to fail before decrementing the request count - * - * @return void - * @throws UnexpectedAlertOpenException - */ - protected function clearFailureOnPage() - { - $this->executeJS('if (!!window.Prototype) { window.Ajax.activeRequestCount = 0; };'); - } -} diff --git a/src/Magento/FunctionalTestingFramework/Extension/ReadinessMetrics/RequireJsDefinitions.php b/src/Magento/FunctionalTestingFramework/Extension/ReadinessMetrics/RequireJsDefinitions.php deleted file mode 100644 index 6df470123..000000000 --- a/src/Magento/FunctionalTestingFramework/Extension/ReadinessMetrics/RequireJsDefinitions.php +++ /dev/null @@ -1,60 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ - -namespace Magento\FunctionalTestingFramework\Extension\ReadinessMetrics; - -use Facebook\WebDriver\Exception\UnexpectedAlertOpenException; - -/** - * Class RequireJsDefinitions - * - * Looks for all active require.js module definitions to complete before passing the readiness check - */ -class RequireJsDefinitions extends AbstractMetricCheck -{ - /** - * Metric passes once there are no enabled modules waiting in the registry queue - * - * @param string|null $value - * @return boolean - */ - protected function doesMetricPass($value) - { - return $value === null; - } - - /** - * Retrieve the name of the first enabled module still waiting in the require.js registry queue - * - * @return string|null - * @throws UnexpectedAlertOpenException - */ - protected function fetchValueFromPage() - { - $script = - 'if (!window.requirejs) { - return null; - } - var contexts = window.requirejs.s.contexts; - for (var label in contexts) { - if (contexts.hasOwnProperty(label)) { - var registry = contexts[label].registry; - for (var module in registry) { - if (registry.hasOwnProperty(module) && registry[module].enabled) { - return module; - } - } - } - } - return null;'; - - $moduleInProgress = $this->executeJS($script); - if ($moduleInProgress === 'null') { - $moduleInProgress = null; - } - return $moduleInProgress; - } -} diff --git a/src/Magento/FunctionalTestingFramework/Extension/TestContextExtension.php b/src/Magento/FunctionalTestingFramework/Extension/TestContextExtension.php index 467074097..19e1edc29 100644 --- a/src/Magento/FunctionalTestingFramework/Extension/TestContextExtension.php +++ b/src/Magento/FunctionalTestingFramework/Extension/TestContextExtension.php @@ -7,17 +7,24 @@ namespace Magento\FunctionalTestingFramework\Extension; use Codeception\Events; +use Magento\FunctionalTestingFramework\Allure\AllureHelper; use Magento\FunctionalTestingFramework\DataGenerator\Handlers\PersistedObjectHandler; /** * Class TestContextExtension * @SuppressWarnings(PHPMD.UnusedPrivateField) + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class TestContextExtension extends BaseExtension { const TEST_PHASE_AFTER = "_after"; - const CODECEPT_AFTER_VERSION = "2.3.9"; + const TEST_PHASE_BEFORE = "_before"; + const TEST_FAILED_FILE = 'failed'; + const TEST_HOOKS = [ + self::TEST_PHASE_AFTER => 'AfterHook', + self::TEST_PHASE_BEFORE => 'BeforeHook' + ]; /** * Codeception Events Mapping to methods @@ -25,6 +32,12 @@ class TestContextExtension extends BaseExtension */ public static $events; + /** + * The name of the currently running test + * @var string + */ + public $currentTest; + /** * Initialize local vars * @@ -35,7 +48,6 @@ public function _initialize() { $events = [ Events::TEST_START => 'testStart', - Events::TEST_FAIL => 'testFail', Events::STEP_AFTER => 'afterStep', Events::TEST_END => 'testEnd', Events::RESULT_PRINT_AFTER => 'saveFailed' @@ -49,30 +61,29 @@ public function _initialize() * @throws \Exception * @return void */ - public function testStart() + public function testStart(\Codeception\Event\TestEvent $e) { + if (getenv('ENABLE_CODE_COVERAGE') === 'true') { + // Curl against test.php and pass in the test name. Used when gathering code coverage. + $this->currentTest = $e->getTest()->getMetadata()->getName(); + $cURLConnection = curl_init(); + curl_setopt_array($cURLConnection, [ + CURLOPT_RETURNTRANSFER => 1, + CURLOPT_URL => getenv('MAGENTO_BASE_URL') . "/test.php?test=" . $this->currentTest, + ]); + curl_exec($cURLConnection); + if (version_compare(PHP_VERSION, '8.0') < 0) { + // this function no longer has an effect in PHP 8.0, but it's required in earlier versions + curl_close($cURLConnection); + } + } + PersistedObjectHandler::getInstance()->clearHookObjects(); PersistedObjectHandler::getInstance()->clearTestObjects(); } /** - * Codeception event listener function, triggered on test failure. - * @param \Codeception\Event\FailEvent $e - * @return void - */ - public function testFail(\Codeception\Event\FailEvent $e) - { - $cest = $e->getTest(); - $context = $this->extractContext($e->getFail()->getTrace(), $cest->getTestMethod()); - // Do not attempt to run _after if failure was in the _after block - // Try to run _after but catch exceptions to prevent them from overwriting original failure. - if ($context != TestContextExtension::TEST_PHASE_AFTER) { - $this->runAfterBlock($e, $cest); - } - } - - /** - * Codeception event listener function, triggered on test ending (naturally or by error). + * Codeception event listener function, triggered on test ending naturally or by errors/failures. * @param \Codeception\Event\TestEvent $e * @return void * @throws \Exception @@ -81,55 +92,33 @@ public function testEnd(\Codeception\Event\TestEvent $e) { $cest = $e->getTest(); - //Access private TestResultObject to find stack and if there are any errors (as opposed to failures) + //Access private TestResultObject to find stack and if there are any errors/failures $testResultObject = call_user_func(\Closure::bind( function () use ($cest) { return $cest->getTestResultObject(); }, $cest )); - $errors = $testResultObject->errors(); - if (!empty($errors)) { - foreach ($errors as $error) { - if ($error->failedTest()->getTestMethod() == $cest->getName()) { - $stack = $errors[0]->thrownException()->getTrace(); - $context = $this->extractContext($stack, $cest->getTestMethod()); - // Do not attempt to run _after if failure was in the _after block - // Try to run _after but catch exceptions to prevent them from overwriting original failure. - if ($context != TestContextExtension::TEST_PHASE_AFTER) { - $this->runAfterBlock($e, $cest); - } - continue; + + // check for errors in all test hooks and attach in allure + if (!empty($testResultObject->errors())) { + foreach ($testResultObject->errors() as $error) { + if ($error->failedTest()->getTestMethod() === $cest->getTestMethod()) { + $this->attachExceptionToAllure($error->thrownException(), $cest->getTestMethod()); } } } - // Reset Session and Cookies after all Test Runs, workaround due to functional.suite.yml restart: true - $this->getDriver()->_runAfter($e->getTest()); - } - /** - * Runs cest's after block, if necessary. - * @param \Symfony\Component\EventDispatcher\Event $e - * @param \Codeception\TestInterface $cest - * @return void - */ - private function runAfterBlock($e, $cest) - { - try { - $actorClass = $e->getTest()->getMetadata()->getCurrent('actor'); - $I = new $actorClass($cest->getScenario()); - if (version_compare(\Codeception\Codecept::VERSION, TestContextExtension::CODECEPT_AFTER_VERSION, "<=")) { - call_user_func(\Closure::bind( - function () use ($cest, $I) { - $cest->executeHook($I, 'after'); - }, - null, - $cest - )); + // check for failures in all test hooks and attach in allure + if (!empty($testResultObject->failures())) { + foreach ($testResultObject->failures() as $failure) { + if ($failure->failedTest()->getTestMethod() === $cest->getTestMethod()) { + $this->attachExceptionToAllure($failure->thrownException(), $cest->getTestMethod()); + } } - } catch (\Exception $e) { - // Do not rethrow Exception } + // Reset Session and Cookies after all Test Runs, workaround due to functional.suite.yml restart: true + $this->getDriver()->_runAfter($e->getTest()); } /** @@ -142,13 +131,53 @@ public function extractContext($trace, $class) { foreach ($trace as $entry) { $traceClass = $entry["class"] ?? null; - if (strpos($traceClass, $class) != 0) { + if (strpos($traceClass, $class) !== 0) { return $entry["function"]; } } return null; } + /** + * Attach stack trace of exceptions thrown in each test hook to allure. + * @param \Exception $exception + * @param string $testMethod + * @return mixed + */ + public function attachExceptionToAllure($exception, $testMethod) + { + if (is_subclass_of($exception, \PHPUnit\Framework\Exception::class)) { + $trace = $exception->getSerializableTrace(); + } else { + $trace = $exception->getTrace(); + } + + $context = $this->extractContext($trace, $testMethod); + + if (isset(self::TEST_HOOKS[$context])) { + $context = self::TEST_HOOKS[$context]; + } else { + $context = 'TestMethod'; + } + + AllureHelper::addAttachmentToCurrentStep($exception, $context . 'Exception'); + + //pop suppressed exceptions and attach to allure + $change = function () { + if ($this instanceof \PHPUnit\Framework\ExceptionWrapper) { + return $this->previous; + } else { + return $this->getPrevious(); + } + }; + + $previousException = $change->call($exception); + + if ($previousException !== null) { + $this->attachExceptionToAllure($previousException, $testMethod); + } + } + /** * Codeception event listener function, triggered before step. * Check if it's a new page. @@ -173,7 +202,20 @@ public function beforeStep(\Codeception\Event\StepEvent $e) */ public function afterStep(\Codeception\Event\StepEvent $e) { - ErrorLogger::getInstance()->logErrors($this->getDriver(), $e); + $browserLog = []; + try { + $browserLog = $this->getDriver()->webDriver->manage()->getLog("browser"); + } catch (\Exception $exception) { + } + if (getenv('ENABLE_BROWSER_LOG') === 'true') { + foreach (explode(',', getenv('BROWSER_LOG_BLOCKLIST')) as $source) { + $browserLog = BrowserLogUtil::filterLogsOfType($browserLog, $source); + } + if (!empty($browserLog)) { + AllureHelper::addAttachmentToCurrentStep(json_encode($browserLog, JSON_PRETTY_PRINT), "Browser Log"); + } + } + BrowserLogUtil::logErrors($browserLog, $this->getDriver(), $e); } /** @@ -218,7 +260,7 @@ public function saveFailed(\Codeception\Event\PrintResultEvent $e) protected function localizePath($path) { $root = realpath($this->getRootDir()) . DIRECTORY_SEPARATOR; - if (substr($path, 0, strlen($root)) == $root) { + if (substr($path, 0, strlen($root)) === $root) { return substr($path, strlen($root)); } return $path; diff --git a/src/Magento/FunctionalTestingFramework/Filter/FilterInterface.php b/src/Magento/FunctionalTestingFramework/Filter/FilterInterface.php new file mode 100644 index 000000000..bb89ab3c2 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Filter/FilterInterface.php @@ -0,0 +1,27 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\FunctionalTestingFramework\Filter; + +/** + * Interface for future test filters + * @api + */ +interface FilterInterface +{ + /** + * @param array $filterValues + */ + public function __construct(array $filterValues = []); + + /** + * @param array $tests + * @return void + */ + public function filter(array &$tests); +} diff --git a/src/Magento/FunctionalTestingFramework/Filter/FilterList.php b/src/Magento/FunctionalTestingFramework/Filter/FilterList.php new file mode 100644 index 000000000..35ff32edd --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Filter/FilterList.php @@ -0,0 +1,57 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\FunctionalTestingFramework\Filter; + +use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; + +/** + * Class FilterList has a list of filters. + */ +class FilterList +{ + /** + * List of filters + * @var \Magento\FunctionalTestingFramework\Filter\FilterInterface[] + */ + private $filters = []; + + /** + * Constructor for Filter list. + * + * @param array $filters + * @throws \Exception + */ + public function __construct(array $filters = []) + { + foreach ($filters as $filterType => $filterValue) { + $className = "Magento\FunctionalTestingFramework\Filter\Test\\" . ucfirst($filterType); + if (!class_exists($className)) { + throw new TestFrameworkException("Filter type '" . $filterType . "' do not exist."); + } + $this->filters[$filterType] = new $className($filterValue); + } + } + + /** + * @return array + */ + public function getFilters(): array + { + return $this->filters; + } + + /** + * @param string $filterType + * @return \Magento\FunctionalTestingFramework\Filter\FilterInterface + */ + public function getFilter(string $filterType): FilterInterface + { + return $this->filters[$filterType]; + } +} diff --git a/src/Magento/FunctionalTestingFramework/Filter/Test/ExcludeGroup.php b/src/Magento/FunctionalTestingFramework/Filter/Test/ExcludeGroup.php new file mode 100644 index 000000000..38a3bf8f2 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Filter/Test/ExcludeGroup.php @@ -0,0 +1,58 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\FunctionalTestingFramework\Filter\Test; + +use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; +use Magento\FunctionalTestingFramework\Filter\FilterInterface; +use Magento\FunctionalTestingFramework\Test\Objects\TestObject; + +/** + * Class ExcludeGroup + */ +class ExcludeGroup implements FilterInterface +{ + const ANNOTATION_TAG = 'group'; + + /** + * @var array + */ + private $filterValues = []; + + /** + * Group constructor. + * + * @param array $filterValues + * @throws TestFrameworkException + */ + public function __construct(array $filterValues = []) + { + $this->filterValues = $filterValues; + } + + /** + * Filter tests by group. + * + * @param TestObject[] $tests + * @return void + */ + public function filter(array &$tests) + { + if ($this->filterValues === []) { + return; + } + /** @var TestObject $test */ + foreach ($tests as $testName => $test) { + $groups = $test->getAnnotationByName(self::ANNOTATION_TAG); + $testExcludeGroup = !empty(array_intersect($groups, $this->filterValues)); + if ($testExcludeGroup) { + unset($tests[$testName]); + } + } + } +} diff --git a/src/Magento/FunctionalTestingFramework/Filter/Test/IncludeGroup.php b/src/Magento/FunctionalTestingFramework/Filter/Test/IncludeGroup.php new file mode 100644 index 000000000..b84a48e55 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Filter/Test/IncludeGroup.php @@ -0,0 +1,58 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\FunctionalTestingFramework\Filter\Test; + +use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; +use Magento\FunctionalTestingFramework\Filter\FilterInterface; +use Magento\FunctionalTestingFramework\Test\Objects\TestObject; + +/** + * Class IncludeGroup + */ +class IncludeGroup implements FilterInterface +{ + const ANNOTATION_TAG = 'group'; + + /** + * @var array + */ + private $filterValues = []; + + /** + * Group constructor. + * + * @param array $filterValues + * @throws TestFrameworkException + */ + public function __construct(array $filterValues = []) + { + $this->filterValues = $filterValues; + } + + /** + * Filter tests by group. + * + * @param TestObject[] $tests + * @return void + */ + public function filter(array &$tests) + { + if ($this->filterValues === []) { + return; + } + /** @var TestObject $test */ + foreach ($tests as $testName => $test) { + $groups = $test->getAnnotationByName(self::ANNOTATION_TAG); + $testIncludeGroup = empty(array_intersect($groups, $this->filterValues)); + if ($testIncludeGroup) { + unset($tests[$testName]); + } + } + } +} diff --git a/src/Magento/FunctionalTestingFramework/Filter/Test/Severity.php b/src/Magento/FunctionalTestingFramework/Filter/Test/Severity.php new file mode 100644 index 000000000..e88c4af7b --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Filter/Test/Severity.php @@ -0,0 +1,68 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\FunctionalTestingFramework\Filter\Test; + +use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; +use Magento\FunctionalTestingFramework\Filter\FilterInterface; +use Magento\FunctionalTestingFramework\Test\Objects\TestObject; +use Magento\FunctionalTestingFramework\Test\Util\AnnotationExtractor; + +/** + * Class Severity + */ +class Severity implements FilterInterface +{ + const ANNOTATION_TAG = 'severity'; + + /** + * @var array + */ + private $filterValues = []; + + /** + * Severity constructor. + * + * @param array $filterValues + * @throws TestFrameworkException + */ + public function __construct(array $filterValues = []) + { + $severityValues = AnnotationExtractor::MAGENTO_TO_ALLURE_SEVERITY_MAP; + + foreach ($filterValues as $filterValue) { + if (!isset($severityValues[$filterValue])) { + throw new TestFrameworkException( + 'Not existing severity specified.' . PHP_EOL + . 'Possible values: '. implode(', ', array_keys($severityValues)) . '.' . PHP_EOL + . 'Provided values: ' . implode(', ', $filterValues) . '.' . PHP_EOL + ); + } + $this->filterValues[] = $severityValues[$filterValue]; + } + } + + /** + * Filter tests by severity. + * + * @param TestObject[] $tests + * @return void + */ + public function filter(array &$tests) + { + /** @var TestObject $test */ + foreach ($tests as $testName => $test) { + $severities = $test->getAnnotationByName(self::ANNOTATION_TAG); + foreach ($severities as $severity) { + if (!in_array($severity, $this->filterValues, true)) { + unset($tests[$testName]); + } + } + } + } +} diff --git a/src/Magento/FunctionalTestingFramework/Helper/Acceptance.php b/src/Magento/FunctionalTestingFramework/Helper/Acceptance.php deleted file mode 100644 index fafe167af..000000000 --- a/src/Magento/FunctionalTestingFramework/Helper/Acceptance.php +++ /dev/null @@ -1,42 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ - -namespace Magento\FunctionalTestingFramework\Helper; - -use Magento\FunctionalTestingFramework\Module\MagentoWebDriver; -use Codeception\Module; - -/** - * Class Acceptance - * - * Define global actions - * All public methods declared in helper class will be available in $I - */ -class Acceptance extends Module -{ - /** - * Reconfig WebDriver. - * - * @param string $config - * @param string $value - * @return void - */ - public function changeConfiguration($config, $value) - { - $this->getModule(MagentoWebDriver::class)->_reconfigure([$config => $value]); - } - - /** - * Get WebDriver configuration. - * - * @param string $config - * @return string - */ - public function getConfiguration($config) - { - return $this->getModule(MagentoWebDriver::class)->_getConfig($config); - } -} diff --git a/src/Magento/FunctionalTestingFramework/Helper/AdminUrlList.php b/src/Magento/FunctionalTestingFramework/Helper/AdminUrlList.php deleted file mode 100644 index ca96033dc..000000000 --- a/src/Magento/FunctionalTestingFramework/Helper/AdminUrlList.php +++ /dev/null @@ -1,189 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ - -namespace Magento\FunctionalTestingFramework\Helper; - -/** - * Class AdminUrlList - * @SuppressWarnings(PHPMD) - */ -// @codingStandardsIgnoreFile -class AdminUrlList -{ - public static $adminLoginPage = '/admin/admin/'; - public static $adminLogoutPage = '/admin/admin/auth/logout/'; - public static $adminForgotYourPasswordPage = '/admin/admin/auth/forgotpassword/'; - - public static $adminDashboardPage = '/admin/admin/dashboard/'; - - public static $adminOrdersGrid = '/admin/sales/order/'; - public static $adminOrderByIdPage = '/admin/sales/order/view/order_id/'; - public static $adminAddOrderPage = '/admin/sales/order_create/index/'; - public static $adminAddOrderForCustomerIdPage = '/admin/sales/order_create/index/customer_id/'; - public static $adminInvoicesGrid = '/admin/sales/invoice/'; - public static $adminAddInvoiceForOrderIdPage = '/admin/sales/order_invoice/new/order_id/'; - public static $adminShipmentsGrid = '/admin/sales/shipment/'; - public static $adminShipmentForIdPage = '/admin/sales/shipment/view/shipment_id/'; - public static $adminCreditMemosGrid = '/admin/sales/creditmemo/'; - public static $adminCreditMemoForIdPage = '/admin/sales/creditmemo/view/creditmemo_id/'; - public static $adminBillingAgreementsGrid = '/admin/paypal/billing_agreement/'; - // TODO: Determine the correct address for Billing Agreements for Billing Agreement ID page URL. - public static $adminTransactionsGrid = '/admin/sales/transactions/'; - // TODO: Determine the correct address for Transactions for Transaction ID page URL. - - public static $adminCatalogGrid = '/admin/catalog/product/'; - public static $adminProductForIdPage = '/admin/catalog/product/edit/id/'; - public static $adminAddSimpleProductPage = '/admin/catalog/product/new/set/4/type/simple/'; - public static $adminAddConfigurableProductPage = '/admin/catalog/product/new/set/4/type/configurable/'; - public static $adminAddGroupedProductPage = '/admin/catalog/product/new/set/4/type/grouped/'; - public static $adminAddVirtualProductPage = '/admin/catalog/product/new/set/4/type/virtual/'; - public static $adminAddBundleProductPage = '/admin/catalog/product/new/set/4/type/bundle/'; - public static $adminAddDownloadableProductPage = '/admin/catalog/product/new/set/4/type/downloadable/'; - - public static $adminCategoriesPage = '/admin/catalog/category/'; - public static $adminCategoryForIdPage = '/admin/catalog/category/edit/id/'; - public static $adminAddRootCategoryPage = '/admin/catalog/category/add/store/0/parent/1'; - public static $adminAddSubCategoryPage = '/admin/catalog/category/add/store/0/parent/2'; - - public static $adminAllCustomersGrid = '/admin/customer/index/'; - public static $adminCustomersNowOnlineGrid = '/admin/customer/online/'; - public static $adminCustomerForCustomerIdPage = '/admin/customer/index/edit/id/'; - public static $adminAddCustomerPage = '/admin/customer/index/new/'; - - public static $adminCatalogPriceRuleGrid = '/admin/catalog_rule/promo_catalog/'; - public static $adminCatalogPriceRuleForIdPage = '/admin/catalog_rule/promo_catalog/edit/id/'; - public static $adminAddCatalogPriceRulePage = '/admin/catalog_rule/promo_catalog/new/'; - public static $adminCartPriceRulesGrid = '/admin/sales_rule/promo_quote/'; - public static $adminCartPriceRuleForIdPage = '/admin/sales_rule/promo_quote/edit/id/'; - public static $adminAddCartPriceRulePage = '/admin/sales_rule/promo_quote/new/'; - public static $adminEmailTemplatesGrid = '/admin/admin/email_template/'; - public static $adminEmailTemplateForIdPage = '/admin/admin/email_template/edit/id/'; - public static $adminAddEmailTemplatePage = '/admin/admin/email_template/new/'; - public static $adminNewsletterTemplateGrid = '/admin/newsletter/template/'; - public static $adminNewsletterTemplateForIdPage = '/admin/newsletter/template/edit/id/'; - public static $adminAddNewsletterTemplatePage = '/admin/newsletter/template/new/'; - public static $adminNewsletterQueueGrid = '/admin/newsletter/queue/'; - // TODO: Determine if there is a Details page for the Newsletter Queue. - public static $adminNewsletterSubscribersGrid = '/admin/newsletter/subscriber/'; - public static $adminURLRewritesGrid = '/admin/admin/url_rewrite/index/'; - public static $adminURLRewriteForIdPage = '/admin/admin/url_rewrite/edit/id/'; - public static $adminAddURLRewritePage = '/admin/admin/url_rewrite/edit/id'; // If you don't list an ID it drops you on the Add page. - public static $adminSearchTermsGrid = '/admin/search/term/index/'; - public static $adminSearchTermForIdPage = '/admin/search/term/edit/id/'; - public static $adminAddSearchTermPage = '/admin/search/term/new/'; - public static $adminSearchSynonymsGrid = '/admin/search/synonyms/index/'; - public static $adminSearchSynonymGroupForIdPage = '/admin/search/synonyms/edit/group_id/'; - public static $adminAddSearchSynonymGroupPage = '/admin/search/synonyms/new/'; - public static $adminSiteMapGrid = '/admin/admin/sitemap/'; - public static $adminSiteMapForIdPage = '/admin/admin/sitemap/edit/sitemap_id/'; - public static $adminAddSiteMapPage = '/admin/admin/sitemap/new/'; - public static $adminReviewsGrid = '/admin/review/product/index/'; - public static $adminReviewByIdPage = '/admin/review/product/edit/id/'; - public static $adminAddReviewPage = '/admin/review/product/new/'; - - public static $adminPagesGrid = '/admin/cms/page/'; - public static $adminPageForIdPage = '/admin/cms/page/edit/page_id/'; - public static $adminAddPagePage = '/admin/cms/page/new/'; - public static $adminBlocksGrid = '/admin/cms/block/'; - public static $adminBlockForIdPage = '/admin/cms/block/edit/block_id/'; - public static $adminAddBlockPage = '/admin/cms/block/new/'; - public static $adminWidgetsGrid = '/admin/admin/widget_instance/'; - // TODO: Determine how the Edit Widget URLs are generated. - public static $adminAddWidgetPage = '/admin/admin/widget_instance/new/'; - public static $adminDesignConfigurationGrid = '/admin/theme/design_config/'; - // TODO: Determine how the Design Configuration URLs are generated. - public static $adminThemesGrid = '/admin/admin/system_design_theme/'; - public static $adminThemeByIdPage = '/admin/admin/system_design_theme/edit/id/'; - public static $adminStoreContentScheduleGrid = '/admin/admin/system_design/'; - public static $adminStoreContentScheduleForIdPage = '/admin/admin/system_design/edit/id/'; - public static $adminAddStoreDesignChangePage = '/admin/admin/system_design/new/'; - - public static $adminProductsInCartGrid = '/admin/reports/report_shopcart/product/'; - public static $adminSearchTermsReportGrid = '/admin/search/term/report/'; - public static $adminAbandonedCartsGrid = '/admin/reports/report_shopcart/abandoned/'; - public static $adminNewsletterProblemsReportGrid = '/admin/newsletter/problem/'; - public static $adminCustomerReviewsReportGrid = '/admin/reports/report_review/customer/'; - public static $adminProductReviewsReportGrid = '/admin/reports/report_review/product/'; - public static $adminProductReviewsForProductIdPage = '/admin/review/product/index/productId/'; - public static $adminOrdersReportGrid = '/admin/reports/report_sales/sales/'; - public static $adminTaxReportGrid = '/admin/reports/report_sales/tax/'; - public static $adminInvoiceReportGrid = '/admin/reports/report_sales/invoiced/'; - public static $adminShippingReportGrid = '/admin/reports/report_sales/shipping/'; - public static $adminRefundsReportGrid = '/admin/reports/report_sales/refunded/'; - public static $adminCouponsReportGrid = '/admin/reports/report_sales/coupons/'; - public static $adminPayPalSettlementReportsGrid = '/admin/paypal/paypal_reports/'; - public static $adminBraintreeSettlementReportGrid = '/admin/braintree/report/'; - public static $adminOrderTotalReportGrid = '/admin/reports/report_customer/totals/'; - public static $adminOrderCountReportGrid = '/admin/reports/report_customer/orders/'; - public static $adminNewAccountsReportGrid = '/admin/reports/report_customer/accounts/'; - public static $adminProductViewsReportGrid = '/admin/reports/report_product/viewed/'; - public static $adminBestsellersReportGrid = '/admin/reports/report_sales/bestsellers/'; - public static $adminLowStockReportGrid = '/admin/reports/report_product/lowstock/'; - public static $adminOrderedProductsReportGrid = '/admin/reports/report_product/sold/'; - public static $adminDownloadsReportGrid = '/admin/reports/report_product/downloads/'; - public static $adminRefreshStatisticsGrid = '/admin/reports/report_statistics/'; - - public static $adminAllStoresGrid = '/admin/admin/system_store/'; - public static $adminCreateStoreViewPage = '/admin/admin/system_store/newStore/'; - public static $adminCreateStorePage = '/admin/admin/system_store/newGroup/'; - public static $adminCreateWebsitePage = '/admin/admin/system_store/newWebsite/'; - public static $adminWebsiteByIdPage = '/admin/admin/system_store/editWebsite/website_id/'; - public static $adminStoreViewByIdPage = '/admin/admin/system_store/editStore/store_id/'; - public static $adminStoreByIdPage = '/admin/admin/system_store/editGroup/group_id/'; - public static $adminConfigurationGrid = '/admin/admin/system_config/'; - public static $adminTermsAndConditionsGrid = '/admin/checkout/agreement/'; - public static $adminTermsAndConditionByIdPage = '/admin/checkout/agreement/edit/id/'; - public static $adminAddNewTermsAndConditionPage = '/admin/checkout/agreement/new/'; - public static $adminOrderStatusGrid = '/admin/sales/order_status/'; - public static $adminAddOrderStatusPage = '/admin/sales/order_status/new/'; - // TODO: Determine how the Order Status URLs are generated. - public static $adminTaxRulesGrid = '/admin/tax/rule/'; - public static $adminTaxRuleByIdPage = '/admin/tax/rule/edit/rule/'; - public static $adminAddTaxRulePage = '/admin/tax/rule/new/'; - public static $adminTaxZonesAndRatesGrid = '/admin/tax/rate/'; - public static $adminTaxZoneAndRateByIdPage = '/admin/tax/rate/edit/rate/'; - public static $adminAddTaxZoneAndRatePage = '/admin/tax/rate/add/'; - public static $adminCurrencyRatesPage = '/admin/admin/system_currency/'; - public static $adminCurrencySymbolsPage = '/admin/admin/system_currencysymbol/'; - public static $adminProductAttributesGrid = '/admin/catalog/product_attribute/'; - public static $adminProductAttributeForIdPage = '/admin/catalog/product_attribute/edit/attribute_id/'; - public static $adminAddProductAttributePage = '/admin/catalog/product_attribute/new/'; - public static $adminAttributeSetsGrid = '/admin/catalog/product_set/'; - public static $adminAttributeSetByIdPage = '/admin/catalog/product_set/edit/id/'; - public static $adminAddAttributeSetPage = '/admin/catalog/product_set/add/'; - public static $adminRatingsGrid = '/admin/review/rating/'; - public static $adminRatingForIdPage = '/admin/review/rating/edit/id/'; - public static $adminAddRatingPage = '/admin/review/rating/new/'; - public static $adminCustomerGroupsGrid = '/admin/customer/group/'; - public static $adminCustomerGroupByIdPage = '/admin/customer/group/edit/id/'; - public static $adminAddCustomerGroupPage = '/admin/customer/group/new/'; - - public static $adminImportPage = '/admin/admin/import/'; - public static $adminExportPage = '/admin/admin/export/'; - public static $adminImportAndExportTaxRatesPage = '/admin/tax/rate/importExport/'; - public static $adminImportHistoryGrid = '/admin/admin/history/'; - public static $adminIntegrationsGrid = '/admin/admin/integration/'; - public static $adminIntegrationByIdPage = '/admin/admin/integration/edit/id/'; - public static $adminAddIntegrationPage = '/admin/admin/integration/new/'; - public static $adminCacheManagementGrid = '/admin/admin/cache/'; - public static $adminBackupsGrid = '/admin/backup/index/'; - public static $adminIndexManagementGrid = '/admin/indexer/indexer/list/'; - public static $adminWebSetupWizardPage = '/setup/'; - public static $adminAllUsersGrid = '/admin/admin/user/'; - public static $adminUserByIdPage = '/admin/admin/user/edit/user_id/'; - public static $adminAddNewUserPage = '/admin/admin/user/new/'; - public static $adminLockedUsersGrid = '/admin/admin/locks/'; - public static $adminUserRolesGrid = '/admin/admin/user_role/'; - public static $adminUserRoleByIdPage = '/admin/admin/user_role/editrole/rid/'; - public static $adminAddUserRolePage = '/admin/admin/user_role/editrole/'; - public static $adminNotificationsGrid = '/admin/admin/notification/'; - public static $adminCustomVariablesGrid = '/admin/admin/system_variable/'; - public static $adminCustomVariableByIdPage = '/admin/admin/system_variable/edit/variable_id/'; - public static $adminAddCustomVariablePage = '/admin/admin/system_variable/new/'; - public static $adminEncryptionKeyPage = '/admin/admin/crypt_key/'; - - public static $adminFindPartnersAndExtensions = '/admin/marketplace/index/'; -} \ No newline at end of file diff --git a/src/Magento/FunctionalTestingFramework/Helper/Code/ClassReader.php b/src/Magento/FunctionalTestingFramework/Helper/Code/ClassReader.php new file mode 100644 index 000000000..146a3d6a9 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Helper/Code/ClassReader.php @@ -0,0 +1,51 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\FunctionalTestingFramework\Helper\Code; + +/** + * Class ClassReader + * + * @internal + */ +class ClassReader +{ + /** + * Read class method signature + * + * @param string $className + * @param string $method + * @return array|null + * @throws \ReflectionException + */ + public function getParameters($className, $method) + { + $class = new \ReflectionClass($className); + $result = null; + $method = $class->getMethod($method); + if ($method) { + $result = []; + /** @var $parameter \ReflectionParameter */ + foreach ($method->getParameters() as $parameter) { + try { + $result[$parameter->getName()] = [ + 'type' => $parameter->getType() === null ? null : $parameter->getType()->getName(), + 'variableName' => $parameter->getName(), + 'isOptional' => $parameter->isOptional(), + 'optionalValue' => $parameter->isOptional() ? + $parameter->isDefaultValueAvailable() ? $parameter->getDefaultValue() : null : + null + ]; + } catch (\ReflectionException $e) { + $message = $e->getMessage(); + throw new \ReflectionException($message, 0, $e); + } + } + } + + return $result; + } +} diff --git a/src/Magento/FunctionalTestingFramework/Helper/EntityRESTApiHelper.php b/src/Magento/FunctionalTestingFramework/Helper/EntityRESTApiHelper.php deleted file mode 100644 index 521ddc9ee..000000000 --- a/src/Magento/FunctionalTestingFramework/Helper/EntityRESTApiHelper.php +++ /dev/null @@ -1,112 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ - -namespace Magento\FunctionalTestingFramework\Helper; - -use GuzzleHttp\Client; - -/** - * Class EntityRESTApiHelper - * @package Magento\FunctionalTestingFramework\Helper - */ -class EntityRESTApiHelper -{ - /** - * Integration admin token uri. - */ - const INTEGRATION_ADMIN_TOKEN_URI = '/rest/V1/integration/admin/token'; - - /** - * Application json header. - */ - const APPLICATION_JSON_HEADER = ['Content-Type' => 'application/json']; - - /** - * Rest API client. - * - * @var Client - */ - private $guzzle_client; - - /** - * EntityRESTApiHelper constructor. - * @param string $host - * @param string $port - */ - public function __construct($host, $port) - { - $this->guzzle_client = new Client([ - 'base_uri' => "http://{$host}:{$port}", - 'timeout' => 5.0, - ]); - } - - /** - * Submit Auth API Request. - * - * @param string $apiMethod - * @param string $requestURI - * @param string $jsonBody - * @param array $headers - * @return \Psr\Http\Message\ResponseInterface - */ - public function submitAuthAPIRequest($apiMethod, $requestURI, $jsonBody, $headers) - { - $allHeaders = $headers; - $authTokenVal = $this->getAuthToken(); - $authToken = ['Authorization' => 'Bearer ' . $authTokenVal]; - $allHeaders = array_merge($allHeaders, $authToken); - - return $this->submitAPIRequest($apiMethod, $requestURI, $jsonBody, $allHeaders); - } - - /** - * Function that sends a REST call to the integration endpoint for an authorization token. - * - * @return string - */ - private function getAuthToken() - { - $jsonArray = json_encode(['username' => 'admin', 'password' => 'admin123']); - - $response = $this->submitAPIRequest( - 'POST', - self::INTEGRATION_ADMIN_TOKEN_URI, - $jsonArray, - self::APPLICATION_JSON_HEADER - ); - - if ($response->getStatusCode() != 200) { - throwException($response->getReasonPhrase() .' Could not get admin token from service, please check logs.'); - } - - $authToken = str_replace('"', "", $response->getBody()->getContents()); - return $authToken; - } - - /** - * Function that submits an api request from the guzzle client using the following parameters: - * - * @param string $apiMethod - * @param string $requestURI - * @param string $jsonBody - * @param array $headers - * @return \Psr\Http\Message\ResponseInterface - */ - private function submitAPIRequest($apiMethod, $requestURI, $jsonBody, $headers) - { - $response = $this->guzzle_client->request( - $apiMethod, - $requestURI, - [ - 'headers' => $headers, - 'body' => $jsonBody - ] - ); - - return $response; - } -} diff --git a/src/Magento/FunctionalTestingFramework/Helper/Helper.php b/src/Magento/FunctionalTestingFramework/Helper/Helper.php new file mode 100644 index 000000000..e47aca32b --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Helper/Helper.php @@ -0,0 +1,15 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\FunctionalTestingFramework\Helper; + +/** + * Class Helper to abstract Codeception module class and make possible to add own functionality if needed. + */ +class Helper extends \Codeception\Module +{ + // +} diff --git a/src/Magento/FunctionalTestingFramework/Helper/HelperContainer.php b/src/Magento/FunctionalTestingFramework/Helper/HelperContainer.php new file mode 100644 index 000000000..f3b5852b8 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Helper/HelperContainer.php @@ -0,0 +1,66 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types = 1); + +namespace Magento\FunctionalTestingFramework\Helper; + +use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; + +/** + * Class HelperContainer + */ +class HelperContainer extends \Codeception\Module +{ + /** + * @var Helper[] + */ + private $helpers = []; + + /** + * Create custom helper class. + * + * @param string $helperClass + * @return Helper + * @throws \Exception + */ + public function create(string $helperClass): Helper + { + if (get_parent_class($helperClass) !== Helper::class) { + throw new \Exception("Helper class must extend " . Helper::class); + } + if (!isset($this->helpers[$helperClass])) { + $this->helpers[$helperClass] = $this->moduleContainer->create($helperClass); + } + + return $this->helpers[$helperClass]; + } + + /** + * Returns helper object by it's class name. + * + * @param string $className + * @return Helper + * @throws TestFrameworkException + */ + public function get(string $className): Helper + { + if ($this->has($className)) { + return $this->helpers[$className]; + } + throw new TestFrameworkException('Custom helper ' . $className . 'not found.'); + } + + /** + * Verifies that helper object exist. + * + * @param string $className + * @return boolean + */ + public function has(string $className): bool + { + return array_key_exists($className, $this->helpers); + } +} diff --git a/src/Magento/FunctionalTestingFramework/Helper/MagentoFakerData.php b/src/Magento/FunctionalTestingFramework/Helper/MagentoFakerData.php deleted file mode 100644 index 9c28a3531..000000000 --- a/src/Magento/FunctionalTestingFramework/Helper/MagentoFakerData.php +++ /dev/null @@ -1,122 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ - -namespace Magento\FunctionalTestingFramework\Helper; - -/** - * Class MagentoFakerData - */ -class MagentoFakerData extends \Codeception\Module -{ - /** - * Get Customer data. - * - * @param array $additional - * @return array - */ - public function getCustomerData(array $additional = []) - { - $faker = \Faker\Factory::create(); - $customerData = [ - 'prefix' => $faker->title, - 'firstname' => $faker->firstName, - 'middlename' => $faker->firstName, - 'lastname' => $faker->lastName, - 'suffix' => \Faker\Provider\en_US\Person::suffix(), - 'email' => $faker->email, - 'dateOfBirth' => $faker->date('m/d/Y', 'now'), - 'gender' => rand(0, 1), - 'group_id' => 1, - 'store_id' => 1, - 'website_id' => 1, - 'taxVatNumber' => \Faker\Provider\at_AT\Payment::vat(), - 'company' => $faker->company, - 'phoneNumber' => $faker->phoneNumber, - 'address' => [ - 'address1' => $faker->streetAddress, - 'address2' => $faker->streetAddress, - 'city' => $faker->city, - 'country' => 'United States', - 'state' => \Faker\Provider\en_US\Address::state(), - 'zipCode' => $faker->postcode - ] - ]; - return array_merge($customerData, $additional); - } - - /** - * Get category data. - * - * @return array - */ - public function getCategoryData() - { - $faker = \Faker\Factory::create(); - - return [ - 'enableCategory' => $faker->boolean(), - 'includeInMenu' => $faker->boolean(), - 'categoryName' => $faker->md5, - 'categoryImage' => '', - 'description' => $faker->sentence(10, true), - 'addCMSBlock' => '', - - 'urlKey' => $faker->uuid, - 'metaTitle' => $faker->word, - 'metaKeywords' => $faker->sentence(5, true), - 'metaDescription' => $faker->sentence(10, true), - ]; - } - - /** - * Get simple product data. - * - * @return array - */ - public function getProductData() - { - $faker = \Faker\Factory::create(); - return [ - 'enableProduct' => $faker->boolean(), - 'attributeSet' => '', - 'productName' => $faker->text(20), - 'sku' => \Faker\Provider\DateTime::unixTime('now'), - 'price' => $faker->randomFloat(2, 0, 999), - 'quantity' => $faker->numberBetween(1, 999), - - 'urlKey' => $faker->uuid, - 'metaTitle' => $faker->word, - 'metaKeywords' => $faker->sentence(5, true), - 'metaDescription' => $faker->sentence(10, true) - ]; - } - - /** - * Get Content Page Data. - * - * @return array - */ - public function getContentPage() - { - $faker = \Faker\Factory::create(); - - $pageContent = [ - 'pageTitle' => $faker->sentence(3, true), - 'contentHeading' => $faker->sentence(3, true), - 'contentBody' => $faker->sentence(10, true), - 'urlKey' => $faker->uuid, - 'metaTitle' => $faker->word, - 'metaKeywords' => $faker->sentence(5, true), - 'metaDescription' => $faker->sentence(10, true), - 'from' => $faker->date($format = 'm/d/Y', 'now'), - 'to' => $faker->date($format = 'm/d/Y') - ]; - $pageContent['layoutUpdateXml'] = "<note><to>Tove</to><from>Jani</from><heading>Reminder</heading>"; - $pageContent['layoutUpdateXml'] .= "<body>Don't forget me this weekend!</body></note>"; - - return $pageContent; - } -} diff --git a/src/Magento/FunctionalTestingFramework/Helper/views/TestInjectMethod.mustache b/src/Magento/FunctionalTestingFramework/Helper/views/TestInjectMethod.mustache new file mode 100644 index 000000000..36f2f8bf6 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Helper/views/TestInjectMethod.mustache @@ -0,0 +1,15 @@ + /** + * @var \Magento\FunctionalTestingFramework\Helper\HelperContainer + */ + private $helperContainer; + + /** + * Special method which automatically creates the respective objects. + */ + public function _inject(\Magento\FunctionalTestingFramework\Helper\HelperContainer $helperContainer) + { + $this->helperContainer = $helperContainer; + {{#arguments}} + $this->helperContainer->create("{{type}}"); + {{/arguments}} + } diff --git a/src/Magento/FunctionalTestingFramework/Module/MagentoActionProxies.php b/src/Magento/FunctionalTestingFramework/Module/MagentoActionProxies.php new file mode 100644 index 000000000..55b19a7c1 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Module/MagentoActionProxies.php @@ -0,0 +1,141 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\FunctionalTestingFramework\Module; + +use Codeception\Module as CodeceptionModule; +use Magento\FunctionalTestingFramework\DataGenerator\Handlers\PersistedObjectHandler; +use Magento\FunctionalTestingFramework\DataGenerator\Handlers\CredentialStore; +use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; + +/** + * Class MagentoActionProxies + * + * Contains all proxy functions whose corresponding MFTF actions need to be accessible for AcceptanceTester $I + * + * @package Magento\FunctionalTestingFramework\Module + */ +class MagentoActionProxies extends CodeceptionModule +{ + /** + * Create an entity + * + * @param string $key StepKey of the createData action. + * @param string $scope + * @param string $entity Name of xml entity to create. + * @param array $dependentObjectKeys StepKeys of other createData actions that are required. + * @param array $overrideFields Array of FieldName => Value of override fields. + * @param string $storeCode + * @return void + */ + public function createEntity( + $key, + $scope, + $entity, + $dependentObjectKeys = [], + $overrideFields = [], + $storeCode = '' + ) { + PersistedObjectHandler::getInstance()->createEntity( + $key, + $scope, + $entity, + $dependentObjectKeys, + $overrideFields, + $storeCode + ); + } + + /** + * Retrieves and updates a previously created entity + * + * @param string $key StepKey of the createData action. + * @param string $scope + * @param string $updateEntity Name of the static XML data to update the entity with. + * @param array $dependentObjectKeys StepKeys of other createData actions that are required. + * @return void + */ + public function updateEntity($key, $scope, $updateEntity, $dependentObjectKeys = []) + { + PersistedObjectHandler::getInstance()->updateEntity( + $key, + $scope, + $updateEntity, + $dependentObjectKeys + ); + } + + /** + * Performs GET on given entity and stores entity for use + * + * @param string $key StepKey of getData action. + * @param string $scope + * @param string $entity Name of XML static data to use. + * @param array $dependentObjectKeys StepKeys of other createData actions that are required. + * @param string $storeCode + * @param integer $index + * @return void + */ + public function getEntity($key, $scope, $entity, $dependentObjectKeys = [], $storeCode = '', $index = null) + { + PersistedObjectHandler::getInstance()->getEntity( + $key, + $scope, + $entity, + $dependentObjectKeys, + $storeCode, + $index + ); + } + + /** + * Retrieves and deletes a previously created entity + * + * @param string $key StepKey of the createData action. + * @param string $scope + * @return void + */ + public function deleteEntity($key, $scope) + { + PersistedObjectHandler::getInstance()->deleteEntity($key, $scope); + } + + /** + * Retrieves a field from an entity, according to key and scope given + * + * @param string $stepKey + * @param string $field + * @param string $scope + * @return string + */ + public function retrieveEntityField($stepKey, $field, $scope) + { + return PersistedObjectHandler::getInstance()->retrieveEntityField($stepKey, $field, $scope); + } + + /** + * Get encrypted value by key + * + * @param string $key + * @return string|null + * @throws TestFrameworkException + */ + public function getSecret($key) + { + return CredentialStore::getInstance()->getSecret($key); + } + + /** + * Returns a value to origin of the action + * + * @param mixed $value + * @return mixed + */ + public function return($value) + { + return $value; + } +} diff --git a/src/Magento/FunctionalTestingFramework/Module/MagentoAssert.php b/src/Magento/FunctionalTestingFramework/Module/MagentoAssert.php index 3c6c08173..78301ad6f 100644 --- a/src/Magento/FunctionalTestingFramework/Module/MagentoAssert.php +++ b/src/Magento/FunctionalTestingFramework/Module/MagentoAssert.php @@ -36,7 +36,7 @@ public function assertArrayIsSorted(array $data, $sortOrder = "asc") $data = array_map('strtolower', $data); } - if ($sortOrder == "asc") { + if ($sortOrder === 'asc') { for ($i = 1; $i < $elementTotal; $i++) { // $i >= $i-1 $this->assertLessThanOrEqual($data[$i], $data[$i-1], $message); diff --git a/src/Magento/FunctionalTestingFramework/Module/MagentoPwaWebDriver.php b/src/Magento/FunctionalTestingFramework/Module/MagentoPwaWebDriver.php deleted file mode 100644 index 9b75cb10d..000000000 --- a/src/Magento/FunctionalTestingFramework/Module/MagentoPwaWebDriver.php +++ /dev/null @@ -1,74 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ - -namespace Magento\FunctionalTestingFramework\Module; - -use Codeception\Module\WebDriver; - -/** - * Class MagentoPwaActions - * - * Contains all custom PWA action functions to be used in PWA tests. - * - * @package Magento\FunctionalTestingFramework\Module - */ -class MagentoPwaWebDriver extends MagentoWebDriver -{ - /** - * Go to the page. - * - * Overriding the MagentoWebDriver version because it contains 'waitForPageLoad'. - * The AJAX check in 'waitForPageLoad' does NOT work with a PWA. - * - * @param string $page - * @throws \Exception - * @return void - */ - public function amOnPage($page) - { - WebDriver::amOnPage($page); - } - - /** - * Wait for a PWA Element to NOT be visible using JavaScript. - * Add the WAIT_TIMEOUT variable to your .env file for this action. - * - * @param null $selector - * @param null $timeout - * @throws \Exception - * @return void - */ - public function waitForPwaElementNotVisible($selector, $timeout = null) - { - // Determine what type of Selector is used. - // Then use the correct JavaScript to locate the Element. - if (\Codeception\Util\Locator::isXPath($selector)) { - $this->waitForJS("return !document.evaluate(`$selector`, document);", $timeout); - } else { - $this->waitForJS("return !document.querySelector(`$selector`);", $timeout); - } - } - - /** - * Wait for a PWA Element to be visible using JavaScript. - * Add the WAIT_TIMEOUT variable to your .env file for this action. - * - * @param null $selector - * @param null $timeout - * @throws \Exception - * @return void - */ - public function waitForPwaElementVisible($selector, $timeout = null) - { - // Determine what type of Selector is used. - // Then use the correct JavaScript to locate the Element. - if (\Codeception\Util\Locator::isXPath($selector)) { - $this->waitForJS("return !!document && !!document.evaluate(`$selector`, document);", $timeout); - } else { - $this->waitForJS("return !!document && !!document.querySelector(`$selector`);", $timeout); - } - } -} diff --git a/src/Magento/FunctionalTestingFramework/Module/MagentoWebDriver.php b/src/Magento/FunctionalTestingFramework/Module/MagentoWebDriver.php index ac6e86c0a..4f240a3bd 100644 --- a/src/Magento/FunctionalTestingFramework/Module/MagentoWebDriver.php +++ b/src/Magento/FunctionalTestingFramework/Module/MagentoWebDriver.php @@ -6,6 +6,7 @@ namespace Magento\FunctionalTestingFramework\Module; +use Codeception\Lib\Actor\Shared\Pause; use Codeception\Module\WebDriver; use Codeception\Test\Descriptor; use Codeception\TestInterface; @@ -14,14 +15,20 @@ use Codeception\Exception\ModuleConfigException; use Codeception\Exception\ModuleException; use Codeception\Util\Uri; +use Codeception\Lib\ModuleContainer; +use Magento\FunctionalTestingFramework\DataTransport\WebApiExecutor; +use Magento\FunctionalTestingFramework\DataTransport\Auth\WebApiAuth; +use Magento\FunctionalTestingFramework\DataTransport\Auth\Tfa\OTP; +use Magento\FunctionalTestingFramework\DataTransport\Protocol\CurlInterface; use Magento\FunctionalTestingFramework\DataGenerator\Handlers\CredentialStore; -use Magento\FunctionalTestingFramework\DataGenerator\Persist\Curl\WebapiExecutor; -use Magento\FunctionalTestingFramework\Util\Protocol\CurlTransport; -use Magento\FunctionalTestingFramework\Util\Protocol\CurlInterface; +use Magento\FunctionalTestingFramework\Module\Util\ModuleUtils; +use Magento\FunctionalTestingFramework\Util\Path\UrlFormatter; use Magento\FunctionalTestingFramework\Util\ConfigSanitizerUtil; use Yandex\Allure\Adapter\AllureException; +use Magento\FunctionalTestingFramework\DataTransport\Protocol\CurlTransport; use Yandex\Allure\Adapter\Support\AttachmentSupport; use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; +use Magento\FunctionalTestingFramework\DataGenerator\Handlers\PersistedObjectHandler; /** * MagentoWebDriver module provides common Magento web actions through Selenium WebDriver. @@ -42,21 +49,33 @@ * ``` * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + * @SuppressWarnings(PHPMD.ExcessivePublicCount) + * @SuppressWarnings(PHPMD.ExcessiveClassComplexity) */ class MagentoWebDriver extends WebDriver { use AttachmentSupport; + use Pause { + pause as codeceptPause; + } + + const MAGENTO_CRON_INTERVAL = 60; + const MAGENTO_CRON_COMMAND = 'cron:run'; /** * List of known magento loading masks by selector + * * @var array */ - public static $loadingMasksLocators = [ + protected $loadingMasksLocators = [ '//div[contains(@class, "loading-mask")]', '//div[contains(@class, "admin_data-grid-loading-mask")]', '//div[contains(@class, "admin__data-grid-loading-mask")]', '//div[contains(@class, "admin__form-loading-mask")]', - '//div[@data-role="spinner"]' + '//div[@data-role="spinner"]', + '//div[contains(@class,"file-uploader-spinner")]', + '//div[contains(@class,"image-uploader-spinner")]', + '//div[contains(@class,"uploader")]//div[@class="file-row"]', ]; /** @@ -69,7 +88,7 @@ class MagentoWebDriver extends WebDriver 'backend_name', 'username', 'password', - 'browser' + 'browser', ]; /** @@ -114,8 +133,16 @@ class MagentoWebDriver extends WebDriver */ private $jsErrors = []; + /** + * Contains last execution times for Cron + * + * @var int[] + */ + private $cronExecution = []; + /** * Sanitizes config, then initializes using parent. + * * @return void */ public function _initialize() @@ -139,6 +166,7 @@ public function _resetConfig() /** * Remap parent::_after, called in TestContextExtension + * * @param TestInterface $test * @return void */ @@ -149,21 +177,32 @@ public function _runAfter(TestInterface $test) /** * Override parent::_after to do nothing. - * @return void + * * @param TestInterface $test * @SuppressWarnings(PHPMD) + * @return void */ public function _after(TestInterface $test) { // DO NOT RESET SESSIONS } + /** + * Return ModuleContainer + * + * @return ModuleContainer + */ + public function getModuleContainer() + { + return $this->moduleContainer; + } + /** * Returns URL of a host. * - * @api * @return mixed * @throws ModuleConfigException + * @api */ public function _getUrl() { @@ -173,6 +212,7 @@ public function _getUrl() "Module connection failure. The URL for client can't bre retrieved" ); } + return $this->config['url']; } @@ -180,15 +220,16 @@ public function _getUrl() * Uri of currently opened page. * * @return string - * @api * @throws ModuleException + * @api */ public function _getCurrentUri() { $url = $this->webDriver->getCurrentURL(); - if ($url == 'about:blank') { + if ($url === 'about:blank') { throw new ModuleException($this, 'Current url is blank, no page was opened'); } + return Uri::retrieveUri($url); } @@ -234,7 +275,7 @@ public function dontSeeInCurrentUrl($needle) $actualUrl = $this->webDriver->getCurrentURL(); $comparison = "Expected: $needle\nActual: $actualUrl"; AllureHelper::addAttachmentToCurrentStep($comparison, 'Comparison'); - $this->assertNotContains($needle, $actualUrl); + $this->assertStringNotContainsString($needle, $actualUrl); } /** @@ -257,6 +298,7 @@ public function grabFromCurrentUrl($regex = null) if (!isset($matches[1])) { $this->fail("Nothing to grab. A regex parameter with a capture group is required. Ex: '/(foo)(bar)/'"); } + return $matches[1]; } @@ -302,7 +344,7 @@ public function seeInCurrentUrl($needle) $actualUrl = $this->webDriver->getCurrentURL(); $comparison = "Expected: $needle\nActual: $actualUrl"; AllureHelper::addAttachmentToCurrentStep($comparison, 'Comparison'); - $this->assertContains($needle, $actualUrl); + $this->assertStringContainsString(urldecode($needle), urldecode($actualUrl)); } /** @@ -326,13 +368,13 @@ public function closeAdminNotification() * @param string $select * @param array $options * @param boolean $requireAction - * @throws \Exception * @return void + * @throws \Exception */ public function searchAndMultiSelectOption($select, array $options, $requireAction = false) { - $selectDropdown = $select . ' .action-select.admin__action-multiselect'; - $selectSearchText = $select + $selectDropdown = $select . ' .action-select.admin__action-multiselect'; + $selectSearchText = $select . ' .admin__action-multiselect-search-wrap>input[data-role="advanced-select-text"]'; $selectSearchResult = $select . ' .admin__action-multiselect-label>span'; @@ -355,8 +397,8 @@ public function searchAndMultiSelectOption($select, array $options, $requireActi * @param string $selectSearchTextField * @param string $selectSearchResult * @param string[] $options - * @throws \Exception * @return void + * @throws \Exception */ public function selectMultipleOptions($selectSearchTextField, $selectSearchResult, array $options) { @@ -393,8 +435,8 @@ public function waitForAjaxLoad($timeout = null) * Wait for all JavaScript to finish executing. * * @param integer $timeout - * @throws \Exception * @return void + * @throws \Exception */ public function waitForPageLoad($timeout = null) { @@ -409,12 +451,14 @@ public function waitForPageLoad($timeout = null) * Wait for all visible loading masks to disappear. Gets all elements by mask selector, then loops over them. * * @param integer $timeout - * @throws \Exception * @return void + * @throws \Exception */ public function waitForLoadingMaskToDisappear($timeout = null) { - foreach (self::$loadingMasksLocators as $maskLocator) { + $timeout = $timeout ?? $this->_getConfig()['pageload_timeout']; + + foreach ($this->loadingMasksLocators as $maskLocator) { // Get count of elements found for looping. // Elements are NOT useful for interaction, as they cannot be fed to codeception actions. $loadingMaskElements = $this->_findElements($maskLocator); @@ -427,18 +471,26 @@ public function waitForLoadingMaskToDisappear($timeout = null) } /** - * @param float $money + * Format input to specified currency in locale specified + * @link https://php.net/manual/en/numberformatter.formatcurrency.php + * + * @param float $value * @param string $locale - * @return array + * @param string $currency + * @return string + * @throws TestFrameworkException */ - public function formatMoney(float $money, $locale = 'en_US.UTF-8') + public function formatCurrency(float $value, $locale, $currency) { - $this->mSetLocale(LC_MONETARY, $locale); - $money = money_format('%.2n', $money); - $this->mResetLocale(); - $prefix = substr($money, 0, 1); - $number = substr($money, 1); - return ['prefix' => $prefix, 'number' => $number]; + $formatter = \NumberFormatter::create($locale, \NumberFormatter::CURRENCY); + if ($formatter && !empty($formatter)) { + $result = $formatter->formatCurrency($value, $currency); + if ($result) { + return $result; + } + } + + throw new TestFrameworkException('Invalid attributes used in formatCurrency.'); } /** @@ -450,6 +502,7 @@ public function formatMoney(float $money, $locale = 'en_US.UTF-8') public function parseFloat($floatString) { $floatString = str_replace(',', '', $floatString); + return floatval($floatString); } @@ -460,7 +513,7 @@ public function parseFloat($floatString) */ public function mSetLocale(int $category, $locale) { - if (self::$localeAll[$category] == $locale) { + if (self::$localeAll[$category] === $locale) { return; } foreach (self::$localeAll as $c => $l) { @@ -471,6 +524,7 @@ public function mSetLocale(int $category, $locale) /** * Reset Locale setting. + * * @return void */ public function mResetLocale() @@ -485,6 +539,7 @@ public function mResetLocale() /** * Scroll to the Top of the Page. + * * @return void */ public function scrollToTopOfPage() @@ -493,51 +548,135 @@ public function scrollToTopOfPage() } /** - * Takes given $command and executes it against exposed MTF CLI entry point. Returns response from server. - * @param string $command - * @param string $arguments - * @throws TestFrameworkException + * Takes given $command and executes it against bin/magento or custom exposed entrypoint. Returns command output. + * + * @param string $command + * @param integer $timeout + * @param string $arguments * @return string + * + * @throws TestFrameworkException */ - public function magentoCLI($command, $arguments = null) + public function magentoCLI($command, $timeout = null, $arguments = null) { // Remove index.php if it's present in url $baseUrl = rtrim( str_replace('index.php', '', rtrim($this->config['url'], '/')), '/' ); - $apiURL = $baseUrl . '/' . ltrim(getenv('MAGENTO_CLI_COMMAND_PATH'), '/'); - $restExecutor = new WebapiExecutor(); + $apiURL = UrlFormatter::format( + $baseUrl . '/' . ltrim(getenv('MAGENTO_CLI_COMMAND_PATH'), '/'), + false + ); + $executor = new CurlTransport(); $executor->write( $apiURL, [ - 'token' => $restExecutor->getAuthToken(), - getenv('MAGENTO_CLI_COMMAND_PARAMETER') => $command, - 'arguments' => $arguments + 'token' => WebApiAuth::getAdminToken(), + getenv('MAGENTO_CLI_COMMAND_PARAMETER') => urlencode($command), + 'arguments' => $arguments, + 'timeout' => $timeout, ], CurlInterface::POST, [] ); $response = $executor->read(); - $restExecutor->close(); $executor->close(); - return $response; + + $util = new ModuleUtils(); + $response = trim($util->utf8SafeControlCharacterTrim($response)); + return $response != "" ? $response : "CLI did not return output."; + } + + /** + * Executes Magento Cron keeping the interval (> 60 seconds between each run) + * + * @param string|null $cronGroups + * @param integer|null $timeout + * @param string|null $arguments + * @return string + */ + public function magentoCron($cronGroups = null, $timeout = null, $arguments = null) + { + $cronGroups = explode(' ', $cronGroups); + return $this->executeCronjobs($cronGroups, $timeout, $arguments); + } + + /** + * Updates last execution time for Cron + * + * @param array $cronGroups + * @return void + */ + private function notifyCronFinished(array $cronGroups = []) + { + if (empty($cronGroups)) { + $this->cronExecution['*'] = time(); + } + + foreach ($cronGroups as $group) { + $this->cronExecution[$group] = time(); + } + } + + /** + * Returns last Cron execution time for specific cron or all crons + * + * @param array $cronGroups + * @return integer + */ + private function getLastCronExecution(array $cronGroups = []) + { + if (empty($this->cronExecution)) { + return 0; + } + + if (empty($cronGroups)) { + return (int)max($this->cronExecution); + } + + $cronGroups = array_merge($cronGroups, ['*']); + + return array_reduce($cronGroups, function ($lastExecution, $group) { + if (isset($this->cronExecution[$group]) && $this->cronExecution[$group] > $lastExecution) { + $lastExecution = $this->cronExecution[$group]; + } + + return (int)$lastExecution; + }, 0); + } + + /** + * Returns time to wait for next run + * + * @param array $cronGroups + * @param integer $cronInterval + * @return integer + */ + private function getCronWait(array $cronGroups = [], int $cronInterval = self::MAGENTO_CRON_INTERVAL) + { + $nextRun = $this->getLastCronExecution($cronGroups) + $cronInterval; + $toNextRun = $nextRun - time(); + + return max(0, $toNextRun); } /** * Runs DELETE request to delete a Magento entity against the url given. + * * @param string $url - * @throws TestFrameworkException * @return string + * @throws TestFrameworkException */ public function deleteEntityByUrl($url) { - $executor = new WebapiExecutor(null); + $executor = new WebApiExecutor(null); $executor->write($url, [], CurlInterface::DELETE, []); $response = $executor->read(); $executor->close(); + return $response; } @@ -547,8 +686,8 @@ public function deleteEntityByUrl($url) * @param string $selector * @param string $dependentSelector * @param boolean $visible - * @throws \Exception * @return void + * @throws \Exception */ public function conditionalClick($selector, $dependentSelector, $visible) { @@ -597,12 +736,13 @@ public function assertElementContainsAttribute($selector, $attribute, $value) // When an "attribute" is blank or null it returns "true" so we assert that "true" is present. $this->assertEquals($attributes, 'true'); } else { - $this->assertContains($value, $attributes); + $this->assertStringContainsString($value, $attributes); } } /** * Sets current test to the given test, and resets test failure artifacts to null + * * @param TestInterface $test * @return void */ @@ -617,6 +757,7 @@ public function _before(TestInterface $test) /** * Override for codeception's default dragAndDrop to include offset options. + * * @param string $source * @param string $target * @param integer $xOffset @@ -625,26 +766,81 @@ public function _before(TestInterface $test) */ public function dragAndDrop($source, $target, $xOffset = null, $yOffset = null) { + $snodes = $this->matchFirstOrFail($this->baseElement, $source); + $tnodes = $this->matchFirstOrFail($this->baseElement, $target); + $action = new WebDriverActions($this->webDriver); if ($xOffset !== null || $yOffset !== null) { - $snodes = $this->matchFirstOrFail($this->baseElement, $source); - $tnodes = $this->matchFirstOrFail($this->baseElement, $target); - $targetX = intval($tnodes->getLocation()->getX() + $xOffset); $targetY = intval($tnodes->getLocation()->getY() + $yOffset); - $travelX = intval($targetX - $snodes->getLocation()->getX()); $travelY = intval($targetY - $snodes->getLocation()->getY()); - - $action = new WebDriverActions($this->webDriver); - $action->moveToElement($snodes)->perform(); - $action->clickAndHold($snodes)->perform(); - $action->moveByOffset($travelX, $travelY)->perform(); + $action->moveToElement($snodes); + $action->clickAndHold($snodes); + // Fix Start + $action->moveByOffset(-1, -1); + $action->moveByOffset(1, 1); + // Fix End + $action->moveByOffset($travelX, $travelY); $action->release()->perform(); } else { - parent::dragAndDrop($source, $target); + $action->clickAndHold($snodes); + // Fix Start + $action->moveByOffset(-1, -1); + $action->moveByOffset(1, 1); + // Fix End + $action->moveToElement($tnodes); + $action->release($tnodes)->perform(); + } + } + + /** + * Simple rapid click as per given count number. + * + * @param string $selector + * @param string $count + * @return void + * @throws \Exception + */ + public function rapidClick($selector, $count) + { + for ($i = 0; $i < $count; $i++) { + $this->click($selector); } } + /** + * Grabs a cookie attributes value. + * You can set additional cookie params like `domain`, `path` in array passed as last argument. + * If the cookie is set by an ajax request (XMLHttpRequest), + * there might be some delay caused by the browser, so try `$I->wait(0.1)`. + * @param string $cookie + * @param array $params + * @return mixed + */ + public function grabCookieAttributes(string $cookie, array $params = []): array + { + $params['name'] = $cookie; + $cookieArrays = $this->filterCookies($this->webDriver->manage()->getCookies(), $params); + $cookieAttributes = []; + if (is_array($cookieArrays)) { // Microsoft Edge returns null if there are no cookies... + foreach ($cookieArrays as $cookieArray) { + if ($cookieArray->getName() === $cookie) { + $cookieAttributes['name'] = $cookieArray->getValue(); + $cookieAttributes['path'] = $cookieArray->getPath(); + $cookieAttributes['domain'] = $cookieArray->getDomain(); + $cookieAttributes['secure'] = $cookieArray->isSecure(); + $cookieAttributes['httpOnly'] = $cookieArray->isHttpOnly(); + $cookieAttributes['sameSite'] = $cookieArray->getSameSite(); + $cookieAttributes['expiry'] = date('d/m/Y', $cookieArray->getExpiry()); + + return $cookieAttributes; + } + } + } + + return $cookieAttributes; + } + /** * Function used to fill sensitive credentials with user data, data is decrypted immediately prior to fill to avoid * exposure in console or log. @@ -660,6 +856,9 @@ public function fillSecretField($field, $value) // decrypted value $decryptedValue = CredentialStore::getInstance()->decryptSecretValue($value); + if ($decryptedValue === false) { + throw new TestFrameworkException("\nFailed to decrypt value {$value} for field {$field}\n"); + } $this->fillField($field, $decryptedValue); } @@ -668,17 +867,21 @@ public function fillSecretField($field, $value) * The data is decrypted immediately prior to data creation to avoid exposure in console or log. * * @param string $command + * @param null $timeout * @param null $arguments * @throws TestFrameworkException * @return string */ - public function magentoCLISecret($command, $arguments = null) + public function magentoCLISecret($command, $timeout = null, $arguments = null) { // to protect any secrets from being printed to console the values are executed only at the webdriver level as a // decrypted value $decryptedCommand = CredentialStore::getInstance()->decryptAllSecretsInString($command); - return $this->magentoCLI($decryptedCommand, $arguments); + if ($decryptedCommand === false) { + throw new TestFrameworkException("\nFailed to decrypt magentoCLI command {$command}\n"); + } + return $this->magentoCLI($decryptedCommand, $timeout, $arguments); } /** @@ -695,9 +898,12 @@ public function _failed(TestInterface $test, $fail) if ($this->pngReport === null && $this->htmlReport === null) { $this->saveScreenshot(); + if (getenv('ENABLE_PAUSE') === 'true') { + $this->pause(true); + } } - if ($this->current_test == null) { + if ($this->current_test === null) { throw new \RuntimeException("Suite condition failure: \n" . $fail->getMessage()); } @@ -711,12 +917,13 @@ public function _failed(TestInterface $test, $fail) /** * Function which saves a screenshot of the current stat of the browser + * * @return void */ public function saveScreenshot() { $testDescription = "unknown." . uniqid(); - if ($this->current_test != null) { + if ($this->current_test !== null) { $testDescription = Descriptor::getTestSignature($this->current_test); } @@ -730,27 +937,15 @@ public function saveScreenshot() * Go to a page and wait for ajax requests to finish * * @param string $page - * @throws \Exception * @return void + * @throws \Exception */ public function amOnPage($page) { - parent::amOnPage($page); + (0 === strpos($page, 'http')) ? parent::amOnUrl($page) : parent::amOnPage($page); $this->waitForPageLoad(); } - /** - * Turn Readiness check on or off - * - * @param boolean $check - * @throws \Exception - * @return void - */ - public function skipReadinessCheck($check) - { - $this->config['skipReadiness'] = $check; - } - /** * Clean Javascript errors in internal array * @@ -787,6 +982,7 @@ private function getJsErrors() $errors .= "\n" . $jsError; } } + return $errors; } @@ -824,4 +1020,94 @@ public function makeScreenshot($name = null) $this->debug("Screenshot saved to $screenName"); AllureHelper::addAttachmentToCurrentStep($screenName, 'Screenshot'); } + + /** + * Return OTP based on a shared secret + * + * @param string|null $secretsPath + * @return string + * @throws TestFrameworkException + */ + public function getOTP($secretsPath = null) + { + return OTP::getOTP($secretsPath); + } + + /** + * Waits proper amount of time to perform Cron execution + * + * @param array $cronGroups + * @param integer $timeout + * @param string $arguments + * @return string + * @throws TestFrameworkException + */ + private function executeCronjobs($cronGroups, $timeout, $arguments): string + { + $cronGroups = array_filter($cronGroups); + + $waitFor = $this->getCronWait($cronGroups); + + if ($waitFor) { + $this->wait($waitFor); + } + + $command = array_reduce($cronGroups, function ($command, $cronGroup) { + $command .= ' --group=' . $cronGroup; + return $command; + }, self::MAGENTO_CRON_COMMAND); + $timeStart = microtime(true); + $cronResult = $this->magentoCLI($command, $timeout, $arguments); + $timeEnd = microtime(true); + + $this->notifyCronFinished($cronGroups); + + return sprintf('%s (wait: %ss, execution: %ss)', $cronResult, $waitFor, round($timeEnd - $timeStart, 2)); + } + + /** + * Switch to another frame on the page by name, ID, CSS or XPath. + * + * @param string|null $locator + * @return void + * @throws \Exception + */ + public function switchToIFrame($locator = null) + { + try { + parent::switchToIFrame($locator); + } catch (\Exception $e) { + $els = $this->_findElements("#$locator"); + if (!count($els)) { + $this->debug('Failed to find locator by ID: ' . $e->getMessage()); + throw new \Exception("IFrame with $locator was not found."); + } + $this->webDriver->switchTo()->frame($els[0]); + } + } + + /** + * Invoke Codeption pause() + * + * @param boolean $pauseOnFail + * @return void + */ + public function pause($pauseOnFail = false) + { + if (\Composer\InstalledVersions::isInstalled('hoa/console') === false) { + $message = "<pause /> action is unavailable." . PHP_EOL; + $message .= "Please install `hoa/console` via \"composer require hoa/console\"" . PHP_EOL; + print($message); + return; + } + if (!\Codeception\Util\Debug::isEnabled()) { + return; + } + + if ($pauseOnFail) { + print(PHP_EOL . "Failure encountered. Pausing execution..." . PHP_EOL . PHP_EOL); + } + + $this->codeceptPause(); + } } diff --git a/src/Magento/FunctionalTestingFramework/Module/MagentoWebDriverDoctor.php b/src/Magento/FunctionalTestingFramework/Module/MagentoWebDriverDoctor.php new file mode 100644 index 000000000..1407c957b --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Module/MagentoWebDriverDoctor.php @@ -0,0 +1,168 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\FunctionalTestingFramework\Module; + +use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; +use Facebook\WebDriver\Remote\RemoteWebDriver; + +/** + * MagentoWebDriverDoctor module extends MagentoWebDriver module and is a light weighted module to diagnose webdriver + * initialization and other setup issues. It uses in memory version of MagentoWebDriver's configuration file. + */ +class MagentoWebDriverDoctor extends MagentoWebDriver +{ + const MAGENTO_CLI_COMMAND = 'info:currency:list'; + const EXCEPTION_CONTEXT_SELENIUM = 'selenium'; + const EXCEPTION_CONTEXT_ADMIN = 'admin'; + const EXCEPTION_CONTEXT_STOREFRONT = 'store'; + const EXCEPTION_CONTEXT_CLI = 'cli'; + + /** + * Remote Web Driver + * + * @var RemoteWebDriver + */ + private $remoteWebDriver = null; + + /** + * Go through parent initialization routines and in addition diagnose potential environment issues + * + * @return void + * @throws TestFrameworkException + */ + public function _initialize() + { + parent::_initialize(); + + $context = []; + + try { + $this->connectToSeleniumServer(); + } catch (TestFrameworkException $e) { + $context[self::EXCEPTION_CONTEXT_SELENIUM] = $e->getMessage(); + } + + try { + $adminUrl = rtrim(getenv('MAGENTO_BACKEND_BASE_URL'), '/') + ?: rtrim(getenv('MAGENTO_BASE_URL'), '/') + . '/' . getenv('MAGENTO_BACKEND_NAME') . '/admin'; + $this->loadPageAtUrl($adminUrl); + } catch (\Exception $e) { + $context[self::EXCEPTION_CONTEXT_ADMIN] = $e->getMessage(); + } + + try { + $storeUrl = getenv('MAGENTO_BASE_URL'); + $this->loadPageAtUrl($storeUrl); + } catch (\Exception $e) { + $context[self::EXCEPTION_CONTEXT_STOREFRONT] = $e->getMessage(); + } + + try { + $this->runMagentoCLI(); + } catch (\Exception $e) { + $context[self::EXCEPTION_CONTEXT_CLI] = $e->getMessage(); + } + + if (null !== $this->remoteWebDriver) { + $this->remoteWebDriver->close(); + } + + if (!empty($context)) { + throw new TestFrameworkException('Exception occurred in MagentoWebDriverDoctor', $context); + } + } + + /** + * Check connecting to running selenium server + * + * @return void + * @throws TestFrameworkException + */ + private function connectToSeleniumServer() + { + try { + $this->remoteWebDriver = RemoteWebDriver::create( + $this->wdHost, + $this->capabilities, + $this->connectionTimeoutInMs, + $this->requestTimeoutInMs, + $this->httpProxy, + $this->httpProxyPort + ); + if (null !== $this->remoteWebDriver) { + return; + } + } catch (\Exception $e) { + } + + throw new TestFrameworkException( + "Failed to connect Selenium WebDriver at: {$this->wdHost}.\n" + . "Please make sure that Selenium Server is running." + ); + } + + /** + * Validate loading a web page at url in the browser controlled by selenium + * + * @param string $url + * @return void + * @throws TestFrameworkException + */ + private function loadPageAtUrl($url) + { + try { + if (null !== $this->remoteWebDriver) { + // Open the web page at url first + $this->remoteWebDriver->get($url); + + // Execute Javascript to retrieve HTTP response code + $script = '' + . 'var xhr = new XMLHttpRequest();' + . "xhr.open('GET', '" . $url . "', false);" + . 'xhr.send(null); ' + . 'return xhr.status'; + $status = $this->remoteWebDriver->executeScript($script); + + if ($status === 200) { + return; + } + } + } catch (\Exception $e) { + } + + throw new TestFrameworkException( + "Failed to load page at url: $url\n" + . "Please check Selenium Browser session have access to Magento instance." + ); + } + + /** + * Check running Magento CLI command + * + * @return void + * @throws TestFrameworkException + */ + private function runMagentoCLI() + { + try { + $regex = '~^.*[\r\n]+.*(?<name>Currency).*(?<code>Code).*~'; + $output = parent::magentoCLI(self::MAGENTO_CLI_COMMAND); + preg_match($regex, $output, $matches); + + if (isset($matches['name']) && isset($matches['code'])) { + return; + } + } catch (\Exception $e) { + } + + throw new TestFrameworkException( + "Failed to run Magento CLI command\n" + . "Please reference Magento DevDoc to setup command.php and .htaccess files." + ); + } +} diff --git a/src/Magento/FunctionalTestingFramework/Module/Util/ModuleUtils.php b/src/Magento/FunctionalTestingFramework/Module/Util/ModuleUtils.php new file mode 100644 index 000000000..d9bc203c4 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Module/Util/ModuleUtils.php @@ -0,0 +1,36 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\FunctionalTestingFramework\Module\Util; + +class ModuleUtils +{ + /** + * Module util function that returns UTF-8 encoding string with control/invisible characters removed, + * and it returns the original string when on error. + * + * @param string $input + * @return string + */ + public function utf8SafeControlCharacterTrim(string $input): string + { + // Convert $input string to UTF-8 encoding + $convInput = iconv("ISO-8859-1", "UTF-8//IGNORE", $input); + if ($convInput !== false) { + // Remove invisible control characters, unused code points and replacement character + // so that they don't break xml test results for Allure + $cleanInput = preg_replace('/[^\PC\s]|\x{FFFD}/u', '', $convInput); + if ($cleanInput !== null) { + return $cleanInput; + } else { + $err = preg_last_error_msg(); + print("MagentoCLI response preg_replace() with error $err.\n"); + } + } + + return $input; + } +} diff --git a/src/Magento/FunctionalTestingFramework/ObjectManager/Config/Mapper/Dom.php b/src/Magento/FunctionalTestingFramework/ObjectManager/Config/Mapper/Dom.php index ba7bc64cd..99912355d 100644 --- a/src/Magento/FunctionalTestingFramework/ObjectManager/Config/Mapper/Dom.php +++ b/src/Magento/FunctionalTestingFramework/ObjectManager/Config/Mapper/Dom.php @@ -54,7 +54,7 @@ public function convert($config) $output = []; /** @var \DOMNode $node */ foreach ($config->documentElement->childNodes as $node) { - if ($node->nodeType != XML_ELEMENT_NODE) { + if ($node->nodeType !== XML_ELEMENT_NODE) { continue; } switch ($node->nodeName) { @@ -73,7 +73,7 @@ public function convert($config) if ($typeNodeShared) { $typeData['shared'] = $this->booleanUtils->toBoolean($typeNodeShared->nodeValue); } - if ($node->nodeName == 'virtualType') { + if ($node->nodeName === 'virtualType') { $attributeType = $typeNodeAttributes->getNamedItem('type'); // attribute type is required for virtual type only in merged configuration if ($attributeType) { @@ -102,14 +102,14 @@ private function setTypeArguments($node) foreach ($node->childNodes as $typeChildNode) { /** @var \DOMNode $typeChildNode */ - if ($typeChildNode->nodeType != XML_ELEMENT_NODE) { + if ($typeChildNode->nodeType !== XML_ELEMENT_NODE) { continue; } switch ($typeChildNode->nodeName) { case 'arguments': /** @var \DOMNode $argumentNode */ foreach ($typeChildNode->childNodes as $argumentNode) { - if ($argumentNode->nodeType != XML_ELEMENT_NODE) { + if ($argumentNode->nodeType !== XML_ELEMENT_NODE) { continue; } $argumentName = $argumentNode->attributes->getNamedItem('name')->nodeValue; diff --git a/src/Magento/FunctionalTestingFramework/ObjectManager/Factory.php b/src/Magento/FunctionalTestingFramework/ObjectManager/Factory.php index c5882d035..922235011 100644 --- a/src/Magento/FunctionalTestingFramework/ObjectManager/Factory.php +++ b/src/Magento/FunctionalTestingFramework/ObjectManager/Factory.php @@ -84,7 +84,8 @@ public function prepareArguments($object, $method, array $arguments = []) { $type = get_class($object); $parameters = $this->classReader->getParameters($type, $method); - if ($parameters == null) { + + if ($parameters === null) { return []; } @@ -227,7 +228,8 @@ public function create($requestedType, array $arguments = []) { $instanceType = $this->config->getInstanceType($requestedType); $parameters = $this->definitions->getParameters($instanceType); - if ($parameters == null) { + + if ($parameters === null) { return new $instanceType(); } if (isset($this->creationStack[$requestedType])) { diff --git a/src/Magento/FunctionalTestingFramework/ObjectManager/Factory/Dynamic/Developer.php b/src/Magento/FunctionalTestingFramework/ObjectManager/Factory/Dynamic/Developer.php index 937be7da6..0d6623d09 100644 --- a/src/Magento/FunctionalTestingFramework/ObjectManager/Factory/Dynamic/Developer.php +++ b/src/Magento/FunctionalTestingFramework/ObjectManager/Factory/Dynamic/Developer.php @@ -178,7 +178,8 @@ public function create($requestedType, array $arguments = []) { $type = $this->config->getInstanceType($requestedType); $parameters = $this->definitions->getParameters($type); - if ($parameters == null) { + + if ($parameters === null) { return new $type(); } if (isset($this->creationStack[$requestedType])) { diff --git a/src/Magento/FunctionalTestingFramework/ObjectManager/ObjectHandlerInterface.php b/src/Magento/FunctionalTestingFramework/ObjectManager/ObjectHandlerInterface.php index 1bc79cbbb..8bde3b96d 100644 --- a/src/Magento/FunctionalTestingFramework/ObjectManager/ObjectHandlerInterface.php +++ b/src/Magento/FunctionalTestingFramework/ObjectManager/ObjectHandlerInterface.php @@ -11,6 +11,8 @@ */ interface ObjectHandlerInterface { + const OBJ_DEPRECATED = 'deprecated'; + /** * Function to enforce singleton design pattern * @@ -22,7 +24,7 @@ public static function getInstance(); * Function to return a single object by name * * @param string $objectName - * @return object + * @return mixed */ public function getObject($objectName); diff --git a/src/Magento/FunctionalTestingFramework/Page/Config/Dom.php b/src/Magento/FunctionalTestingFramework/Page/Config/Dom.php index 989209461..14b9b65e5 100644 --- a/src/Magento/FunctionalTestingFramework/Page/Config/Dom.php +++ b/src/Magento/FunctionalTestingFramework/Page/Config/Dom.php @@ -12,6 +12,7 @@ use Magento\FunctionalTestingFramework\Config\Dom\NodePathMatcher; use Magento\FunctionalTestingFramework\Util\ModulePathExtractor; use Magento\FunctionalTestingFramework\Util\Validation\DuplicateNodeValidationUtil; +use Magento\FunctionalTestingFramework\Util\Validation\SingleNodePerFileValidationUtil; /** * MFTF page.xml configuration XML DOM utility @@ -36,6 +37,12 @@ class Dom extends \Magento\FunctionalTestingFramework\Config\MftfDom */ private $validationUtil; + /** SingleNodePerFileValidationUtil + * + * @var SingleNodePerFileValidationUtil + */ + private $singleNodePerFileValidationUtil; + /** * Page Dom constructor. * @param string $xml @@ -57,6 +64,7 @@ public function __construct( ) { $this->modulePathExtractor = new ModulePathExtractor(); $this->validationUtil = new DuplicateNodeValidationUtil('name', $exceptionCollector); + $this->singleNodePerFileValidationUtil = new SingleNodePerFileValidationUtil($exceptionCollector); parent::__construct( $xml, $filename, @@ -79,31 +87,44 @@ public function initDom($xml, $filename = null) { $dom = parent::initDom($xml, $filename); - $pagesNode = $dom->getElementsByTagName('pages')->item(0); - $this->validationUtil->validateChildUniqueness( - $pagesNode, - $filename, - $pagesNode->getAttribute(self::PAGE_META_NAME_ATTRIBUTE) - ); - $pageNodes = $dom->getElementsByTagName('page'); - $currentModule = - $this->modulePathExtractor->getExtensionPath($filename) - . '_' - . $this->modulePathExtractor->extractModuleName($filename); - foreach ($pageNodes as $pageNode) { - $pageModule = $pageNode->getAttribute("module"); - $pageName = $pageNode->getAttribute("name"); - if ($pageModule !== $currentModule) { - if (MftfApplicationConfig::getConfig()->verboseEnabled()) { - print( - "Page Module does not match path Module. " . - "(Page, Module): ($pageName, $pageModule) - Path Module: $currentModule" . - PHP_EOL - ); + if ($dom->getElementsByTagName('pages')->length > 0) { + /** @var \DOMElement $pagesNode */ + $pagesNode = $dom->getElementsByTagName('pages')[0]; + $this->validationUtil->validateChildUniqueness( + $pagesNode, + $filename, + $pagesNode->getAttribute(self::PAGE_META_NAME_ATTRIBUTE) + ); + + // Validate single page node per file + $this->singleNodePerFileValidationUtil->validateSingleNodeForTag( + $dom, + 'page', + $filename + ); + + if ($dom->getElementsByTagName('page')->length > 0) { + /** @var \DOMElement $pageNode */ + $pageNode = $dom->getElementsByTagName('page')[0]; + $currentModule = + $this->modulePathExtractor->getExtensionPath($filename) + . '_' + . $this->modulePathExtractor->extractModuleName($filename); + $pageModule = $pageNode->getAttribute("module"); + $pageName = $pageNode->getAttribute("name"); + if ($pageModule !== $currentModule) { + if (MftfApplicationConfig::getConfig()->verboseEnabled()) { + print( + "Page Module does not match path Module. " . + "(Page, Module): ($pageName, $pageModule) - Path Module: $currentModule" . + PHP_EOL + ); + } } + $pageNode->setAttribute(self::PAGE_META_FILENAME_ATTRIBUTE, $filename); } - $pageNode->setAttribute(self::PAGE_META_FILENAME_ATTRIBUTE, $filename); } + return $dom; } } diff --git a/src/Magento/FunctionalTestingFramework/Page/Config/SectionDom.php b/src/Magento/FunctionalTestingFramework/Page/Config/SectionDom.php index b7f5d5db9..5875f0ac1 100644 --- a/src/Magento/FunctionalTestingFramework/Page/Config/SectionDom.php +++ b/src/Magento/FunctionalTestingFramework/Page/Config/SectionDom.php @@ -12,6 +12,7 @@ use Magento\FunctionalTestingFramework\Config\Dom\NodePathMatcher; use Magento\FunctionalTestingFramework\Util\ModulePathExtractor; use Magento\FunctionalTestingFramework\Util\Validation\DuplicateNodeValidationUtil; +use Magento\FunctionalTestingFramework\Util\Validation\SingleNodePerFileValidationUtil; /** * MFTF section.xml configuration XML DOM utility @@ -28,6 +29,12 @@ class SectionDom extends \Magento\FunctionalTestingFramework\Config\MftfDom */ private $validationUtil; + /** SingleNodePerFileValidationUtil + * + * @var SingleNodePerFileValidationUtil + */ + private $singleNodePerFileValidationUtil; + /** * Entity Dom constructor. * @param string $xml @@ -48,6 +55,7 @@ public function __construct( $errorFormat = self::ERROR_FORMAT_DEFAULT ) { $this->validationUtil = new DuplicateNodeValidationUtil('name', $exceptionCollector); + $this->singleNodePerFileValidationUtil = new SingleNodePerFileValidationUtil($exceptionCollector); parent::__construct( $xml, $filename, @@ -69,15 +77,26 @@ public function __construct( public function initDom($xml, $filename = null) { $dom = parent::initDom($xml, $filename); - $sectionNodes = $dom->getElementsByTagName('section'); - foreach ($sectionNodes as $sectionNode) { - $sectionNode->setAttribute(self::SECTION_META_FILENAME_ATTRIBUTE, $filename); - $this->validationUtil->validateChildUniqueness( - $sectionNode, - $filename, - $sectionNode->getAttribute(self::SECTION_META_NAME_ATTRIBUTE) + + if ($dom->getElementsByTagName('sections')->length > 0) { + // Validate single section node per file + $this->singleNodePerFileValidationUtil->validateSingleNodeForTag( + $dom, + 'section', + $filename ); + if ($dom->getElementsByTagName('section')->length > 0) { + /** @var \DOMElement $sectionNode */ + $sectionNode = $dom->getElementsByTagName('section')[0]; + $sectionNode->setAttribute(self::SECTION_META_FILENAME_ATTRIBUTE, $filename); + $this->validationUtil->validateChildUniqueness( + $sectionNode, + $filename, + $sectionNode->getAttribute(self::SECTION_META_NAME_ATTRIBUTE) + ); + } } + return $dom; } } diff --git a/src/Magento/FunctionalTestingFramework/Page/Handlers/PageObjectHandler.php b/src/Magento/FunctionalTestingFramework/Page/Handlers/PageObjectHandler.php index d9eec1af1..dd723fa1b 100644 --- a/src/Magento/FunctionalTestingFramework/Page/Handlers/PageObjectHandler.php +++ b/src/Magento/FunctionalTestingFramework/Page/Handlers/PageObjectHandler.php @@ -9,6 +9,8 @@ use Magento\FunctionalTestingFramework\ObjectManager\ObjectHandlerInterface; use Magento\FunctionalTestingFramework\ObjectManagerFactory; use Magento\FunctionalTestingFramework\Page\Objects\PageObject; +use Magento\FunctionalTestingFramework\Util\Logger\LoggingUtil; +use Magento\FunctionalTestingFramework\Util\Validation\NameValidationUtil; use Magento\FunctionalTestingFramework\XmlParser\PageParser; use Magento\FunctionalTestingFramework\Exceptions\XmlException; @@ -21,7 +23,7 @@ class PageObjectHandler implements ObjectHandlerInterface const PARAMETERIZED = 'parameterized'; const AREA = 'area'; const FILENAME = 'filename'; - const NAME_BLACKLIST_ERROR_MSG = "Page names cannot contain non alphanumeric characters.\tPage='%s'"; + const NAME_BLOCKLIST_ERROR_MSG = "Page names cannot contain non alphanumeric characters.\tPage='%s'"; /** * The singleton instance of this class @@ -53,25 +55,39 @@ private function __construct() return; } + $pageNameValidator = new NameValidationUtil(); foreach ($parserOutput as $pageName => $pageData) { if (preg_match('/[^a-zA-Z0-9_]/', $pageName)) { - throw new XmlException(sprintf(self::NAME_BLACKLIST_ERROR_MSG, $pageName)); + throw new XmlException(sprintf(self::NAME_BLOCKLIST_ERROR_MSG, $pageName)); } + + $filename = $pageData[self::FILENAME] ?? null; + $pageNameValidator->validateAffixes($pageName, NameValidationUtil::PAGE, $filename); $area = $pageData[self::AREA] ?? null; $url = $pageData[self::URL] ?? null; - if ($area == 'admin') { + if ($area === 'admin') { $url = ltrim($url, "/"); } $module = $pageData[self::MODULE] ?? null; $sectionNames = array_keys($pageData[self::SECTION] ?? []); - $parameterized = $pageData[self::PARAMETERIZED] ?? false; + $urlContainsMustaches = strpos($url, "{{") !== false && strpos($url, "}}") !== false; + $parameterized = $pageData[self::PARAMETERIZED] ?? $urlContainsMustaches ?? false; $filename = $pageData[self::FILENAME] ?? null; + $deprecated = $pageData[self::OBJ_DEPRECATED] ?? null; + + if ($deprecated !== null) { + LoggingUtil::getInstance()->getLogger(self::class)->deprecation( + "The page '{$pageName}' is deprecated.", + ["fileName" => $filename, "deprecatedMessage" => $deprecated] + ); + } $this->pageObjects[$pageName] = - new PageObject($pageName, $url, $module, $sectionNames, $parameterized, $area, $filename); + new PageObject($pageName, $url, $module, $sectionNames, $parameterized, $area, $filename, $deprecated); } + $pageNameValidator->summarize(NameValidationUtil::PAGE . " name"); } /** diff --git a/src/Magento/FunctionalTestingFramework/Page/Handlers/SectionObjectHandler.php b/src/Magento/FunctionalTestingFramework/Page/Handlers/SectionObjectHandler.php index e77e4c502..6ac3456fd 100644 --- a/src/Magento/FunctionalTestingFramework/Page/Handlers/SectionObjectHandler.php +++ b/src/Magento/FunctionalTestingFramework/Page/Handlers/SectionObjectHandler.php @@ -10,6 +10,8 @@ use Magento\FunctionalTestingFramework\ObjectManagerFactory; use Magento\FunctionalTestingFramework\Page\Objects\ElementObject; use Magento\FunctionalTestingFramework\Page\Objects\SectionObject; +use Magento\FunctionalTestingFramework\Util\Logger\LoggingUtil; +use Magento\FunctionalTestingFramework\Util\Validation\NameValidationUtil; use Magento\FunctionalTestingFramework\XmlParser\SectionParser; use Magento\FunctionalTestingFramework\Exceptions\XmlException; @@ -56,6 +58,8 @@ private function __construct() return; } + $sectionNameValidator = new NameValidationUtil(); + $elementNameValidator = new NameValidationUtil(); foreach ($parserOutput as $sectionName => $sectionData) { $elements = []; @@ -63,33 +67,64 @@ private function __construct() throw new XmlException(sprintf(self::SECTION_NAME_ERROR_MSG, $sectionName)); } + $filename = $sectionData[self::FILENAME] ?? null; + $sectionNameValidator->validateAffixes($sectionName, NameValidationUtil::SECTION, $filename); + try { foreach ($sectionData[SectionObjectHandler::ELEMENT] as $elementName => $elementData) { if (preg_match('/[^a-zA-Z0-9_]/', $elementName)) { throw new XmlException(sprintf(self::ELEMENT_NAME_ERROR_MSG, $elementName, $sectionName)); } + + $elementNameValidator->validateCamelCase( + $elementName, + NameValidationUtil::SECTION_ELEMENT_NAME, + $filename + ); $elementType = $elementData[SectionObjectHandler::TYPE] ?? null; $elementSelector = $elementData[SectionObjectHandler::SELECTOR] ?? null; $elementLocatorFunc = $elementData[SectionObjectHandler::LOCATOR_FUNCTION] ?? null; $elementTimeout = $elementData[SectionObjectHandler::TIMEOUT] ?? null; $elementParameterized = $elementData[SectionObjectHandler::PARAMETERIZED] ?? false; - + $elementDeprecated = $elementData[self::OBJ_DEPRECATED] ?? null; + if ($elementDeprecated !== null) { + LoggingUtil::getInstance()->getLogger(ElementObject::class)->deprecation( + "The element '{$elementName}' is deprecated.", + ["fileName" => $filename, "deprecatedMessage" => $elementDeprecated] + ); + } $elements[$elementName] = new ElementObject( $elementName, $elementType, $elementSelector, $elementLocatorFunc, $elementTimeout, - $elementParameterized + $elementParameterized, + $elementDeprecated ); } } catch (XmlException $exception) { throw new XmlException($exception->getMessage() . " in Section '{$sectionName}'"); } - $filename = $sectionData[self::FILENAME] ?? null; - $this->sectionObjects[$sectionName] = new SectionObject($sectionName, $elements, $filename); + $sectionDeprecated = $sectionData[self::OBJ_DEPRECATED] ?? null; + + if ($sectionDeprecated !== null) { + LoggingUtil::getInstance()->getLogger(SectionObject::class)->deprecation( + $sectionDeprecated, + ["sectionName" => $filename, "deprecatedSection" => $sectionDeprecated] + ); + } + + $this->sectionObjects[$sectionName] = new SectionObject( + $sectionName, + $elements, + $filename, + $sectionDeprecated + ); } + $sectionNameValidator->summarize(NameValidationUtil::SECTION . " name"); + $elementNameValidator->summarize(NameValidationUtil::SECTION_ELEMENT_NAME); } /** diff --git a/src/Magento/FunctionalTestingFramework/Page/Objects/ElementObject.php b/src/Magento/FunctionalTestingFramework/Page/Objects/ElementObject.php index 6b0626f10..262647b34 100644 --- a/src/Magento/FunctionalTestingFramework/Page/Objects/ElementObject.php +++ b/src/Magento/FunctionalTestingFramework/Page/Objects/ElementObject.php @@ -56,6 +56,13 @@ class ElementObject */ private $parameterized; + /** + * Deprecated message. + * + * @var string + */ + private $deprecated; + /** * ElementObject constructor. * @param string $name @@ -66,11 +73,11 @@ class ElementObject * @param boolean $parameterized * @throws XmlException */ - public function __construct($name, $type, $selector, $locatorFunction, $timeout, $parameterized) + public function __construct($name, $type, $selector, $locatorFunction, $timeout, $parameterized, $deprecated = null) { - if ($selector != null && $locatorFunction != null) { + if ($selector !== null && $locatorFunction !== null) { throw new XmlException("Element '{$name}' cannot have both a selector and a locatorFunction."); - } elseif ($selector == null && $locatorFunction == null) { + } elseif ($selector === null && $locatorFunction === null) { throw new XmlException("Element '{$name}' must have either a selector or a locatorFunction.'"); } @@ -78,11 +85,22 @@ public function __construct($name, $type, $selector, $locatorFunction, $timeout, $this->type = $type; $this->selector = $selector; $this->locatorFunction = $locatorFunction; - if (strpos($locatorFunction, "Locator::") === false) { + if ($locatorFunction !== null && strpos($locatorFunction, "Locator::") === false) { $this->locatorFunction = "Locator::" . $locatorFunction; } $this->timeout = $timeout; $this->parameterized = $parameterized; + $this->deprecated = $deprecated; + } + + /** + * Getter for the deprecated attr + * + * @return string + */ + public function getDeprecated() + { + return $this->deprecated; } /** @@ -142,11 +160,11 @@ public function getPrioritizedSelector() */ public function getTimeout() { - if ($this->timeout == ElementObject::DEFAULT_TIMEOUT_SYMBOL) { + if ($this->timeout === ElementObject::DEFAULT_TIMEOUT_SYMBOL) { return null; } - return (int)$this->timeout; + return (int) $this->timeout; } /** diff --git a/src/Magento/FunctionalTestingFramework/Page/Objects/PageObject.php b/src/Magento/FunctionalTestingFramework/Page/Objects/PageObject.php index d99c40240..e4a1acdc0 100644 --- a/src/Magento/FunctionalTestingFramework/Page/Objects/PageObject.php +++ b/src/Magento/FunctionalTestingFramework/Page/Objects/PageObject.php @@ -65,18 +65,34 @@ class PageObject */ private $filename; + /** + * Deprecated message. + * + * @var string + */ + private $deprecated; + /** * PageObject constructor. - * @param string $name - * @param string $url - * @param string $module - * @param array $sections - * @param boolean $parameterized - * @param string $area - * @param string $filename - */ - public function __construct($name, $url, $module, $sections, $parameterized, $area, $filename = null) - { + * @param string $name + * @param string $url + * @param string $module + * @param array $sections + * @param boolean $parameterized + * @param string $area + * @param string|null $filename + * @param string|null $deprecated + */ + public function __construct( + $name, + $url, + $module, + $sections, + $parameterized, + $area, + $filename = null, + $deprecated = null + ) { $this->name = $name; $this->url = $url; $this->module = $module; @@ -84,6 +100,17 @@ public function __construct($name, $url, $module, $sections, $parameterized, $ar $this->parameterized = $parameterized; $this->area = $area; $this->filename = $filename; + $this->deprecated = $deprecated; + } + + /** + * Getter for the deprecated attr of the section + * + * @return string + */ + public function getDeprecated() + { + return $this->deprecated; } /** diff --git a/src/Magento/FunctionalTestingFramework/Page/Objects/SectionObject.php b/src/Magento/FunctionalTestingFramework/Page/Objects/SectionObject.php index 8c1cac326..3674eea16 100644 --- a/src/Magento/FunctionalTestingFramework/Page/Objects/SectionObject.php +++ b/src/Magento/FunctionalTestingFramework/Page/Objects/SectionObject.php @@ -32,17 +32,26 @@ class SectionObject */ private $filename; + /** + * Deprecated message. + * + * @var string + */ + private $deprecated; + /** * SectionObject constructor. - * @param string $name - * @param array $elements - * @param string $filename + * @param string $name + * @param array $elements + * @param string|null $filename + * @param string|null $deprecated */ - public function __construct($name, $elements, $filename = null) + public function __construct($name, $elements, $filename = null, $deprecated = null) { $this->name = $name; $this->elements = $elements; $this->filename = $filename; + $this->deprecated = $deprecated; } /** @@ -55,6 +64,16 @@ public function getName() return $this->name; } + /** + * Getter for the deprecated attr of the section + * + * @return string + */ + public function getDeprecated() + { + return $this->deprecated; + } + /** * Getter for the Section Filename * diff --git a/src/Magento/FunctionalTestingFramework/Page/etc/PageObject.xsd b/src/Magento/FunctionalTestingFramework/Page/etc/PageObject.xsd index c6f399a67..7b857a235 100644 --- a/src/Magento/FunctionalTestingFramework/Page/etc/PageObject.xsd +++ b/src/Magento/FunctionalTestingFramework/Page/etc/PageObject.xsd @@ -7,19 +7,10 @@ --> <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> - <xs:annotation> - <xs:documentation>The definition of a page object.</xs:documentation> - </xs:annotation> - - <xs:element name="pages"> - <xs:annotation> - <xs:documentation> - The root element for configuration data. - </xs:documentation> - </xs:annotation> - <xs:complexType> + <xs:redefine schemaLocation="mergedPageObject.xsd"> + <xs:complexType name="PageType"> <xs:sequence> - <xs:element ref="page" maxOccurs="unbounded" minOccurs="1"> + <xs:element ref="page" maxOccurs="1" minOccurs="1"> <xs:annotation> <xs:documentation> Contains sequence of ui sections in a page. @@ -28,81 +19,5 @@ </xs:element> </xs:sequence> </xs:complexType> - </xs:element> - - <xs:element name="page"> - <xs:complexType> - <xs:sequence> - <xs:element ref="section" maxOccurs="unbounded" minOccurs="0"> - <xs:annotation> - <xs:documentation> - Contains sequence of ui elements. - </xs:documentation> - </xs:annotation> - </xs:element> - </xs:sequence> - <xs:attribute type="notEmptyType" name="name" use="required"> - <xs:annotation> - <xs:documentation> - Unique page name identifier. - </xs:documentation> - </xs:annotation> - </xs:attribute> - <xs:attribute type="notEmptyType" name="url" use="required"> - <xs:annotation> - <xs:documentation> - URL for the page. Do not include the hostname. For example: "/admin/customer/index/" - </xs:documentation> - </xs:annotation> - </xs:attribute> - <xs:attribute type="notEmptyType" name="module" use="required"> - <xs:annotation> - <xs:documentation> - The name of the module to which the page belongs. For example: "Magento_Catalog". - </xs:documentation> - </xs:annotation> - </xs:attribute> - <xs:attribute type="xs:boolean" name="parameterized" use="optional"/> - <xs:attribute type="pageArea" name="area" use="required"/> - <xs:attributeGroup ref="removeAttribute"/> - <xs:attribute type="xs:string" name="filename"/> - </xs:complexType> - </xs:element> - - <xs:element name="section"> - <xs:complexType> - <xs:attribute type="notEmptyType" name="name" use="required"> - <xs:annotation> - <xs:documentation> - Unique section name identifier. - </xs:documentation> - </xs:annotation> - </xs:attribute> - <xs:attributeGroup ref="removeAttribute"/> - </xs:complexType> - </xs:element> - - <xs:simpleType name="notEmptyType"> - <xs:restriction base="xs:string"> - <xs:minLength value="1" /> - </xs:restriction> - </xs:simpleType> - - <xs:attributeGroup name="removeAttribute"> - <xs:attribute type="xs:boolean" name="remove" use="optional" default="false"> - <xs:annotation> - <xs:documentation> - Set to true to remove this element during parsing. - </xs:documentation> - </xs:annotation> - </xs:attribute> - </xs:attributeGroup> - - <xs:simpleType name="pageArea" final="restriction" > - <xs:restriction base="xs:string"> - <xs:enumeration value="admin" /> - <xs:enumeration value="storefront" /> - <xs:enumeration value="external" /> - </xs:restriction> - </xs:simpleType> + </xs:redefine> </xs:schema> diff --git a/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd b/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd index 44e057369..2267ed09b 100644 --- a/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd +++ b/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd @@ -7,15 +7,10 @@ --> <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> - <xs:element name="sections"> - <xs:annotation> - <xs:documentation> - The root element for configuration data. - </xs:documentation> - </xs:annotation> - <xs:complexType> + <xs:redefine schemaLocation="mergedSectionObject.xsd"> + <xs:complexType name="SectionType"> <xs:sequence> - <xs:element ref="section" maxOccurs="unbounded" minOccurs="1"> + <xs:element ref="section" maxOccurs="1" minOccurs="1"> <xs:annotation> <xs:documentation> Contains sequence of ui elements in a section of a page. @@ -24,112 +19,5 @@ </xs:element> </xs:sequence> </xs:complexType> - </xs:element> - - <xs:element name="section"> - <xs:complexType> - <xs:sequence> - <xs:element ref="element" maxOccurs="unbounded" minOccurs="1"> - <xs:annotation> - <xs:documentation> - Contains information of an ui element. - </xs:documentation> - </xs:annotation> - </xs:element> - </xs:sequence> - <xs:attribute type="notEmptyType" name="name" use="required"> - <xs:annotation> - <xs:documentation> - Unique section name identifier. - </xs:documentation> - </xs:annotation> - </xs:attribute> - <xs:attributeGroup ref="removeAttribute"/> - <xs:attribute type="xs:string" name="filename"/> - </xs:complexType> - </xs:element> - - <xs:element name="element"> - <xs:complexType> - <xs:attribute type="notEmptyType" name="name" use="required"> - <xs:annotation> - <xs:documentation> - Element name. - </xs:documentation> - </xs:annotation> - </xs:attribute> - <xs:attribute type="uiElementType" name="type" use="required"> - <xs:annotation> - <xs:documentation> - The type of the element, e.g. select, radio, etc. - </xs:documentation> - </xs:annotation> - </xs:attribute> - <xs:attribute type="notEmptyType" name="selector" use="optional"> - <xs:annotation> - <xs:documentation> - Selector of the element. Optional due to being able to use either this or locatorFunction. - </xs:documentation> - </xs:annotation> - </xs:attribute> - <xs:attribute type="notEmptyType" name="locatorFunction" use="optional"> - <xs:annotation> - <xs:documentation> - LocatorFunction of an element, substitute for a selector. - </xs:documentation> - </xs:annotation> - </xs:attribute> - <xs:attribute type="timeoutType" name="timeout" use="optional"> - <xs:annotation> - <xs:documentation> - Optional timeout value in second to wait for the operation on the element. use "-" for default value. - </xs:documentation> - </xs:annotation> - </xs:attribute> - <xs:attribute type="xs:boolean" name="parameterized" use="optional"/> - <xs:attributeGroup ref="removeAttribute"/> - </xs:complexType> - </xs:element> - - <xs:simpleType name="uiElementType"> - <xs:restriction base="xs:string"> - <xs:enumeration value="text"/> - <xs:enumeration value="textarea"/> - <xs:enumeration value="input"/> - <xs:enumeration value="button"/> - <xs:enumeration value="checkbox"/> - <xs:enumeration value="radio"/> - <xs:enumeration value="checkboxset"/> - <xs:enumeration value="radioset"/> - <xs:enumeration value="date"/> - <xs:enumeration value="file"/> - <xs:enumeration value="select"/> - <xs:enumeration value="multiselect"/> - <xs:enumeration value="wysiwyg"/> - <xs:enumeration value="iframe"/> - <xs:enumeration value="block"/> - </xs:restriction> - </xs:simpleType> - - <xs:simpleType name="notEmptyType"> - <xs:restriction base="xs:string"> - <xs:minLength value="1" /> - </xs:restriction> - </xs:simpleType> - - <xs:simpleType name="timeoutType"> - <xs:restriction base="xs:string"> - <xs:pattern value="([0-9])+|-"/> - </xs:restriction> - </xs:simpleType> - - <xs:attributeGroup name="removeAttribute"> - <xs:attribute type="xs:boolean" name="remove" use="optional" default="false"> - <xs:annotation> - <xs:documentation> - Set to true to remove this element during parsing. - </xs:documentation> - </xs:annotation> - </xs:attribute> - </xs:attributeGroup> + </xs:redefine> </xs:schema> diff --git a/src/Magento/FunctionalTestingFramework/Page/etc/mergedPageObject.xsd b/src/Magento/FunctionalTestingFramework/Page/etc/mergedPageObject.xsd new file mode 100644 index 000000000..65ac8290d --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Page/etc/mergedPageObject.xsd @@ -0,0 +1,116 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> + <xs:annotation> + <xs:documentation>The definition of a page object.</xs:documentation> + </xs:annotation> + + <xs:element name="pages"> + <xs:annotation> + <xs:documentation> + The root element for configuration data. + </xs:documentation> + </xs:annotation> + <xs:complexType> + <xs:sequence> + <xs:element ref="page" maxOccurs="unbounded" minOccurs="1"> + <xs:annotation> + <xs:documentation> + Contains sequence of ui sections in a page. + </xs:documentation> + </xs:annotation> + </xs:element> + </xs:sequence> + </xs:complexType> + </xs:element> + + <xs:element name="page"> + <xs:complexType> + <xs:sequence> + <xs:element ref="section" maxOccurs="unbounded" minOccurs="0"> + <xs:annotation> + <xs:documentation> + Contains sequence of ui elements. + </xs:documentation> + </xs:annotation> + </xs:element> + </xs:sequence> + <xs:attribute type="notEmptyType" name="name" use="required"> + <xs:annotation> + <xs:documentation> + Unique page name identifier. + </xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attribute type="xs:string" name="deprecated"> + <xs:annotation> + <xs:documentation> + Message and flag which shows that entity is deprecated. + </xs:documentation> + </xs:annotation> + </xs:attribute> + + <xs:attribute type="notEmptyType" name="url" use="required"> + <xs:annotation> + <xs:documentation> + URL for the page. Do not include the hostname. For example: "/admin/customer/index/" + </xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attribute type="notEmptyType" name="module" use="required"> + <xs:annotation> + <xs:documentation> + The name of the module to which the page belongs. For example: "Magento_Catalog". + </xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attribute type="xs:boolean" name="parameterized" use="optional"/> + <xs:attribute type="pageArea" name="area" use="required"/> + <xs:attributeGroup ref="removeAttribute"/> + <xs:attribute type="xs:string" name="filename"/> + </xs:complexType> + </xs:element> + + <xs:element name="section"> + <xs:complexType> + <xs:attribute type="notEmptyType" name="name" use="required"> + <xs:annotation> + <xs:documentation> + Unique section name identifier. + </xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attributeGroup ref="removeAttribute"/> + </xs:complexType> + </xs:element> + + <xs:simpleType name="notEmptyType"> + <xs:restriction base="xs:string"> + <xs:minLength value="1" /> + </xs:restriction> + </xs:simpleType> + + <xs:attributeGroup name="removeAttribute"> + <xs:attribute type="xs:boolean" name="remove" use="optional" default="false"> + <xs:annotation> + <xs:documentation> + Set to true to remove this element during parsing. + </xs:documentation> + </xs:annotation> + </xs:attribute> + </xs:attributeGroup> + + <xs:simpleType name="pageArea" final="restriction" > + <xs:restriction base="xs:string"> + <xs:enumeration value="admin" /> + <xs:enumeration value="storefront" /> + <xs:enumeration value="external" /> + </xs:restriction> + </xs:simpleType> +</xs:schema> diff --git a/src/Magento/FunctionalTestingFramework/Page/etc/mergedSectionObject.xsd b/src/Magento/FunctionalTestingFramework/Page/etc/mergedSectionObject.xsd new file mode 100644 index 000000000..1b5b44ddd --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Page/etc/mergedSectionObject.xsd @@ -0,0 +1,149 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> + <xs:element name="sections"> + <xs:annotation> + <xs:documentation> + The root element for configuration data. + </xs:documentation> + </xs:annotation> + <xs:complexType> + <xs:sequence> + <xs:element ref="section" maxOccurs="unbounded" minOccurs="1"> + <xs:annotation> + <xs:documentation> + Contains sequence of ui elements in a section of a page. + </xs:documentation> + </xs:annotation> + </xs:element> + </xs:sequence> + </xs:complexType> + </xs:element> + + <xs:element name="section"> + <xs:complexType> + <xs:sequence> + <xs:element ref="element" maxOccurs="unbounded" minOccurs="1"> + <xs:annotation> + <xs:documentation> + Contains information of an ui element. + </xs:documentation> + </xs:annotation> + </xs:element> + </xs:sequence> + <xs:attribute type="notEmptyType" name="name" use="required"> + <xs:annotation> + <xs:documentation> + Unique section name identifier. + </xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attribute type="xs:string" name="deprecated"> + <xs:annotation> + <xs:documentation> + Message and flag which shows that entity is deprecated. + </xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attributeGroup ref="removeAttribute"/> + <xs:attribute type="xs:string" name="filename"/> + </xs:complexType> + </xs:element> + + <xs:element name="element"> + <xs:complexType> + <xs:attribute type="notEmptyType" name="name" use="required"> + <xs:annotation> + <xs:documentation> + Element name. + </xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attribute type="xs:string" name="deprecated"> + <xs:annotation> + <xs:documentation> + Message and flag which shows that entity is deprecated. + </xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attribute type="uiElementType" name="type" use="required"> + <xs:annotation> + <xs:documentation> + The type of the element, e.g. select, radio, etc. + </xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attribute type="notEmptyType" name="selector" use="optional"> + <xs:annotation> + <xs:documentation> + Selector of the element. Optional due to being able to use either this or locatorFunction. + </xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attribute type="notEmptyType" name="locatorFunction" use="optional"> + <xs:annotation> + <xs:documentation> + LocatorFunction of an element, substitute for a selector. + </xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attribute type="timeoutType" name="timeout" use="optional"> + <xs:annotation> + <xs:documentation> + Optional timeout value in second to wait for the operation on the element. use "-" for default value. + </xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attribute type="xs:boolean" name="parameterized" use="optional"/> + <xs:attributeGroup ref="removeAttribute"/> + </xs:complexType> + </xs:element> + + <xs:simpleType name="uiElementType"> + <xs:restriction base="xs:string"> + <xs:enumeration value="text"/> + <xs:enumeration value="textarea"/> + <xs:enumeration value="input"/> + <xs:enumeration value="button"/> + <xs:enumeration value="checkbox"/> + <xs:enumeration value="radio"/> + <xs:enumeration value="checkboxset"/> + <xs:enumeration value="radioset"/> + <xs:enumeration value="date"/> + <xs:enumeration value="file"/> + <xs:enumeration value="select"/> + <xs:enumeration value="multiselect"/> + <xs:enumeration value="wysiwyg"/> + <xs:enumeration value="iframe"/> + <xs:enumeration value="block"/> + </xs:restriction> + </xs:simpleType> + + <xs:simpleType name="notEmptyType"> + <xs:restriction base="xs:string"> + <xs:minLength value="1" /> + </xs:restriction> + </xs:simpleType> + + <xs:simpleType name="timeoutType"> + <xs:restriction base="xs:string"> + <xs:pattern value="([0-9])+|-"/> + </xs:restriction> + </xs:simpleType> + + <xs:attributeGroup name="removeAttribute"> + <xs:attribute type="xs:boolean" name="remove" use="optional" default="false"> + <xs:annotation> + <xs:documentation> + Set to true to remove this element during parsing. + </xs:documentation> + </xs:annotation> + </xs:attribute> + </xs:attributeGroup> +</xs:schema> diff --git a/src/Magento/FunctionalTestingFramework/StaticCheck/ActionGroupArgumentsCheck.php b/src/Magento/FunctionalTestingFramework/StaticCheck/ActionGroupArgumentsCheck.php new file mode 100644 index 000000000..8b95fb9d5 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/StaticCheck/ActionGroupArgumentsCheck.php @@ -0,0 +1,233 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\FunctionalTestingFramework\StaticCheck; + +use Magento\FunctionalTestingFramework\Test\Handlers\ActionGroupObjectHandler; +use Magento\FunctionalTestingFramework\Test\Objects\ActionGroupObject; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Finder\Finder; +use Exception; +use Magento\FunctionalTestingFramework\Util\Script\ScriptUtil; +use Symfony\Component\Finder\SplFileInfo; +use DOMElement; + +/** + * Class ActionGroupArgumentsCheck + * @package Magento\FunctionalTestingFramework\StaticCheck + */ +class ActionGroupArgumentsCheck implements StaticCheckInterface +{ + const ACTIONGROUP_NAME_REGEX_PATTERN = '/<actionGroup name=["\']([^\'"]*)/'; + const ERROR_LOG_FILENAME = 'mftf-arguments-checks'; + const ERROR_LOG_MESSAGE = 'MFTF Action Group Unused Arguments Check'; + + /** + * Array containing all errors found after running the execute() function. + * @var array + */ + private $errors = []; + + /** + * String representing the output summary found after running the execute() function. + * @var string + */ + private $output; + + /** + * ScriptUtil instance + * + * @var ScriptUtil + */ + private $scriptUtil; + + /** + * Checks unused arguments in action groups and prints out error to file. + * + * @param InputInterface $input + * @return void + * @throws Exception + */ + public function execute(InputInterface $input) + { + $this->scriptUtil = new ScriptUtil(); + $allModules = $this->scriptUtil->getAllModulePaths(); + + $actionGroupXmlFiles = $this->scriptUtil->getModuleXmlFilesByScope( + $allModules, + DIRECTORY_SEPARATOR . 'ActionGroup' . DIRECTORY_SEPARATOR + ); + + $this->errors = $this->findErrorsInFileSet($actionGroupXmlFiles); + + $this->output = $this->scriptUtil->printErrorsToFile( + $this->errors, + StaticChecksList::getErrorFilesPath() . DIRECTORY_SEPARATOR . self::ERROR_LOG_FILENAME . '.txt', + self::ERROR_LOG_MESSAGE + ); + } + + /** + * Return array containing all errors found after running the execute() function. + * @return array + */ + public function getErrors() + { + return $this->errors; + } + + /** + * Return string of a short human readable result of the check. For example: "No unused arguments found." + * @return string + */ + public function getOutput() + { + return $this->output; + } + + /** + * Finds all unused arguments in given set of actionGroup files + * @param Finder $files + * @return array $testErrors + */ + private function findErrorsInFileSet($files) + { + $actionGroupErrors = []; + /** @var SplFileInfo $filePath */ + foreach ($files as $filePath) { + $actionGroupToArguments = []; + $contents = $filePath->getContents(); + /** @var DOMElement $actionGroup */ + $actionGroup = $this->getActionGroupDomElement($contents); + $arguments = $this->extractActionGroupArguments($actionGroup); + $unusedArguments = $this->findUnusedArguments($arguments, $contents); + if (!empty($unusedArguments)) { + $actionGroupToArguments[$actionGroup->getAttribute('name')] = $unusedArguments; + $actionGroupErrors += $this->setErrorOutput($actionGroupToArguments, $filePath); + } + } + return $actionGroupErrors; + } + + /** + * Extract actionGroup DomElement from xml file + * @param string $contents + * @return \DOMElement + */ + public function getActionGroupDomElement($contents) + { + $domDocument = new \DOMDocument(); + $domDocument->loadXML($contents); + return $domDocument->getElementsByTagName('actionGroup')[0]; + } + + /** + * Get list of action group arguments declared in an action group + * @param \DOMElement $actionGroup + * @return array $arguments + */ + public function extractActionGroupArguments($actionGroup) + { + $arguments = []; + $argumentsNodes = $actionGroup->getElementsByTagName('arguments'); + if ($argumentsNodes->length > 0) { + $argumentNodes = $argumentsNodes[0]->getElementsByTagName('argument'); + foreach ($argumentNodes as $argumentNode) { + $arguments[] = $argumentNode->getAttribute('name'); + } + } + return $arguments; + } + + /** + * Returns unused arguments in an action group + * @param array $arguments + * @param string $contents + * @return array + */ + public function findUnusedArguments($arguments, $contents) + { + $unusedArguments = []; + preg_match(self::ACTIONGROUP_NAME_REGEX_PATTERN, $contents, $actionGroupName); + $validActionGroup = false; + try { + $actionGroup = ActionGroupObjectHandler::getInstance()->getObject($actionGroupName[1]); + if ($actionGroup) { + $validActionGroup = true; + } + } catch (Exception $e) { + } + + if (!$validActionGroup) { + return $unusedArguments; + } + + foreach ($arguments as $argument) { + //pattern to match all argument references + $patterns = [ + '(\{{2}' . $argument . '(\.[a-zA-Z0-9_\[\]\(\).,\'\/ ]+)?}{2})', + '([(,\s\'$$]' . $argument . '(\.[a-zA-Z0-9_$\[\]]+)?[),\s\'])' + ]; + // matches entity references + if (preg_match($patterns[0], $contents)) { + continue; + } + //matches parametrized references + if (preg_match($patterns[1], $contents)) { + continue; + } + //for extending action groups, exclude arguments that are also defined in parent action group + if ($this->isParentActionGroupArgument($argument, $actionGroup)) { + continue; + } + $unusedArguments[] = $argument; + } + return $unusedArguments; + } + + /** + * Checks if the argument is also defined in the parent for extending action groups. + * @param string $argument + * @param ActionGroupObject $actionGroup + * @return boolean + */ + private function isParentActionGroupArgument($argument, $actionGroup) + { + $parentActionGroupName = $actionGroup->getParentName(); + if ($parentActionGroupName !== null) { + $parentActionGroup = ActionGroupObjectHandler::getInstance()->getObject($parentActionGroupName); + $parentArguments = $parentActionGroup->getArguments(); + foreach ($parentArguments as $parentArgument) { + if ($argument === $parentArgument->getName()) { + return true; + } + } + } + return false; + } + + /** + * Builds and returns error output for violating references + * + * @param array $actionGroupToArguments + * @param SplFileInfo $path + * @return mixed + */ + private function setErrorOutput($actionGroupToArguments, $path) + { + $actionGroupErrors = []; + if (!empty($actionGroupToArguments)) { + // Build error output + $errorOutput = "\nFile \"{$path->getRealPath()}\""; + $errorOutput .= "\ncontains action group(s) with unused arguments.\n\t\t"; + foreach ($actionGroupToArguments as $actionGroup => $arguments) { + $errorOutput .= "\n\t {$actionGroup} has unused argument(s): " . implode(", ", $arguments); + } + $actionGroupErrors[$path->getRealPath()][] = $errorOutput; + } + return $actionGroupErrors; + } +} diff --git a/src/Magento/FunctionalTestingFramework/StaticCheck/AnnotationsCheck.php b/src/Magento/FunctionalTestingFramework/StaticCheck/AnnotationsCheck.php new file mode 100644 index 000000000..0b3e6c40d --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/StaticCheck/AnnotationsCheck.php @@ -0,0 +1,262 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\FunctionalTestingFramework\StaticCheck; + +use Magento\FunctionalTestingFramework\Config\MftfApplicationConfig; +use Magento\FunctionalTestingFramework\Test\Handlers\TestObjectHandler; +use Magento\FunctionalTestingFramework\Test\Objects\TestObject; +use Magento\FunctionalTestingFramework\Util\Script\ScriptUtil; +use Symfony\Component\Console\Input\InputInterface; +use Exception; + +/** + * Class AnnotationsCheck + * @package Magento\FunctionalTestingFramework\StaticCheck + */ +class AnnotationsCheck implements StaticCheckInterface +{ + const ERROR_LOG_FILENAME = 'mftf-annotations-static-check'; + const ERROR_LOG_MESSAGE = 'MFTF Annotations Static Check'; + + /** + * Array containing all errors found after running the execute() function. + * @var array + */ + private $errors = []; + + /** + * String representing the output summary found after running the execute() function. + * @var string + */ + private $output; + + /** + * Array containing + * key = Story appended to Title + * value = test names that have that pair + * @var array + */ + private $storiesTitlePairs = []; + + /** + * Array containing + * key = testCaseId appended to Title + * value = test names that have that pair + * @var array + */ + private $testCaseIdTitlePairs = []; + + /** + * Validates test annotations + * + * @param InputInterface $input + * @return void + * @throws Exception + */ + public function execute(InputInterface $input) + { + // Set MFTF to the UNIT_TEST_PHASE to mute the default DEPRECATION warnings from the TestObjectHandler. + MftfApplicationConfig::create( + true, + MftfApplicationConfig::UNIT_TEST_PHASE, + false, + MftfApplicationConfig::LEVEL_DEFAULT, + true + ); + $allTests = TestObjectHandler::getInstance(false)->getAllObjects(); + + foreach ($allTests as $test) { + if ($this->validateSkipIssueId($test)) { + //if test is skipped ignore other checks + continue; + } + $this->validateRequiredAnnotations($test); + $this->aggregateStoriesTitlePairs($test); + $this->aggregateTestCaseIdTitlePairs($test); + } + + $this->validateStoriesTitlePairs(); + $this->validateTestCaseIdTitlePairs(); + + $scriptUtil = new ScriptUtil(); + $this->output = $scriptUtil->printErrorsToFile( + $this->errors, + StaticChecksList::getErrorFilesPath() . DIRECTORY_SEPARATOR . self::ERROR_LOG_FILENAME . '.txt', + self::ERROR_LOG_MESSAGE + ); + } + + /** + * Return array containing all errors found after running the execute() function. + * @return array + */ + public function getErrors() + { + return $this->errors; + } + + /** + * Return string of a short human readable result of the check. For example: "No Dependency errors found." + * @return string + */ + public function getOutput() + { + return $this->output; + } + + /** + * Validates that the test has the following annotations: + * stories + * title + * description + * severity + * + * @param TestObject $test + * @return void + * @SuppressWarnings(PHPMD.CyclomaticComplexity) + * @SuppressWarnings(PHPMD.NPathComplexity) + */ + private function validateRequiredAnnotations($test) + { + $annotations = $test->getAnnotations(); + $missing = []; + + $stories = $annotations['stories'] ?? null; + if ($stories === null || !isset($stories[0]) || empty(trim($stories[0]))) { + $missing[] = "stories"; + } + + $testCaseId = "[NO TESTCASEID]"; + if (isset($annotations['testCaseId'][0])) { + $testCaseId = trim($annotations['testCaseId'][0]); + } + + $title = $annotations['title'] ?? null; + if ($title === null + || !isset($title[0]) + || empty(trim($title[0])) + || empty(trim(substr(trim($title[0]), strlen($testCaseId . ': '))))) { + $missing[] = "title"; + } + + $description = $annotations['description']['main'] ?? null; + if ($description === null || empty(trim($description))) { + $missing[] = "description"; + } + + $severity = $annotations['severity'] ?? null; + if ($severity === null || !isset($severity[0]) || empty(trim($severity[0]))) { + $missing[] = "severity"; + } + + $allMissing = join(", ", $missing); + if (strlen($allMissing) > 0) { + $this->errors[][] = "Test {$test->getName()} is missing the required annotations: " . $allMissing; + } + } + + /** + * Validates that if the test is skipped, that it has an issueId value. + * + * @param TestObject $test + * @return boolean + */ + private function validateSkipIssueId($test) + { + $validateSkipped = false; + $annotations = $test->getAnnotations(); + + $skip = $annotations['skip'] ?? null; + if ($skip !== null) { + $validateSkipped = true; + if ((!isset($skip[0]) || strlen($skip[0]) === 0) + && (!isset($skip['issueId']) || strlen($skip['issueId']) === 0)) { + $this->errors[][] = "Test {$test->getName()} is skipped but the issueId is empty."; + } + } + return $validateSkipped; + } + + /** + * Add the key = "stories appended to title", value = test name, to the class variable. + * + * @param TestObject $test + * @return void + */ + private function aggregateStoriesTitlePairs($test) + { + $annotations = $test->getAnnotations(); + $stories = $annotations['stories'][0] ?? null; + $title = $this->getTestTitleWithoutPrefix($test); + if ($stories !== null && $title !== null) { + $this->storiesTitlePairs[$stories . $title][] = $test->getName(); + } + } + + /** + * Add the key = "testCaseId appended to title", value = test name, to the class variable. + * + * @param TestObject $test + * @return void + */ + private function aggregateTestCaseIdTitlePairs($test) + { + $annotations = $test->getAnnotations(); + $testCaseId = $annotations['testCaseId'][0] ?? null; + $title = $this->getTestTitleWithoutPrefix($test); + if ($testCaseId !== null && $title !== null) { + $this->testCaseIdTitlePairs[$testCaseId . $title][] = $test->getName(); + } + } + + /** + * Strip away the testCaseId prefix that was automatically added to the test title + * so that way we have just the raw title from the XML file. + * + * @param TestObject $test + * @return string|null + */ + private function getTestTitleWithoutPrefix($test) + { + $annotations = $test->getAnnotations(); + $title = $annotations['title'][0] ?? null; + if ($title === null) { + return null; + } else { + $testCaseId = $annotations['testCaseId'][0] ?? "[NO TESTCASEID]"; + return substr($title, strlen($testCaseId . ": ")); + } + } + + /** + * Adds an error if any story+title pairs are used by more than one test. + * + * @return void + */ + private function validateStoriesTitlePairs() + { + foreach ($this->storiesTitlePairs as $pair) { + if (sizeof($pair) > 1) { + $this->errors[][] = "Stories + title combination must be unique: " . join(", ", $pair); + } + } + } + + /** + * Adds an error if any testCaseId+title pairs are used by more than one test. + * + * @return void + */ + private function validateTestCaseIdTitlePairs() + { + foreach ($this->testCaseIdTitlePairs as $pair) { + if (sizeof($pair) > 1) { + $this->errors[][] = "testCaseId + title combination must be unique: " . join(", ", $pair); + } + } + } +} diff --git a/src/Magento/FunctionalTestingFramework/StaticCheck/CreatedDataFromOutsideActionGroupCheck.php b/src/Magento/FunctionalTestingFramework/StaticCheck/CreatedDataFromOutsideActionGroupCheck.php new file mode 100644 index 000000000..611e2946b --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/StaticCheck/CreatedDataFromOutsideActionGroupCheck.php @@ -0,0 +1,193 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\FunctionalTestingFramework\StaticCheck; + +use Magento\FunctionalTestingFramework\Config\MftfApplicationConfig; +use InvalidArgumentException; +use Exception; +use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; +use Magento\FunctionalTestingFramework\Exceptions\XmlException; +use Magento\FunctionalTestingFramework\Page\Objects\SectionObject; +use Magento\FunctionalTestingFramework\Test\Objects\ActionObject; +use Magento\FunctionalTestingFramework\Page\Objects\ElementObject; +use Magento\FunctionalTestingFramework\Test\Objects\ActionGroupObject; +use Magento\FunctionalTestingFramework\Page\Objects\PageObject; +use Magento\FunctionalTestingFramework\Test\Objects\TestObject; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Finder\Finder; +use Magento\FunctionalTestingFramework\Util\Script\ScriptUtil; +use Magento\FunctionalTestingFramework\DataGenerator\Handlers\OperationDefinitionObjectHandler; +use Magento\FunctionalTestingFramework\DataGenerator\Objects\OperationDefinitionObject; +use Magento\FunctionalTestingFramework\DataGenerator\Handlers\DataObjectHandler; +use Magento\FunctionalTestingFramework\DataGenerator\Objects\EntityDataObject; +use Symfony\Component\Finder\SplFileInfo; +use DOMNodeList; +use DOMElement; + +/** + * Class CreatedDataFromOutsideActionGroupCheck + * @package Magento\FunctionalTestingFramework\StaticCheck + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class CreatedDataFromOutsideActionGroupCheck implements StaticCheckInterface +{ + const ACTIONGROUP_REGEX_PATTERN = '/\$(\$)*([\w.]+)(\$)*\$/'; + const ERROR_LOG_FILENAME = 'create-data-from-outside-action-group'; + const ERROR_MESSAGE = 'Created Data From Outside Action Group'; + + /** + * Array containing all errors found after running the execute() function + * + * @var array + */ + private $errors = []; + + /** + * String representing the output summary found after running the execute() function + * + * @var string + */ + private $output; + + /** + * ScriptUtil instance + * + * @var ScriptUtil + */ + private $scriptUtil; + + /** + * Checks test dependencies, determined by references in tests versus the dependencies listed in the Magento module + * + * @param InputInterface $input + * @return void + * @throws Exception + */ + public function execute(InputInterface $input) + { + $this->scriptUtil = new ScriptUtil(); + $this->loadAllXmlFiles($input); + $this->errors = []; + $this->errors += $this->findReferenceErrorsInActionFiles($this->actionGroupXmlFile); + // hold on to the output and print any errors to a file + $this->output = $this->scriptUtil->printErrorsToFile( + $this->errors, + StaticChecksList::getErrorFilesPath() . DIRECTORY_SEPARATOR . self::ERROR_LOG_FILENAME . '.txt', + self::ERROR_MESSAGE + ); + } + + /** + * Return array containing all errors found after running the execute() function + * + * @return array + */ + public function getErrors() + { + return $this->errors; + } + + /** + * Return string of a short human readable result of the check. For example: "No Dependency errors found." + * + * @return string + */ + public function getOutput() + { + return $this->output; + } + + /** + * Read all XML files for scanning + * + * @param InputInterface $input + * @return void + * @throws Exception + * @SuppressWarnings(PHPMD.CyclomaticComplexity) + */ + private function loadAllXmlFiles($input) + { + $modulePaths = []; + $path = $input->getOption('path'); + if ($path) { + if (!realpath($path)) { + throw new InvalidArgumentException('Invalid --path option: ' . $path); + } + MftfApplicationConfig::create( + true, + MftfApplicationConfig::UNIT_TEST_PHASE, + false, + MftfApplicationConfig::LEVEL_DEFAULT, + true + ); + $modulePaths[] = realpath($path); + } else { + $modulePaths = $this->scriptUtil->getAllModulePaths(); + } + + // These files can contain references to other entities + $this->actionGroupXmlFile = $this->scriptUtil->getModuleXmlFilesByScope($modulePaths, 'ActionGroup'); + + if (empty($this->actionGroupXmlFile)) { + if ($path) { + throw new InvalidArgumentException( + 'Invalid --path option: ' + . $path + . PHP_EOL + . 'Please make sure --path points to a valid MFTF Test Module.' + ); + } elseif (empty($this->rootSuiteXmlFiles)) { + throw new TestFrameworkException('No xml file to scan.'); + } + } + } + + /** + * Find reference errors in set of action files + * + * @param Finder $files + * @return array + * @throws XmlException + */ + private function findReferenceErrorsInActionFiles($files) + { + $testErrors = []; + /** @var SplFileInfo $filePath */ + foreach ($files as $filePath) { + $contents = file_get_contents($filePath); + preg_match_all(self::ACTIONGROUP_REGEX_PATTERN, $contents, $actionGroupReferences); + if (count($actionGroupReferences) > 0) { + $testErrors = array_merge($testErrors, $this->setErrorOutput($actionGroupReferences, $filePath)); + } + } + + return $testErrors; + } + + /** + * Build and return error output for violating references + * + * @param array $actionGroupReferences + * @param SplFileInfo $path + * @return mixed + */ + private function setErrorOutput($actionGroupReferences, $path) + { + $testErrors = []; + $errorOutput = ""; + $filePath = StaticChecksList::getFilePath($path->getRealPath()); + + foreach ($actionGroupReferences as $key => $actionGroupReferencesData) { + foreach ($actionGroupReferencesData as $actionGroupReferencesDataResult) { + $errorOutput .= "\nFile \"{$filePath}\" contains: ". "\n\t + {$actionGroupReferencesDataResult} in {$filePath}"; + $testErrors[$filePath][] = $errorOutput; + } + } + return $testErrors; + } +} diff --git a/src/Magento/FunctionalTestingFramework/StaticCheck/DeprecatedEntityUsageCheck.php b/src/Magento/FunctionalTestingFramework/StaticCheck/DeprecatedEntityUsageCheck.php new file mode 100644 index 000000000..547e6a633 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/StaticCheck/DeprecatedEntityUsageCheck.php @@ -0,0 +1,722 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\FunctionalTestingFramework\StaticCheck; + +use Magento\FunctionalTestingFramework\Config\MftfApplicationConfig; +use InvalidArgumentException; +use Exception; +use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; +use Magento\FunctionalTestingFramework\Exceptions\XmlException; +use Magento\FunctionalTestingFramework\Page\Objects\SectionObject; +use Magento\FunctionalTestingFramework\Test\Objects\ActionObject; +use Magento\FunctionalTestingFramework\Page\Objects\ElementObject; +use Magento\FunctionalTestingFramework\Test\Objects\ActionGroupObject; +use Magento\FunctionalTestingFramework\Page\Objects\PageObject; +use Magento\FunctionalTestingFramework\Test\Objects\TestObject; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Finder\Finder; +use Magento\FunctionalTestingFramework\Util\Script\ScriptUtil; +use Magento\FunctionalTestingFramework\DataGenerator\Handlers\OperationDefinitionObjectHandler; +use Magento\FunctionalTestingFramework\DataGenerator\Objects\OperationDefinitionObject; +use Magento\FunctionalTestingFramework\DataGenerator\Handlers\DataObjectHandler; +use Magento\FunctionalTestingFramework\DataGenerator\Objects\EntityDataObject; +use Symfony\Component\Finder\SplFileInfo; +use DOMNodeList; +use DOMElement; + +/** + * Class DeprecatedEntityUsageCheck + * @package Magento\FunctionalTestingFramework\StaticCheck + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class DeprecatedEntityUsageCheck implements StaticCheckInterface +{ + const EXTENDS_REGEX_PATTERN = '/extends=["\']([^\'"]*)/'; + const ACTIONGROUP_REGEX_PATTERN = '/ref=["\']([^\'"]*)/'; + const DEPRECATED_REGEX_PATTERN = '/deprecated=["\']([^\'"]*)/'; + + const ERROR_LOG_FILENAME = 'mftf-deprecated-entity-usage-checks'; + const ERROR_LOG_MESSAGE = 'MFTF Deprecated Entity Usage Check'; + + /** + * Array containing all errors found after running the execute() function + * + * @var array + */ + private $errors = []; + + /** + * String representing the output summary found after running the execute() function + * + * @var string + */ + private $output; + + /** + * ScriptUtil instance + * + * @var ScriptUtil + */ + private $scriptUtil; + + /** + * Data operations + * + * @var array + */ + private $dataOperations = ['create', 'update', 'get', 'delete']; + + /** + * Test xml files to scan + * + * @var Finder|array + */ + private $testXmlFiles = []; + + /** + * Action group xml files to scan + * + * @var Finder|array + */ + private $actionGroupXmlFiles = []; + + /** + * Suite xml files to scan + * + * @var Finder|array + */ + private $suiteXmlFiles = []; + + /** + * Root suite xml files to scan + * + * @var Finder|array + */ + private $rootSuiteXmlFiles = []; + + /** + * Data xml files to scan + * + * @var Finder|array + */ + private $dataXmlFiles = []; + + /** + * Checks test dependencies, determined by references in tests versus the dependencies listed in the Magento module + * + * @param InputInterface $input + * @return void + * @throws Exception + */ + public function execute(InputInterface $input) + { + $this->scriptUtil = new ScriptUtil(); + $this->loadAllXmlFiles($input); + + $this->errors = []; + $this->errors += $this->findReferenceErrorsInActionFiles($this->testXmlFiles); + $this->errors += $this->findReferenceErrorsInActionFiles($this->actionGroupXmlFiles); + $this->errors += $this->findReferenceErrorsInActionFiles($this->suiteXmlFiles, true); + if (!empty($this->rootSuiteXmlFiles)) { + $this->errors += $this->findReferenceErrorsInActionFiles($this->rootSuiteXmlFiles, true); + } + $this->errors += $this->findReferenceErrorsInDataFiles($this->dataXmlFiles); + + // Hold on to the output and print any errors to a file + $this->output = $this->scriptUtil->printErrorsToFile( + $this->errors, + StaticChecksList::getErrorFilesPath() . DIRECTORY_SEPARATOR . self::ERROR_LOG_FILENAME . '.txt', + self::ERROR_LOG_MESSAGE + ); + } + + /** + * Return array containing all errors found after running the execute() function + * + * @return array + */ + public function getErrors() + { + return $this->errors; + } + + /** + * Return string of a short human readable result of the check. For example: "No Dependency errors found." + * + * @return string + */ + public function getOutput() + { + return $this->output; + } + + /** + * Read all XML files for scanning + * + * @param InputInterface $input + * @return void + * @throws Exception + * @SuppressWarnings(PHPMD.CyclomaticComplexity) + */ + private function loadAllXmlFiles($input) + { + $modulePaths = []; + $includeRootPath = true; + $path = $input->getOption('path'); + if ($path) { + if (!realpath($path)) { + throw new InvalidArgumentException('Invalid --path option: ' . $path); + } + MftfApplicationConfig::create( + true, + MftfApplicationConfig::UNIT_TEST_PHASE, + false, + MftfApplicationConfig::LEVEL_DEFAULT, + true + ); + $modulePaths[] = realpath($path); + $includeRootPath = false; + } else { + $modulePaths = $this->scriptUtil->getAllModulePaths(); + } + + // These files can contain references to other entities + $this->testXmlFiles = $this->scriptUtil->getModuleXmlFilesByScope($modulePaths, 'Test'); + $this->actionGroupXmlFiles = $this->scriptUtil->getModuleXmlFilesByScope($modulePaths, 'ActionGroup'); + $this->suiteXmlFiles = $this->scriptUtil->getModuleXmlFilesByScope($modulePaths, 'Suite'); + if ($includeRootPath) { + $this->rootSuiteXmlFiles = $this->scriptUtil->getRootSuiteXmlFiles(); + } + $this->dataXmlFiles= $this->scriptUtil->getModuleXmlFilesByScope($modulePaths, 'Data'); + + if (empty($this->thistestXmlFiles) + && empty($this->actionGroupXmlFiles) + && empty($this->suiteXmlFiles) + && empty($this->dataXmlFiles) + ) { + if ($path) { + throw new InvalidArgumentException( + 'Invalid --path option: ' + . $path + . PHP_EOL + . 'Please make sure --path points to a valid MFTF Test Module.' + ); + } elseif (empty($this->rootSuiteXmlFiles)) { + throw new TestFrameworkException('No xml file to scan.'); + } + } + } + + /** + * Find reference errors in set of action files + * + * @param Finder $files + * @param boolean $checkTestRef + * @return array + * @throws XmlException + */ + private function findReferenceErrorsInActionFiles($files, $checkTestRef = false) + { + $testErrors = []; + /** @var SplFileInfo $filePath */ + foreach ($files as $filePath) { + $contents = file_get_contents($filePath); + if ($this->isDeprecated($contents)) { + continue; + } + preg_match_all(ActionObject::ACTION_ATTRIBUTE_VARIABLE_REGEX_PATTERN, $contents, $braceReferences); + preg_match_all(self::ACTIONGROUP_REGEX_PATTERN, $contents, $actionGroupReferences); + preg_match_all(self::EXTENDS_REGEX_PATTERN, $contents, $extendReferences); + + $domDocument = new \DOMDocument(); + $domDocument->load($filePath); + $createdDataReferences = $this->getAttributesFromDOMNodeList( + $domDocument->getElementsByTagName('createData'), + 'entity' + ); + $updatedDataReferences = $this->getAttributesFromDOMNodeList( + $domDocument->getElementsByTagName('updateData'), + 'entity' + ); + $getDataReferences = $this->getAttributesFromDOMNodeList( + $domDocument->getElementsByTagName('getData'), + 'entity' + ); + + // Remove Duplicates + $actionGroupReferences[1] = array_unique($actionGroupReferences[1]); + $extendReferences[1] = array_unique($extendReferences[1]); + $braceReferences[0] = array_unique($braceReferences[0]); + $braceReferences[2] = array_filter(array_unique($braceReferences[2])); + $createdDataReferences = array_unique($createdDataReferences); + $updatedDataReferences = array_unique($updatedDataReferences); + $getDataReferences = array_unique($getDataReferences); + + // Resolve entity references + $entityReferences = $this->scriptUtil->resolveEntityReferences($braceReferences[0], $contents, true); + // Resolve parameterized references + $entityReferences = array_merge( + $entityReferences, + $this->scriptUtil->resolveParametrizedReferences($braceReferences[2], $contents, true) + ); + // Resolve action group entity by names + $entityReferences = array_merge( + $entityReferences, + $this->scriptUtil->resolveEntityByNames($actionGroupReferences[1]) + ); + // Resolve extends entity by names + $entityReferences = array_merge( + $entityReferences, + $this->scriptUtil->resolveEntityByNames($extendReferences[1]) + ); + // Resolve create data entity by names + $entityReferences = array_merge( + $entityReferences, + $this->scriptUtil->resolveEntityByNames($createdDataReferences) + ); + // Resolve update data entity by names + $entityReferences = array_merge( + $entityReferences, + $this->scriptUtil->resolveEntityByNames($updatedDataReferences) + ); + // Resolve get data entity by names + $entityReferences = array_merge( + $entityReferences, + $this->scriptUtil->resolveEntityByNames($getDataReferences) + ); + // Find test references if needed + if ($checkTestRef) { + $entityReferences = array_merge($entityReferences, $this->resolveTestEntityInSuite($domDocument)); + } + + // Find violating references + $violatingReferences = $this->findViolatingReferences($entityReferences); + + // Find metadata references from persist data + $metadataReferences = $this->getMetadataFromData($createdDataReferences, 'create'); + $metadataReferences = array_merge_recursive( + $metadataReferences, + $this->getMetadataFromData($updatedDataReferences, 'update') + ); + $metadataReferences = array_merge_recursive( + $metadataReferences, + $this->getMetadataFromData($getDataReferences, 'get') + ); + + // Find violating references + $violatingReferences = array_merge( + $violatingReferences, + $this->findViolatingMetadataReferences($metadataReferences) + ); + + // Set error output + $testErrors = array_merge($testErrors, $this->setErrorOutput($violatingReferences, $filePath)); + } + return $testErrors; + } + + /** + * Checks if entity is deprecated in action files. + * @param string $contents + * @return boolean + */ + private function isDeprecated($contents) + { + preg_match_all(self::DEPRECATED_REGEX_PATTERN, $contents, $deprecatedEntity); + return (!empty($deprecatedEntity[1])); + } + + /** + * Find reference errors in a set of data files + * + * @param Finder $files + * @return array + * @SuppressWarnings(PHPMD.CyclomaticComplexity) + */ + private function findReferenceErrorsInDataFiles($files) + { + $testErrors = []; + /** @var SplFileInfo $filePath */ + foreach ($files as $filePath) { + $dataReferences = []; + $metadataReferences = []; + $domDocument = new \DOMDocument(); + $domDocument->load($filePath); + $entities = $domDocument->getElementsByTagName('entity'); + foreach ($entities as $entity) { + /** @var DOMElement $entity */ + $deprecated = $entity->getAttribute('deprecated'); + // skip check if entity is deprecated + if (!empty($deprecated)) { + continue; + } + $entityName = $entity->getAttribute('name'); + $metadataType = $entity->getAttribute('type'); + $parentEntityName = $entity->getAttribute('extends'); + if (!empty($metadataType && !isset($metadataReferences[$entityName][$metadataType]))) { + $metadataReferences[$entityName][$metadataType] = 'all'; + } + if (!empty($parentEntityName)) { + $dataReferences[$entityName][] = $parentEntityName; + } + // Find metadata reference in `var` elements + $varElements = $entity->getElementsByTagName('var'); + foreach ($varElements as $varElement) { + /** @var DOMElement $varElement */ + $metadataType = $varElement->getAttribute('entityType'); + if (!empty($metadataType) && !isset($metadataReferences[$entityName][$metadataType])) { + $metadataReferences[$entityName][$metadataType] = 'all'; + } + } + // Find metadata reference in `requiredEntity` elements, and + // Find data references in `requiredEntity` elements + $requiredElements = $entity->getElementsByTagName('requiredEntity'); + foreach ($requiredElements as $requiredElement) { + /** @var DOMElement $requiredElement */ + $metadataType = $requiredElement->getAttribute('type'); + if (!empty($metadataType) && !isset($metadataReferences[$entityName][$metadataType])) { + $metadataReferences[$entityName][$metadataType] = 'all'; + } + $dataReferences[$entityName][] = $requiredElement->nodeValue; + } + } + + // Find violating references + // Metadata references is unique + $violatingReferences = $this->findViolatingMetadataReferences($metadataReferences); + // Data references is not unique + $violatingReferences = array_merge_recursive( + $violatingReferences, + $this->findViolatingDataReferences($this->twoDimensionArrayUnique($dataReferences)) + ); + + // Set error output + $testErrors = array_merge($testErrors, $this->setErrorOutput($violatingReferences, $filePath)); + } + return $testErrors; + } + + /** + * Trim duplicate values from two-dimensional array. Dimension 1 array key is unique. + * + * @param array $inArray + * @return array + */ + private function twoDimensionArrayUnique($inArray) + { + $outArray = []; + foreach ($inArray as $key => $arr) { + $outArray[$key] = array_unique($arr); + } + return $outArray; + } + + /** + * Return attribute value for each node in DOMNodeList as an array + * + * @param DOMNodeList $nodes + * @param string $attributeName + * @return array + */ + private function getAttributesFromDOMNodeList($nodes, $attributeName) + { + $attributes = []; + /** @var DOMElement $node */ + foreach ($nodes as $node) { + $attributeValue = $node->getAttribute($attributeName); + if (!empty($attributeValue)) { + $attributes[] = $attributeValue; + } + } + return $attributes; + } + + /** + * Find metadata from data array + * + * @param array $references + * @param string $type + * @return array + */ + private function getMetadataFromData($references, $type) + { + $metaDataReferences = []; + try { + foreach ($references as $dataName) { + /** @var EntityDataObject $dataEntity */ + $dataEntity = $this->scriptUtil->findEntity($dataName); + if ($dataEntity) { + $metadata = $dataEntity->getType(); + if (!empty($metadata)) { + $metaDataReferences[$dataName][$metadata] = $type; + } + } + } + } catch (Exception $e) { + } + return $metaDataReferences; + } + + /** + * Find violating metadata references. Input array format is either + * + * [ + * $dataName1 => [ + * $metaDataName1 = [ + * 'create', + * 'update', + * ], + * ], + * $dataName2 => [ + * $metaDataName2 => 'create', + * ], + * $dataName3 => [ + * $metaDataName3 = [ + * 'get', + * 'create', + * 'update', + * ], + * ], + * ... + * ] + * + * or + * + * [ + * $dataName1 => [ + * $metaDataName1 => 'all', + * $metaDataName2 => 'all', + * ... + * ], + * $dataName2 => [ + * $metaDataName2 => 'all', + * ... + * ], + * $dataName5 => [ + * $metaDataName5 => 'all', + * $metaDataName4 => 'all', + * $metaDataName1 => 'all', + * ... + * ], + * ... + * ] + * + * @param array $references + * @return array + * @SuppressWarnings(PHPMD.CyclomaticComplexity) + * @SuppressWarnings(PHPMD.NPathComplexity) + */ + private function findViolatingMetadataReferences($references) + { + $allObjects = OperationDefinitionObjectHandler::getInstance()->getAllObjects(); + + // Find Violations + $violatingReferences = []; + foreach ($references as $dataName => $metadataArray) { + foreach ($metadataArray as $metadata => $types) { + $operations = []; + $strict = true; + if (is_array($types)) { + $operations = $types; + } elseif ($types === 'all') { + $operations = $this->dataOperations; + $strict = false; + } else { + $operations = [$types]; + } + + $deprecated = null; + $exists = false; + foreach ($operations as $operation) { + if (array_key_exists($operation . $metadata, $allObjects)) { + $exists = true; + /** @var OperationDefinitionObject $entity */ + $entity = $allObjects[$operation . $metadata]; + // When not strictly checking, it's not deprecated as long as we found one that's not deprecated + if (!$strict && empty($entity->getDeprecated())) { + $deprecated = false; + break; + } + // When strictly checking, it's deprecated as long as we found one that's deprecated + if ($strict && !empty($entity->getDeprecated())) { + $deprecated = true; + break; + } + } + } + if ($exists && !$strict && $deprecated !== false) { + $deprecated = true; + } + if ($strict && $deprecated !== true) { + $deprecated = false; + } + + if ($deprecated) { + $violatingReferences["\"{$dataName}\" references deprecated"][] = [ + 'name' => $metadata, + // TODO add filename in OperationDefinitionObject + 'file' => 'metadata xml file', + ]; + } + } + } + return $violatingReferences; + } + + /** + * Find violating data references. Input array format is + * + * [ + * $dataName1 => [ $requiredDataName1, $requiredDataName2, $requiredDataName3], + * $dataName2 => [ $requiredDataName2, $requiredDataName5, $requiredDataName7], + * ... + * ] + * + * @param array $references + * @return array + */ + private function findViolatingDataReferences($references) + { + // Find Violations + $violatingReferences = []; + foreach ($references as $dataName => $requiredDataNames) { + foreach ($requiredDataNames as $requiredDataName) { + try { + /** @var EntityDataObject $requiredData */ + $requiredData = DataObjectHandler::getInstance()->getObject($requiredDataName); + if ($requiredData && $requiredData->getDeprecated()) { + $violatingReferences["\"{$dataName}\" references deprecated"][] = [ + 'name' => $requiredDataName, + 'file' => $requiredData->getFilename(), + ]; + } + } catch (Exception $e) { + } + } + } + return $violatingReferences; + } + + /** + * Find violating references. Input array format is + * + * [ + * 'actionGroupName' => $actionGroupEntity, + * 'dataGroupName' => $dataEntity, + * 'testName' => $testEntity, + * 'pageName' => $pageEntity, + * 'section.field' => $fieldElementEntity, + * ... + * ] + * + * @param array $references + * @return array + */ + private function findViolatingReferences($references) + { + // Find Violations + $violatingReferences = []; + foreach ($references as $key => $entity) { + if ($entity->getDeprecated()) { + $classType = get_class($entity); + $name = $entity->getName(); + if ($classType === ElementObject::class) { + $name = $key; + list($section,) = explode('.', $key, 2); + /** @var SectionObject $references[$section] */ + $file = StaticChecksList::getFilePath($references[$section]->getFilename()); + } else { + $file = StaticChecksList::getFilePath($entity->getFilename()); + } + $violatingReferences[$this->getSubjectFromClassType($classType)][] = [ + 'name' => $name, + 'file' => $file, + ]; + } + } + return $violatingReferences; + } + + /** + * Build and return error output for violating references + * + * @param array $violatingReferences + * @param SplFileInfo $path + * @return mixed + */ + private function setErrorOutput($violatingReferences, $path) + { + $testErrors = []; + + $filePath = StaticChecksList::getFilePath($path->getRealPath()); + + if (!empty($violatingReferences)) { + // Build error output + $errorOutput = "\nFile \"{$filePath}\" contains:\n"; + foreach ($violatingReferences as $subject => $data) { + $errorOutput .= "\t- {$subject}:\n"; + foreach ($data as $item) { + $errorOutput .= "\t\t\"" . $item['name'] . "\" in " . $item['file'] . "\n"; + } + } + $testErrors[$filePath][] = $errorOutput; + } + + return $testErrors; + } + + /** + * Resolve test entity in suite + * + * @param \DOMDocument $domDocument + * @return array + */ + private function resolveTestEntityInSuite($domDocument) + { + $testReferences = $this->getAttributesFromDOMNodeList( + $domDocument->getElementsByTagName('test'), + 'name' + ); + + // Remove Duplicates + $testReferences = array_unique($testReferences); + + // Resolve test entity by names + try { + return $this->scriptUtil->resolveEntityByNames($testReferences); + } catch (XmlException $e) { + return []; + } + } + + /** + * Return subject string for a class name + * + * @param string $classname + * @return string|null + */ + private function getSubjectFromClassType($classname) + { + $subject = null; + if ($classname === ActionGroupObject::class) { + $subject = 'Deprecated ActionGroup(s)'; + } elseif ($classname === TestObject::class) { + $subject = 'Deprecated Test(s)'; + } elseif ($classname === SectionObject::class) { + $subject = 'Deprecated Section(s)'; + } elseif ($classname === PageObject::class) { + $subject = 'Deprecated Page(s)'; + } elseif ($classname === ElementObject::class) { + $subject = 'Deprecated Element(s)'; + } elseif ($classname === EntityDataObject::class) { + $subject = 'Deprecated Data(s)'; + } elseif ($classname === OperationDefinitionObject::class) { + $subject = 'Deprecated Metadata(s)'; + } + return $subject; + } +} diff --git a/src/Magento/FunctionalTestingFramework/StaticCheck/PauseActionUsageCheck.php b/src/Magento/FunctionalTestingFramework/StaticCheck/PauseActionUsageCheck.php new file mode 100644 index 000000000..e7d106bf0 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/StaticCheck/PauseActionUsageCheck.php @@ -0,0 +1,229 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\FunctionalTestingFramework\StaticCheck; + +use Magento\FunctionalTestingFramework\Config\MftfApplicationConfig; +use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Finder\Finder; +use Exception; +use Magento\FunctionalTestingFramework\Util\Script\ScriptUtil; +use Symfony\Component\Finder\SplFileInfo; + +/** + * Class PauseActionUsageCheck + * @package Magento\FunctionalTestingFramework\StaticCheck + */ +class PauseActionUsageCheck implements StaticCheckInterface +{ + const ERROR_LOG_FILENAME = 'mftf-pause-action-usage-checks'; + const ERROR_LOG_MESSAGE = 'MFTF Pause Action Usage Check'; + + /** + * Array containing all errors found after running the execute() function. + * @var array + */ + private $errors = []; + + /** + * String representing the output summary found after running the execute() function. + * @var string + */ + private $output; + + /** + * ScriptUtil instance + * + * @var ScriptUtil + */ + private $scriptUtil; + + /** + * Test xml files to scan + * + * @var Finder|array + */ + private $testXmlFiles = []; + + /** + * Action group xml files to scan + * + * @var Finder|array + */ + private $actionGroupXmlFiles = []; + + /** + * Suite xml files to scan + * + * @var Finder|array + */ + private $suiteXmlFiles = []; + + /** + * Root suite xml files to scan + * + * @var Finder|array + */ + private $rootSuiteXmlFiles = []; + + /** + * Checks usage of pause action in action groups, tests and suites and prints out error to file. + * + * @param InputInterface $input + * @return void + * @throws Exception + */ + public function execute(InputInterface $input) + { + $this->scriptUtil = new ScriptUtil(); + $modulePaths = []; + $includeRootPath = true; + $path = $input->getOption('path'); + if ($path) { + if (!realpath($path)) { + throw new \InvalidArgumentException('Invalid --path option: ' . $path); + } + $modulePaths[] = realpath($path); + $includeRootPath = false; + } else { + $modulePaths = $this->scriptUtil->getAllModulePaths(); + } + + $this->testXmlFiles = $this->scriptUtil->getModuleXmlFilesByScope($modulePaths, 'Test'); + $this->actionGroupXmlFiles = $this->scriptUtil->getModuleXmlFilesByScope($modulePaths, 'ActionGroup'); + $this->suiteXmlFiles = $this->scriptUtil->getModuleXmlFilesByScope($modulePaths, 'Suite'); + if ($includeRootPath) { + $this->rootSuiteXmlFiles = $this->scriptUtil->getRootSuiteXmlFiles(); + } + $this->errors = []; + $this->errors += $this->validatePauseActionUsageInActionGroups($this->actionGroupXmlFiles); + $this->errors += $this->validatePauseActionUsageInTests($this->testXmlFiles); + $this->errors += $this->validatePauseActionUsageInSuites($this->suiteXmlFiles); + $this->errors += $this->validatePauseActionUsageInSuites($this->rootSuiteXmlFiles); + + $this->output = $this->scriptUtil->printErrorsToFile( + $this->errors, + StaticChecksList::getErrorFilesPath() . DIRECTORY_SEPARATOR . self::ERROR_LOG_FILENAME . '.txt', + self::ERROR_LOG_MESSAGE + ); + } + + /** + * Finds usages of pause action in action group files + * @param array $actionGroupXmlFiles + * @return array + */ + private function validatePauseActionUsageInActionGroups($actionGroupXmlFiles) + { + $actionGroupErrors = []; + foreach ($actionGroupXmlFiles as $filePath) { + $domDocument = new \DOMDocument(); + $domDocument->load($filePath); + $actionGroup = $domDocument->getElementsByTagName('actionGroup')->item(0); + $violatingStepKeys = $this->findViolatingPauseStepKeys($actionGroup); + $actionGroupErrors = array_merge($actionGroupErrors, $this->setErrorOutput($violatingStepKeys, $filePath)); + } + return $actionGroupErrors; + } + + /** + * Finds usages of pause action in test files + * @param array $testXmlFiles + * @return array + */ + private function validatePauseActionUsageInTests($testXmlFiles) + { + $testErrors = []; + foreach ($testXmlFiles as $filePath) { + $domDocument = new \DOMDocument(); + $domDocument->load($filePath); + $test = $domDocument->getElementsByTagName('test')->item(0); + $violatingStepKeys = $this->findViolatingPauseStepKeys($test); + $testErrors = array_merge($testErrors, $this->setErrorOutput($violatingStepKeys, $filePath)); + } + return $testErrors; + } + + /** + * Finds usages of pause action in suite files + * @param array $suiteXmlFiles + * @return array + */ + private function validatePauseActionUsageInSuites($suiteXmlFiles) + { + $suiteErrors = []; + foreach ($suiteXmlFiles as $filePath) { + $domDocument = new \DOMDocument(); + $domDocument->load($filePath); + $suite = $domDocument->getElementsByTagName('suite')->item(0); + $violatingStepKeys = $this->findViolatingPauseStepKeys($suite); + $suiteErrors = array_merge($suiteErrors, $this->setErrorOutput($violatingStepKeys, $filePath)); + } + return $suiteErrors; + } + + /** + * Finds violating pause action step keys + * @param \DomNode $entity + * @return array + */ + private function findViolatingPauseStepKeys($entity) + { + $violatingStepKeys = []; + $entityName = $entity->getAttribute('name'); + $references = $entity->getElementsByTagName('pause'); + + foreach ($references as $reference) { + $pauseStepKey = $reference->getAttribute('stepKey'); + $violatingStepKeys[$entityName][] = $pauseStepKey; + } + return $violatingStepKeys; + } + + /** + * Return array containing all errors found after running the execute() function. + * @return array + */ + public function getErrors() + { + return $this->errors; + } + + /** + * Return string of a short human readable result of the check. For example: "No errors found." + * @return string + */ + public function getOutput() + { + return $this->output; + } + + /** + * Build and return error output for pause action usages + * + * @param array $violatingReferences + * @param SplFileInfo $path + * @return mixed + */ + private function setErrorOutput($violatingReferences, $path) + { + $testErrors = []; + + $filePath = StaticChecksList::getFilePath($path->getRealPath()); + + if (!empty($violatingReferences)) { + // Build error output + $errorOutput = "\nFile \"{$filePath}\""; + $errorOutput .= "\ncontains pause action(s):\n\t\t"; + foreach ($violatingReferences as $entityName => $stepKey) { + $errorOutput .= "\n\t {$entityName} has pause action at stepKey(s): " . implode(", ", $stepKey); + } + $testErrors[$filePath][] = $errorOutput; + } + return $testErrors; + } +} diff --git a/src/Magento/FunctionalTestingFramework/StaticCheck/StaticChecksList.php b/src/Magento/FunctionalTestingFramework/StaticCheck/StaticChecksList.php index f3cf20739..7eda14f1b 100644 --- a/src/Magento/FunctionalTestingFramework/StaticCheck/StaticChecksList.php +++ b/src/Magento/FunctionalTestingFramework/StaticCheck/StaticChecksList.php @@ -7,12 +7,21 @@ namespace Magento\FunctionalTestingFramework\StaticCheck; +use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; +use Magento\FunctionalTestingFramework\Util\Path\FilePathFormatter; + /** * Class StaticChecksList has a list of static checks to run on test xml * @codingStandardsIgnoreFile */ class StaticChecksList implements StaticCheckListInterface { + const DEPRECATED_ENTITY_USAGE_CHECK_NAME = 'deprecatedEntityUsage'; + const PAUSE_ACTION_USAGE_CHECK_NAME = 'pauseActionUsage'; + const CREATED_DATA_FROM_OUTSIDE_ACTIONGROUP = 'createdDataFromOutsideActionGroup'; + const UNUSED_ENTITY_CHECK = 'unusedEntityCheck'; + const STATIC_RESULTS = 'tests' . DIRECTORY_SEPARATOR .'_output' . DIRECTORY_SEPARATOR . 'static-results'; + /** * Property contains all static check scripts. * @@ -20,16 +29,35 @@ class StaticChecksList implements StaticCheckListInterface */ private $checks; + /** + * Directory path for static checks error files + * + * @var string + */ + private static $errorFilesPath = null; + /** * Constructor * * @param array $checks + * @throws TestFrameworkException */ public function __construct(array $checks = []) { $this->checks = [ 'testDependencies' => new TestDependencyCheck(), + 'actionGroupArguments' => new ActionGroupArgumentsCheck(), + self::DEPRECATED_ENTITY_USAGE_CHECK_NAME => new DeprecatedEntityUsageCheck(), + 'annotations' => new AnnotationsCheck(), + self::PAUSE_ACTION_USAGE_CHECK_NAME => new PauseActionUsageCheck(), + self::UNUSED_ENTITY_CHECK => new UnusedEntityCheck(), + self::CREATED_DATA_FROM_OUTSIDE_ACTIONGROUP => new CreatedDataFromOutsideActionGroupCheck() ] + $checks; + + // Static checks error files directory + if (null === self::$errorFilesPath) { + self::$errorFilesPath = FilePathFormatter::format(TESTS_BP) . self::STATIC_RESULTS; + } } /** @@ -39,4 +67,30 @@ public function getStaticChecks() { return $this->checks; } + + /** + * Return the directory path for the static check error files + */ + public static function getErrorFilesPath() + { + return self::$errorFilesPath; + } + + /** + * Return relative path to files for unit testing purposes. + * @param string $fileNames + * @return string + */ + public static function getFilePath($fileNames) + { + if (!empty($fileNames)) { + $relativeFileNames = ltrim( + str_replace(MAGENTO_BP, '', $fileNames) + ); + if (!empty($relativeFileNames)) { + return $relativeFileNames; + } + } + return $fileNames; + } } diff --git a/src/Magento/FunctionalTestingFramework/StaticCheck/TestDependencyCheck.php b/src/Magento/FunctionalTestingFramework/StaticCheck/TestDependencyCheck.php index 6cfa4e9a8..848b2c463 100644 --- a/src/Magento/FunctionalTestingFramework/StaticCheck/TestDependencyCheck.php +++ b/src/Magento/FunctionalTestingFramework/StaticCheck/TestDependencyCheck.php @@ -6,37 +6,26 @@ namespace Magento\FunctionalTestingFramework\StaticCheck; -use Magento\FunctionalTestingFramework\Config\MftfApplicationConfig; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\DataObjectHandler; -use Magento\FunctionalTestingFramework\Exceptions\TestReferenceException; +use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; use Magento\FunctionalTestingFramework\Exceptions\XmlException; -use Magento\FunctionalTestingFramework\Page\Handlers\PageObjectHandler; -use Magento\FunctionalTestingFramework\Page\Handlers\SectionObjectHandler; -use Magento\FunctionalTestingFramework\Test\Handlers\ActionGroupObjectHandler; -use Magento\FunctionalTestingFramework\Test\Handlers\TestObjectHandler; use Magento\FunctionalTestingFramework\Test\Objects\ActionObject; -use Magento\FunctionalTestingFramework\Util\ModuleResolver; -use Magento\FunctionalTestingFramework\Util\TestGenerator; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Finder\Finder; use Exception; +use Magento\FunctionalTestingFramework\Util\Script\ScriptUtil; +use Magento\FunctionalTestingFramework\Util\Script\TestDependencyUtil; /** * Class TestDependencyCheck * @package Magento\FunctionalTestingFramework\StaticCheck - * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class TestDependencyCheck implements StaticCheckInterface { const EXTENDS_REGEX_PATTERN = '/extends=["\']([^\'"]*)/'; const ACTIONGROUP_REGEX_PATTERN = '/ref=["\']([^\'"]*)/'; - const ACTIONGROUP_ARGUMENT_REGEX_PATTERN = '/<argument[^\/>]*name="([^"\']*)/'; - /** - * Array of FullModuleName => [dependencies] - * @var array - */ - private $allDependencies; + const ERROR_LOG_FILENAME = 'mftf-dependency-checks'; + const ERROR_LOG_MESSAGE = 'MFTF File Dependency Check'; /** * Array of FullModuleName => [dependencies], including flattened dependency tree @@ -56,17 +45,11 @@ class TestDependencyCheck implements StaticCheckInterface */ private $moduleNameToComposerName; - /** - * Transactional Array to keep track of what dependencies have already been extracted. - * @var array - */ - private $alreadyExtractedDependencies; - /** * Array containing all errors found after running the execute() function. * @var array */ - private $errors; + private $errors = []; /** * String representing the output summary found after running the execute() function. @@ -80,42 +63,55 @@ class TestDependencyCheck implements StaticCheckInterface */ private $allEntities = []; + /** + * ScriptUtil instance + * + * @var ScriptUtil + */ + private $scriptUtil; + + /** + * @var TestDependencyUtil + */ + private $testDependencyUtil; + /** * Checks test dependencies, determined by references in tests versus the dependencies listed in the Magento module * * @param InputInterface $input - * @return string - * @throws Exception; + * @return void + * @throws Exception */ public function execute(InputInterface $input) { - MftfApplicationConfig::create( - true, - MftfApplicationConfig::UNIT_TEST_PHASE, - false, - MftfApplicationConfig::LEVEL_NONE, - true - ); + $this->scriptUtil = new ScriptUtil(); + $this->testDependencyUtil = new TestDependencyUtil(); + $allModules = $this->scriptUtil->getAllModulePaths(); - ModuleResolver::getInstance()->getModulesPath(); if (!class_exists('\Magento\Framework\Component\ComponentRegistrar')) { - return "TEST DEPENDENCY CHECK ABORTED: MFTF must be attached or pointing to Magento codebase."; + throw new TestFrameworkException( + "TEST DEPENDENCY CHECK ABORTED: MFTF must be attached or pointing to Magento codebase." + ); } $registrar = new \Magento\Framework\Component\ComponentRegistrar(); $this->moduleNameToPath = $registrar->getPaths(\Magento\Framework\Component\ComponentRegistrar::MODULE); - $this->moduleNameToComposerName = $this->buildModuleNameToComposerName($this->moduleNameToPath); - $this->flattenedDependencies = $this->buildComposerDependencyList(); + $this->moduleNameToComposerName = $this->testDependencyUtil->buildModuleNameToComposerName( + $this->moduleNameToPath + ); + $this->flattenedDependencies = $this->testDependencyUtil->buildComposerDependencyList( + $this->moduleNameToPath, + $this->moduleNameToComposerName + ); - $allModules = ModuleResolver::getInstance()->getModulesPath(); $filePaths = [ DIRECTORY_SEPARATOR . 'Test' . DIRECTORY_SEPARATOR, DIRECTORY_SEPARATOR . 'ActionGroup' . DIRECTORY_SEPARATOR, DIRECTORY_SEPARATOR . 'Data' . DIRECTORY_SEPARATOR, ]; // These files can contain references to other modules. - $testXmlFiles = $this->buildFileList($allModules, $filePaths[0]); - $actionGroupXmlFiles = $this->buildFileList($allModules, $filePaths[1]); - $dataXmlFiles= $this->buildFileList($allModules, $filePaths[2]); + $testXmlFiles = $this->scriptUtil->getModuleXmlFilesByScope($allModules, $filePaths[0]); + $actionGroupXmlFiles = $this->scriptUtil->getModuleXmlFilesByScope($allModules, $filePaths[1]); + $dataXmlFiles= $this->scriptUtil->getModuleXmlFilesByScope($allModules, $filePaths[2]); $this->errors = []; $this->errors += $this->findErrorsInFileSet($testXmlFiles); @@ -123,14 +119,18 @@ public function execute(InputInterface $input) $this->errors += $this->findErrorsInFileSet($dataXmlFiles); // hold on to the output and print any errors to a file - $this->output = $this->printErrorsToFile(); + $this->output = $this->scriptUtil->printErrorsToFile( + $this->errors, + StaticChecksList::getErrorFilesPath() . DIRECTORY_SEPARATOR . self::ERROR_LOG_FILENAME . '.txt', + self::ERROR_LOG_MESSAGE + ); } /** * Return array containing all errors found after running the execute() function. * @return array */ - public function getErrors() + public function getErrors(): array { return $this->errors; } @@ -139,7 +139,7 @@ public function getErrors() * Return string of a short human readable result of the check. For example: "No Dependency errors found." * @return string */ - public function getOutput() + public function getOutput(): string { return $this->output; } @@ -148,17 +148,16 @@ public function getOutput() * Finds all reference errors in given set of files * @param Finder $files * @return array - * @throws TestReferenceException * @throws XmlException */ - private function findErrorsInFileSet($files) + private function findErrorsInFileSet(Finder $files): array { $testErrors = []; foreach ($files as $filePath) { - $modulePath = dirname(dirname(dirname(dirname($filePath)))); - $moduleFullName = array_search($modulePath, $this->moduleNameToPath) ?? null; + $this->allEntities = []; + $moduleName = $this->testDependencyUtil->getModuleName($filePath, $this->moduleNameToPath); // Not a module, is either dev/tests/acceptance or loose folder with test materials - if ($moduleFullName == null) { + if ($moduleName === null) { continue; } @@ -173,125 +172,58 @@ private function findErrorsInFileSet($files) $braceReferences[1] = array_unique($braceReferences[1]); $braceReferences[2] = array_filter(array_unique($braceReferences[2])); - // resolve data entity references - $this->resolveDataEntityReferences($braceReferences[0], $contents); + // resolve entity references + $this->allEntities = array_merge( + $this->allEntities, + $this->scriptUtil->resolveEntityReferences($braceReferences[0], $contents) + ); - //resolve entity references - $this->resolveParametrizedReferences($braceReferences[2], $contents); + // resolve parameterized references + $this->allEntities = array_merge( + $this->allEntities, + $this->scriptUtil->resolveParametrizedReferences($braceReferences[2], $contents) + ); - // Check actionGroup references - $this->resolveEntityReferences($actionGroupReferences[1]); + // resolve entity by names + $this->allEntities = array_merge( + $this->allEntities, + $this->scriptUtil->resolveEntityByNames($actionGroupReferences[1]) + ); - // Check extended objects - $this->resolveEntityReferences($extendReferences[1]); + // resolve entity by names + $this->allEntities = array_merge( + $this->allEntities, + $this->scriptUtil->resolveEntityByNames($extendReferences[1]) + ); // Find violating references and set error output - $violatingReferences = $this->findViolatingReferences($moduleFullName); - $testErrors = $this->setErrorOutput($violatingReferences, $filePath); + $violatingReferences = $this->findViolatingReferences($moduleName); + $testErrors = array_merge($testErrors, $this->setErrorOutput($violatingReferences, $filePath)); } return $testErrors; } - /** - * Drill down into params in {{ref.params('string', $data.key$, entity.reference)}} - * and resolve references. - * - * @param array $braceReferences - * @param string $contents - * @return void - * @throws XmlException - */ - private function resolveParametrizedReferences($braceReferences, $contents) - { - foreach ($braceReferences as $parameterizedReference) { - preg_match( - ActionObject::ACTION_ATTRIBUTE_VARIABLE_REGEX_PARAMETER, - $parameterizedReference, - $arguments - ); - $splitArguments = explode(',', ltrim(rtrim($arguments[0], ")"), "(")); - foreach ($splitArguments as $argument) { - // Do nothing for 'string' or $persisted.data$ - if (preg_match(ActionObject::STRING_PARAMETER_REGEX, $argument)) { - continue; - } elseif (preg_match(TestGenerator::PERSISTED_OBJECT_NOTATION_REGEX, $argument)) { - continue; - } - // trim `data.field` to `data` - preg_match('/([^.]+)/', $argument, $entityName); - // Double check that {{data.field}} isn't an argument for an ActionGroup - $entity = $this->findEntity($entityName[1]); - preg_match_all(self::ACTIONGROUP_ARGUMENT_REGEX_PATTERN, $contents, $possibleArgument); - if (array_search($entityName[1], $possibleArgument[1]) !== false) { - continue; - } - if ($entity !== null) { - $this->allEntities[$entity->getName()] = $entity; - } - } - } - } - - /** - * Check `data` entities in {{data.field}} or {{data.field('param')}} and resolve references - * - * @param array $braceReferences - * @param string $contents - * @return void - * @throws XmlException - - */ - private function resolveDataEntityReferences($braceReferences, $contents) - { - foreach ($braceReferences as $reference) { - // trim `{{data.field}}` to `data` - preg_match('/{{([^.]+)/', $reference, $entityName); - // Double check that {{data.field}} isn't an argument for an ActionGroup - $entity = $this->findEntity($entityName[1]); - preg_match_all(self::ACTIONGROUP_ARGUMENT_REGEX_PATTERN, $contents, $possibleArgument); - if (array_search($entityName[1], $possibleArgument[1]) !== false) { - continue; - } - if ($entity !== null) { - $this->allEntities[$entity->getName()] = $entity; - } - } - } - - /** - * Resolve entity references - * - * @param array $references - * @return void - * @throws XmlException - */ - private function resolveEntityReferences($references) - { - foreach ($references as $reference) { - $entity = $this->findEntity($reference); - if ($entity !== null) { - $this->allEntities[$entity->getName()] = $entity; - } - } - } - /** * Find violating references * * @param string $moduleName * @return array */ - private function findViolatingReferences($moduleName) + private function findViolatingReferences(string $moduleName): array { // Find Violations $violatingReferences = []; $currentModule = $this->moduleNameToComposerName[$moduleName]; - $modulesReferencedInTest = $this->getModuleDependenciesFromReferences($this->allEntities); + $modulesReferencedInTest = $this->testDependencyUtil->getModuleDependenciesFromReferences( + $this->allEntities, + $this->moduleNameToComposerName, + $this->moduleNameToPath + ); $moduleDependencies = $this->flattenedDependencies[$moduleName]; foreach ($modulesReferencedInTest as $entityName => $files) { $valid = false; foreach ($files as $module) { - if (array_key_exists($module, $moduleDependencies) || $module == $currentModule) { + if (array_key_exists($module, $moduleDependencies) || $module === $currentModule) { $valid = true; break; } @@ -307,11 +239,10 @@ private function findViolatingReferences($moduleName) /** * Builds and returns error output for violating references * - * @param array $violatingReferences - * @param string $path - * @return mixed + * @param array $violatingReferences + * @return array */ - private function setErrorOutput($violatingReferences, $path) + private function setErrorOutput(array $violatingReferences, $path): array { $testErrors = []; @@ -327,166 +258,4 @@ private function setErrorOutput($violatingReferences, $path) return $testErrors; } - - /** - * Builds and returns array of FullModuleNae => composer name - * @param array $array - * @return array - */ - private function buildModuleNameToComposerName($array) - { - $returnList = []; - foreach ($array as $moduleName => $path) { - $composerData = json_decode(file_get_contents($path . DIRECTORY_SEPARATOR . "composer.json")); - $returnList[$moduleName] = $composerData->name; - } - return $returnList; - } - - /** - * Builds and returns flattened dependency list based on composer dependencies - * @return array - */ - private function buildComposerDependencyList() - { - $flattenedDependencies = []; - - foreach ($this->moduleNameToPath as $moduleName => $pathToModule) { - $composerData = json_decode(file_get_contents($pathToModule . DIRECTORY_SEPARATOR . "composer.json"), true); - $this->allDependencies[$moduleName] = $composerData['require']; - } - foreach ($this->allDependencies as $moduleName => $dependencies) { - $this->alreadyExtractedDependencies = []; - $flattenedDependencies[$moduleName] = $this->extractSubDependencies($moduleName); - } - return $flattenedDependencies; - } - - /** - * Recursive function to fetch dependencies of given dependency, and its child dependencies - * @param string $subDependencyName - * @return array - */ - private function extractSubDependencies($subDependencyName) - { - $flattenedArray = []; - - if (array_search($subDependencyName, $this->alreadyExtractedDependencies) !== false) { - return $flattenedArray; - } - - if (isset($this->allDependencies[$subDependencyName])) { - $subDependencyArray = $this->allDependencies[$subDependencyName]; - $flattenedArray = array_merge($flattenedArray, $this->allDependencies[$subDependencyName]); - - // Keep track of dependencies that have already been used, prevents circular dependency problems - $this->alreadyExtractedDependencies[] = $subDependencyName; - foreach ($subDependencyArray as $composerDependencyName => $version) { - $subDependencyFullName = array_search($composerDependencyName, $this->moduleNameToComposerName); - $flattenedArray = array_merge( - $flattenedArray, - $this->extractSubDependencies($subDependencyFullName) - ); - } - } - return $flattenedArray; - } - - /** - * Finds unique array ofcomposer dependencies of given testObjects - * @param array $array - * @return array - */ - private function getModuleDependenciesFromReferences($array) - { - $filenames = []; - foreach ($array as $item) { - // Should it append ALL filenames, including merges? - $allFiles = explode(",", $item->getFilename()); - foreach ($allFiles as $file) { - $modulePath = dirname(dirname(dirname(dirname($file)))); - $fullModuleName = array_search($modulePath, $this->moduleNameToPath); - $composerModuleName = $this->moduleNameToComposerName[$fullModuleName]; - $filenames[$item->getName()][] = $composerModuleName; - } - } - return $filenames; - } - - /** - * Builds list of all XML files in given modulePaths + path given - * @param string $modulePaths - * @param string $path - * @return Finder - */ - private function buildFileList($modulePaths, $path) - { - $finder = new Finder(); - foreach ($modulePaths as $modulePath) { - if (!realpath($modulePath . $path)) { - continue; - } - $finder->files()->in($modulePath . $path)->name("*.xml"); - } - return $finder->files(); - } - - /** - * Attempts to find any MFTF entity by its name. Returns null if none are found. - * @param string $name - * @return mixed - * @throws XmlException - */ - private function findEntity($name) - { - if ($name == '_ENV' || $name == '_CREDS') { - return null; - } - - if (DataObjectHandler::getInstance()->getObject($name)) { - return DataObjectHandler::getInstance()->getObject($name); - } elseif (PageObjectHandler::getInstance()->getObject($name)) { - return PageObjectHandler::getInstance()->getObject($name); - } elseif (SectionObjectHandler::getInstance()->getObject($name)) { - return SectionObjectHandler::getInstance()->getObject($name); - } - - try { - return ActionGroupObjectHandler::getInstance()->getObject($name); - } catch (TestReferenceException $e) { - } - try { - return TestObjectHandler::getInstance()->getObject($name); - } catch (TestReferenceException $e) { - } - return null; - } - - /** - * Prints out given errors to file, and returns summary result string - * @return string - */ - private function printErrorsToFile() - { - $errors = $this->getErrors(); - - if (empty($errors)) { - return "No Dependency errors found."; - } - - $outputPath = getcwd() . DIRECTORY_SEPARATOR . "mftf-dependency-checks.txt"; - $fileResource = fopen($outputPath, 'w'); - $header = "MFTF File Dependency Check:\n"; - fwrite($fileResource, $header); - - foreach ($errors as $test => $error) { - fwrite($fileResource, $error[0] . PHP_EOL); - } - - fclose($fileResource); - $errorCount = count($errors); - $output = "Dependency errors found across {$errorCount} file(s). Error details output to {$outputPath}"; - - return $output; - } } diff --git a/src/Magento/FunctionalTestingFramework/StaticCheck/UnusedEntityCheck.php b/src/Magento/FunctionalTestingFramework/StaticCheck/UnusedEntityCheck.php new file mode 100644 index 000000000..50a348db0 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/StaticCheck/UnusedEntityCheck.php @@ -0,0 +1,634 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\FunctionalTestingFramework\StaticCheck; + +use Exception; +use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Finder\Finder; +use Magento\FunctionalTestingFramework\Util\Script\ScriptUtil; +use Symfony\Component\Finder\SplFileInfo; +use DOMElement; + +/** + * Class UnusedEntityCheck + * + * @package Magento\FunctionalTestingFramework\StaticCheck + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class UnusedEntityCheck implements StaticCheckInterface +{ + const ERROR_LOG_FILENAME = "mftf-unused-entity-usage-checks"; + const ENTITY_REGEX_PATTERN = "/\{(\{)*([\w.]+)(\})*\}/"; + const SELECTOR_REGEX_PATTERN = '/selector=["\']([^\'"]*)/'; + const ERROR_LOG_MESSAGE = "MFTF Unused Entity Usage Check"; + const SECTION_REGEX_PATTERN = "/\w*Section\b/"; + const REQUIRED_ENTITY = '/<requiredEntity(.*?)>(.+?)<\/requiredEntity>/'; + const ENTITY_SEPERATED_BY_DOT_REFERENCE = '/([\w]+)(\.)+([\w]+)/'; + + /** + * ScriptUtil instance + * + * @var ScriptUtil $scriptUtil + */ + private $scriptUtil; + + /** + * Checks test dependencies, determined by references in tests versus the dependencies listed in the Magento module + * + * @param InputInterface $input + * @return void + * @throws Exception + */ + public function execute(InputInterface $input) + { + $this->errors = $this->unusedEntities(); + $this->output = $this->scriptUtil->printErrorsToFile( + $this->errors, + StaticChecksList::getErrorFilesPath() . DIRECTORY_SEPARATOR . self::ERROR_LOG_FILENAME . '.txt', + self::ERROR_LOG_MESSAGE + ); + } + + /** + * Centralized method to get unused Entities + * @return array + */ + public function unusedEntities() + { + $this->scriptUtil = new ScriptUtil(); + $domDocument = new \DOMDocument(); + $modulePaths = $this->scriptUtil->getAllModulePaths(); + $testXmlFiles = $this->scriptUtil->getModuleXmlFilesByScope($modulePaths, "Test"); + $actionGroupXmlFiles = $this->scriptUtil->getModuleXmlFilesByScope($modulePaths, "ActionGroup"); + $dataXmlFiles = $this->scriptUtil->getModuleXmlFilesByScope($modulePaths, "Data"); + $pageXmlFiles = $this->scriptUtil->getModuleXmlFilesByScope($modulePaths, "Page"); + $sectionXmlFiles = $this->scriptUtil->getModuleXmlFilesByScope($modulePaths, "Section"); + $suiteXmlFiles = $this->scriptUtil->getModuleXmlFilesByScope($modulePaths, 'Suite'); + foreach ($dataXmlFiles as $filePath) { + $domDocument->load($filePath); + $entityResult = $this->getAttributesFromDOMNodeList( + $domDocument->getElementsByTagName("entity"), + ["type" => "name"] + ); + foreach ($entityResult as $entitiesResultData) { + $dataNames[$entitiesResultData[key($entitiesResultData)]] = [ + "dataFilePath"=>$filePath->getRealPath() + ]; + } + } + foreach ($actionGroupXmlFiles as $filePath) { + $domDocument->load($filePath); + $actionGroupName = $domDocument->getElementsByTagName("actionGroup")->item(0)->getAttribute("name"); + if (!empty($domDocument->getElementsByTagName("actionGroup")->item(0)->getAttribute("deprecated"))) { + continue; + } + $allActionGroupFileNames[$actionGroupName ] = + $filePath->getRealPath(); + } + + foreach ($sectionXmlFiles as $filePath) { + $domDocument->load($filePath); + $sectionName = $domDocument->getElementsByTagName("section")->item(0)->getAttribute("name"); + $sectionFileNames[$sectionName] = $filePath->getRealPath(); + } + foreach ($pageXmlFiles as $filePath) { + $domDocument->load($filePath); + $pageName = $domDocument->getElementsByTagName("page")->item(0)->getAttribute("name"); + $pageFiles[$pageName] = $filePath->getRealPath(); + } + $actionGroupReferences = $this->unusedActionEntity( + $domDocument, + $actionGroupXmlFiles, + $testXmlFiles, + $allActionGroupFileNames, + $suiteXmlFiles + ); + $entityReferences = $this->unusedData( + $domDocument, + $actionGroupXmlFiles, + $testXmlFiles, + $dataNames, + $dataXmlFiles + ); + $pagesReference = $this->unusedPageEntity( + $domDocument, + $actionGroupXmlFiles, + $testXmlFiles, + $pageFiles, + $suiteXmlFiles + ); + $sectionReference = $this->unusedSectionEntity( + $domDocument, + $actionGroupXmlFiles, + $testXmlFiles, + $pageXmlFiles, + $sectionFileNames, + $suiteXmlFiles + ); + return $this->setErrorOutput( + array_merge( + array_values($actionGroupReferences), + array_values($entityReferences), + array_values($pagesReference), + array_values($sectionReference) + ) + ); + } + + /** + * Setting error message + * + * @return array + * @throws Exception + */ + private function setErrorOutput($unusedFilePath) + { + $testErrors = []; + foreach ($unusedFilePath as $files) { + $contents = file_get_contents($files); + $file = fopen($files, 'a'); + if (!str_contains($contents, '<!--@Ignore(Unused_Entity_Check)-->')) { + fwrite($file, '<!--@Ignore(Unused_Entity_Check)-->'); + } + } + return $testErrors; + } + + /** + * Retrieves Unused Action Group Entities + * + * @param DOMDocument $domDocument + * @param ScriptUtil $actionGroupXmlFiles + * @param ScriptUtil $testXmlFiles + * @param ScriptUtil $allActionGroupFileNames + * @param ScriptUtil $suiteXmlFiles + * @return array + * @throws Exception + */ + public function unusedActionEntity( + $domDocument, + $actionGroupXmlFiles, + $testXmlFiles, + $allActionGroupFileNames, + $suiteXmlFiles + ) { + foreach ($suiteXmlFiles as $filePath) { + $domDocument->load($filePath); + $referencesSuite= $this->getAttributesFromDOMNodeList( + $domDocument->getElementsByTagName("actionGroup"), + "ref" + ); + foreach ($referencesSuite as $referencesResultSuite) { + if (isset($allActionGroupFileNames[$referencesResultSuite])) { + unset($allActionGroupFileNames[$referencesResultSuite]); + } + } + } + + foreach ($actionGroupXmlFiles as $filePath) { + $domDocument->load($filePath); + $actionGroup = $domDocument->getElementsByTagName("actionGroup")->item(0); + $references = $actionGroup->getAttribute("extends"); + if (in_array($references, array_keys($allActionGroupFileNames))) { + unset($allActionGroupFileNames[$references]); + } + } + foreach ($testXmlFiles as $filePath) { + $domDocument->load($filePath); + $testReferences = $this->getAttributesFromDOMNodeList( + $domDocument->getElementsByTagName("actionGroup"), + "ref" + ); + foreach ($testReferences as $testReferencesResult) { + if (isset($allActionGroupFileNames[$testReferencesResult])) { + unset($allActionGroupFileNames[$testReferencesResult]); + } + } + } + return $allActionGroupFileNames; + } + + /** + * Retrieves Unused Page Entities + * + * @param DOMDocument $domDocument + * @return array + * @throws Exception + */ + public function unusedPageEntity($domDocument, $actionGroupXmlFiles, $testXmlFiles, $pageNames, $suiteXmlFiles) + { + + foreach ($suiteXmlFiles as $filePath) { + $domDocument->load($filePath); + $contents = file_get_contents($filePath); + $pagesReferencesInSuites = $this->getAttributesFromDOMNodeList( + $domDocument->getElementsByTagName("amOnPage"), + "url" + ); + foreach ($pagesReferencesInSuites as $pagesReferencesInSuitesResult) { + $explodepagesReferencesResult = explode( + ".", + trim($pagesReferencesInSuitesResult, "{}") + ); + unset($pageNames[$explodepagesReferencesResult[0]]); + } + $pageNames = $this->entityReferencePatternCheck($domDocument, $pageNames, $contents, false, []); + } + foreach ($actionGroupXmlFiles as $filePath) { + $domDocument->load($filePath); + $contents = file_get_contents($filePath); + $pagesReferences = $this->getAttributesFromDOMNodeList( + $domDocument->getElementsByTagName("amOnPage"), + "url" + ); + foreach ($pagesReferences as $pagesReferencesResult) { + $explodepagesReferencesResult = explode( + ".", + trim($pagesReferencesResult, "{}") + ); + unset($pageNames[$explodepagesReferencesResult[0]]); + } + $pageNames = $this->entityReferencePatternCheck($domDocument, $pageNames, $contents, false, []); + } + + foreach ($testXmlFiles as $filePath) { + $domDocument->load($filePath); + $contents = file_get_contents($filePath); + $testReferences = $this->getAttributesFromDOMNodeList( + $domDocument->getElementsByTagName("amOnPage"), + "url" + ); + foreach ($testReferences as $pagesReferencesResult) { + $explodepagesReferencesResult = explode( + ".", + trim($pagesReferencesResult, "{}") + ); + unset($pageNames[$explodepagesReferencesResult[0]]); + } + $pageNames = $this->entityReferencePatternCheck($domDocument, $pageNames, $contents, false, []); + } + return $pageNames; + } + + /** + * Common Pattern Check Method + * + * @param DOMDocument $domDocument + * @param array $fileNames + * @param string $contents + * @return array + * @throws Exception + */ + private function entityReferencePatternCheck($domDocument, $fileNames, $contents, $data, $removeDataFilePath) + { + $sectionArgumentValueReference = $this->getAttributesFromDOMNodeList( + $domDocument->getElementsByTagName("argument"), + "value" + ); + $sectionDefaultValueArgumentReference = $this->getAttributesFromDOMNodeList( + $domDocument->getElementsByTagName("argument"), + "defaultValue" + ); + $sectionArgumentValue = array_merge($sectionArgumentValueReference, $sectionDefaultValueArgumentReference); + foreach ($sectionArgumentValue as $sectionArgumentValueResult) { + $explodedReference = str_contains($sectionArgumentValueResult, '$') + ? explode(".", trim($sectionArgumentValueResult, '$')) + : explode(".", trim($sectionArgumentValueResult, "{}")); + if (in_array($explodedReference[0], array_keys($fileNames))) { + $removeDataFilePath[] = isset($fileNames[$explodedReference[0]]["dataFilePath"]) + ? $fileNames[$explodedReference[0]]["dataFilePath"] + : []; + unset($fileNames[$explodedReference[0]]); + } + } + preg_match_all(self::ENTITY_REGEX_PATTERN, $contents, $bracketReferencesData); + preg_match_all( + self::ENTITY_SEPERATED_BY_DOT_REFERENCE, + $contents, + $entitySeperatedByDotReferenceActionGroup + ); + $entityReferenceDataResultActionGroup = array_merge( + array_unique($bracketReferencesData[0]), + array_unique($entitySeperatedByDotReferenceActionGroup[0]) + ); + + foreach (array_unique($entityReferenceDataResultActionGroup) as $bracketReferencesResults) { + $bracketReferencesDataResultOutput = explode(".", trim($bracketReferencesResults, "{}")); + if (in_array($bracketReferencesDataResultOutput[0], array_keys($fileNames))) { + $removeDataFilePath[] = isset($fileNames[$bracketReferencesDataResultOutput[0]]["dataFilePath"]) + ? $fileNames[$bracketReferencesDataResultOutput[0]]["dataFilePath"] + : []; + unset($fileNames[$bracketReferencesDataResultOutput[0]]); + } + } + + return ($data === true) ? ['dataFilePath'=>$removeDataFilePath ,'fileNames'=> $fileNames ] : $fileNames ; + } + + /** + * Retrieves Unused Section Entities + * + * @param DOMDocument $domDocument + * @param ScriptUtil $actionGroupXmlFiles + * @param ScriptUtil $testXmlFiles + * @param ScriptUtil $pageXmlFiles + * @param array $sectionFileNames + * @param ScriptUtil $suiteXmlFiles + * @return array + * @throws Exception + */ + public function unusedSectionEntity( + $domDocument, + $actionGroupXmlFiles, + $testXmlFiles, + $pageXmlFiles, + $sectionFileNames, + $suiteXmlFiles + ) { + foreach ($suiteXmlFiles as $filePath) { + $contents = file_get_contents($filePath); + $domDocument->load($filePath); + preg_match_all( + self::SELECTOR_REGEX_PATTERN, + $contents, + $selectorReferences + ); + + if (isset($selectorReferences[1])) { + foreach (array_unique($selectorReferences[1]) as $selectorReferencesResult) { + $trimSelector = explode(".", trim($selectorReferencesResult, "{{}}")); + if (isset($sectionFileNames[$trimSelector[0]])) { + unset($sectionFileNames[$trimSelector[0]]); + } + } + } + $sectionFileNames = $this->entityReferencePatternCheck( + $domDocument, + $sectionFileNames, + $contents, + false, + [] + ); + } + foreach ($actionGroupXmlFiles as $filePath) { + $contents = file_get_contents($filePath); + $domDocument->load($filePath); + preg_match_all( + self::SELECTOR_REGEX_PATTERN, + $contents, + $selectorReferences + ); + + if (isset($selectorReferences[1])) { + foreach (array_unique($selectorReferences[1]) as $selectorReferencesResult) { + $trimSelector = explode(".", trim($selectorReferencesResult, "{{}}")); + if (isset($sectionFileNames[$trimSelector[0]])) { + unset($sectionFileNames[$trimSelector[0]]); + } + } + } + $sectionFileNames = $this->entityReferencePatternCheck( + $domDocument, + $sectionFileNames, + $contents, + false, + [] + ); + } + $sectionFileNames = $this->getUnusedSectionEntitiesReferenceInActionGroupAndTestFiles( + $testXmlFiles, + $pageXmlFiles, + $domDocument, + $sectionFileNames + ); + return $sectionFileNames; + } + + /** + * Get unused section entities reference in Action group and Test files + * @param ScriptUtil $testXmlFiles + * @param ScriptUtil $pageXmlFiles + * @param DOMDocument $domDocument + * @param array $sectionFileNames + * @return array + * @throws Exception + */ + private function getUnusedSectionEntitiesReferenceInActionGroupAndTestFiles( + $testXmlFiles, + $pageXmlFiles, + $domDocument, + $sectionFileNames + ) { + foreach ($testXmlFiles as $filePath) { + $contents = file_get_contents($filePath); + $domDocument->load($filePath); + preg_match_all( + self::SELECTOR_REGEX_PATTERN, + $contents, + $selectorReferences + ); + if (isset($selectorReferences[1])) { + foreach (array_unique($selectorReferences[1]) as $selectorReferencesResult) { + $trimSelector = explode(".", trim($selectorReferencesResult, "{{}}")); + if (isset($sectionFileNames[$trimSelector[0]])) { + unset($sectionFileNames[$trimSelector[0]]); + } + } + } + $sectionFileNames = $this->entityReferencePatternCheck( + $domDocument, + $sectionFileNames, + $contents, + false, + [] + ); + } + + foreach ($pageXmlFiles as $filePath) { + $contents = file_get_contents($filePath); + preg_match_all( + self::SELECTOR_REGEX_PATTERN, + $contents, + $selectorReferences + ); + if (isset($selectorReferences[1])) { + foreach (array_unique($selectorReferences[1]) as $selectorReferencesResult) { + $trimSelector = explode(".", trim($selectorReferencesResult, "{{}}")); + if (isset($sectionFileNames[$trimSelector[0]])) { + unset($sectionFileNames[$trimSelector[0]]); + } + } + } + $sectionFileNames = $this->entityReferencePatternCheck( + $domDocument, + $sectionFileNames, + $contents, + false, + [] + ); + } + return $sectionFileNames; + } + /** + * Return Unused Data entities + * + * @param DOMDocument $domDocument + * @return array + */ + public function unusedData( + $domDocument, + $actionGroupXmlFiles, + $testXmlFiles, + $dataNames, + $dataXmlFiles + ) { + $removeDataFilePath = []; + foreach ($dataXmlFiles as $filePath) { + $domDocument->load($filePath); + $contents = file_get_contents($filePath); + preg_match_all(self::REQUIRED_ENTITY, $contents, $requiredEntityReference); + foreach ($requiredEntityReference[2] as $requiredEntityReferenceResult) { + if (isset($dataNames[$requiredEntityReferenceResult])) { + $removeDataFilePath[] = + $dataNames[$requiredEntityReferenceResult]["dataFilePath"]; + unset($dataNames[$requiredEntityReferenceResult]); + } + } + } + foreach ($actionGroupXmlFiles as $filePath) { + $domDocument->load($filePath); + $contents = file_get_contents($filePath); + $getUnusedFilePath = $this->entityReferencePatternCheck( + $domDocument, + $dataNames, + $contents, + true, + $removeDataFilePath + ); + $dataNames = $getUnusedFilePath['fileNames']; + $removeDataFilePath = $getUnusedFilePath['dataFilePath']; + } + foreach ($testXmlFiles as $filePath) { + $domDocument->load($filePath); + $contents = file_get_contents($filePath); + $getUnusedFilePath = $this->entityReferencePatternCheck( + $domDocument, + $dataNames, + $contents, + true, + $removeDataFilePath + ); + $dataNames = $getUnusedFilePath['fileNames']; + $removeDataFilePath = $getUnusedFilePath['dataFilePath']; + $createdDataReferences = $this->getAttributesFromDOMNodeList( + $domDocument->getElementsByTagName("createData"), + "entity" + ); + $updatedDataReferences = $this->getAttributesFromDOMNodeList( + $domDocument->getElementsByTagName("updateData"), + "entity" + ); + $getDataReferences = $this->getAttributesFromDOMNodeList( + $domDocument->getElementsByTagName("getData"), + "entity" + ); + $dataReferences = array_unique( + array_merge( + $createdDataReferences, + $updatedDataReferences, + $getDataReferences + ) + ); + if (count($dataReferences) > 0) { + foreach ($dataReferences as $dataReferencesResult) { + if (isset($dataNames[$dataReferencesResult])) { + $removeDataFilePath[] = $dataNames[$dataReferencesResult]["dataFilePath"]; + unset($dataNames[$dataReferencesResult]); + } + } + } + } + $dataFilePathResult = $this->unsetFilePath($dataNames, $removeDataFilePath); + return array_unique($dataFilePathResult); + } + + /** + * Remove used entities file path from unused entities array + * + * @return array + */ + private function unsetFilePath($dataNames, $removeDataFilePath) + { + $dataFilePathResult = []; + foreach ($dataNames as $key => $dataNamesResult) { + if (in_array($dataNamesResult["dataFilePath"], $removeDataFilePath)) { + unset($dataNames[$key]); + continue; + } + $dataFilePathResult[] = $dataNames[$key]['dataFilePath']; + } + return array_unique($dataFilePathResult); + } + + /** + * Return attribute value for each node in DOMNodeList as an array + * + * @param DOMNodeList $nodes + * @param string $attributeName + * @return array + */ + private function getAttributesFromDOMNodeList($nodes, $attributeName) + { + $attributes = []; + foreach ($nodes as $node) { + if (is_string($attributeName)) { + $attributeValue = $node->getAttribute($attributeName); + } else { + $attributeValue = [$node->getAttribute(key($attributeName)) => + $node->getAttribute($attributeName[key($attributeName)])]; + } + if (!empty($attributeValue)) { + $attributes[] = $attributeValue; + } + } + return $attributes; + } + + /** + * Extract actionGroup DomElement from xml file + * + * @param string $contents + * @return \DOMElement + */ + public function getActionGroupDomElement(string $contents): DOMElement + { + $domDocument = new \DOMDocument(); + $domDocument->loadXML($contents); + return $domDocument->getElementsByTagName("actionGroup")[0]; + } + + /** + * Return array containing all errors found after running the execute() function + * + * @return array + */ + public function getErrors() + { + return $this->errors; + } + + /** + * Return output + * + * @return array + */ + public function getOutput() + { + return $this->output; + } +} diff --git a/src/Magento/FunctionalTestingFramework/Step/Backend/AdminStep.php b/src/Magento/FunctionalTestingFramework/Step/Backend/AdminStep.php deleted file mode 100644 index 5ae74a076..000000000 --- a/src/Magento/FunctionalTestingFramework/Step/Backend/AdminStep.php +++ /dev/null @@ -1,2316 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ - -namespace Magento\FunctionalTestingFramework\Step\Backend; - -require_once __DIR__ . '/../../Helper/AdminUrlList.php'; - -/** - * Class AdminStep - * @SuppressWarnings(PHPMD) - * @codingStandardsIgnoreFile - */ -class AdminStep extends \Magento\FunctionalTestingFramework\AcceptanceTester -{ - public static $adminPageTitle = '.page-title'; - - public function openNewTabGoToVerify($url) - { - $I = $this; - $I->openNewTab(); - $I->amOnPage($url); - $I->waitForPageLoad(); - $I->seeInCurrentUrl($url); - } - - public function closeNewTab() - { - $I = $this; - $I->closeTab(); - } - - // Key Admin Pages - public function goToRandomAdminPage() - { - $I = $this; - - $admin_url_list = array( - "/admin/admin/dashboard/", - "/admin/sales/order/", - "/admin/sales/invoice/", - "/admin/sales/shipment/", - "/admin/sales/creditmemo/", - "/admin/paypal/billing_agreement/", - "/admin/sales/transactions/", - "/admin/catalog/product/", - "/admin/catalog/category/", - "/admin/customer/index/", - "/admin/customer/online/", - "/admin/catalog_rule/promo_catalog/", - "/admin/sales_rule/promo_quote/", - "/admin/admin/email_template/", - "/admin/newsletter/template/", - "/admin/newsletter/queue/", - "/admin/newsletter/subscriber/", - "/admin/admin/url_rewrite/index/", - "/admin/search/term/index/", - "/admin/search/synonyms/index/", - "/admin/admin/sitemap/", - "/admin/review/product/index/", - "/admin/cms/page/", - "/admin/cms/block/", - "/admin/admin/widget_instance/", - "/admin/theme/design_config/", - "/admin/admin/system_design_theme/", - "/admin/admin/system_design/", - "/admin/reports/report_shopcart/product/", - "/admin/search/term/report/", - "/admin/reports/report_shopcart/abandoned/", - "/admin/newsletter/problem/", - "/admin/reports/report_review/customer/", - "/admin/reports/report_review/product/", - "/admin/reports/report_sales/sales/", - "/admin/reports/report_sales/tax/", - "/admin/reports/report_sales/invoiced/", - "/admin/reports/report_sales/shipping/", - "/admin/reports/report_sales/refunded/", - "/admin/reports/report_sales/coupons/", - "/admin/paypal/paypal_reports/", - "/admin/braintree/report/", - "/admin/reports/report_customer/totals/", - "/admin/reports/report_customer/orders/", - "/admin/reports/report_customer/accounts/", - "/admin/reports/report_product/viewed/", - "/admin/reports/report_sales/bestsellers/", - "/admin/reports/report_product/lowstock/", - "/admin/reports/report_product/sold/", - "/admin/reports/report_product/downloads/", - "/admin/reports/report_statistics/", - "/admin/admin/system_store/", - "/admin/admin/system_config/", - "/admin/checkout/agreement/", - "/admin/sales/order_status/", - "/admin/tax/rule/", - "/admin/tax/rate/", - "/admin/admin/system_currency/", - "/admin/admin/system_currencysymbol/", - "/admin/catalog/product_attribute/", - "/admin/catalog/product_set/", - "/admin/review/rating/", - "/admin/customer/group/", - "/admin/admin/import/", - "/admin/admin/export/", - "/admin/tax/rate/importExport/", - "/admin/admin/history/", - "/admin/admin/integration/", - "/admin/admin/cache/", - "/admin/backup/index/", - "/admin/indexer/indexer/list/", - "/admin/admin/user/", - "/admin/admin/locks/", - "/admin/admin/user_role/", - "/admin/admin/notification/", - "/admin/admin/system_variable/", - "/admin/admin/crypt_key/" - ); - - $random_admin_url = array_rand($admin_url_list, 1); - - $I->amOnPage($admin_url_list[$random_admin_url]); - $I->waitForPageLoad(); - - return $admin_url_list[$random_admin_url]; - } - - public function goToTheAdminLoginPage() - { - $I = $this; - $I->amOnPage(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminLoginPage); - $I->waitForPageLoad(); - } - - public function goToTheAdminLogoutPage() - { - $I = $this; - $I->amOnPage(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminLogoutPage); - } - - // Sales - public function goToTheAdminOrdersGrid() - { - $I = $this; - $I->amOnPage(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminOrdersGrid); - $I->waitForPageLoad(); - } - - public function goToTheAdminOrderForIdPage($orderId) - { - $I = $this; - $I->amOnPage((\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminOrderByIdPage . $orderId)); - $I->waitForPageLoad(); - } - - public function goToTheAdminAddOrderPage() - { - $I = $this; - $I->amOnPage(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminAddOrderPage); - $I->waitForPageLoad(); - } - - public function goToTheAdminAddOrderForCustomerIdPage($customerId) - { - $I = $this; - $I->amOnPage((\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminAddOrderForCustomerIdPage . $customerId)); - $I->waitForPageLoad(); - } - - public function goToTheAdminInvoicesGrid() - { - $I = $this; - $I->amOnPage(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminInvoicesGrid); - $I->waitForPageLoad(); - } - - public function goToTheAdminAddInvoiceForOrderIdPage($orderId) - { - $I = $this; - $I->amOnPage((\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminAddInvoiceForOrderIdPage . $orderId)); - $I->waitForPageLoad(); - } - - public function goToTheAdminShipmentsGrid() - { - $I = $this; - $I->amOnPage(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminShipmentsGrid); - $I->waitForPageLoad(); - } - - public function goToTheAdminShipmentForIdPage($shipmentId) - { - $I = $this; - $I->amOnPage((\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminShipmentForIdPage . $shipmentId)); - $I->waitForPageLoad(); - } - - public function goToTheAdminCreditMemosGrid() - { - $I = $this; - $I->amOnPage(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminCreditMemosGrid); - $I->waitForPageLoad(); - } - - public function goToTheAdminCreditMemoForIdPage($creditMemoId) - { - $I = $this; - $I->amOnPage((\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminCreditMemoForIdPage . $creditMemoId)); - $I->waitForPageLoad(); - } - - public function goToTheAdminBillingAgreementsGrid() - { - $I = $this; - $I->amOnPage(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminBillingAgreementsGrid); - $I->waitForPageLoad(); - } - - public function goToTheAdminTransactionsGrid() - { - $I = $this; - $I->amOnPage(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminTransactionsGrid); - $I->waitForPageLoad(); - } - - // Products - public function goToTheAdminCatalogPage() - { - $I = $this; - $I->amOnPage(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminCatalogGrid); - $I->waitForPageLoad(); - } - - public function goToTheAdminProductForIdPage($productId) - { - $I = $this; - $I->amOnPage((\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminProductForIdPage . $productId)); - $I->waitForPageLoad(); - } - - public function goToTheAdminAddSimpleProductPage() - { - $I = $this; - $I->amOnPage(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminAddSimpleProductPage); - $I->waitForPageLoad(); - } - - public function goToTheAdminAddConfigurableProductPage() - { - $I = $this; - $I->amOnPage(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminAddConfigurableProductPage); - $I->waitForPageLoad(); - } - - public function goToTheAdminAddGroupedProductPage() - { - $I = $this; - $I->amOnPage(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminAddGroupedProductPage); - $I->waitForPageLoad(); - } - - public function goToTheAdminAddVirtualProductPage() - { - $I = $this; - $I->amOnPage(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminAddVirtualProductPage); - $I->waitForPageLoad(); - } - - public function goToTheAdminAddBundledProductPage() - { - $I = $this; - $I->amOnPage(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminAddBundleProductPage); - $I->waitForPageLoad(); - } - - public function goToTheAdminAddDownloadableProductPage() - { - $I = $this; - $I->amOnPage(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminAddDownloadableProductPage); - $I->waitForPageLoad(); - } - - public function goToTheAdminCategoriesPage() - { - $I = $this; - $I->amOnPage(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminCategoriesPage); - $I->waitForPageLoad(); - } - - public function goToTheAdminCategoryForIdPage($categoryId) - { - $I = $this; - $I->amOnPage((\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminCategoryForIdPage . $categoryId)); - $I->waitForPageLoad(); - } - - public function goToTheAdminAddRootCategoryForStoreIdPage($storeId) - { - $I = $this; - $I->amOnPage(('/admin/catalog/category/add/store/' . $storeId . '/parent/1')); - $I->waitForPageLoad(); - } - - public function goToTheAdminAddSubCategoryForStoreIdPage($storeId) - { - $I = $this; - $I->amOnPage(('/admin/catalog/category/add/store/' . $storeId . '/parent/2')); - $I->waitForPageLoad(); - } - - // Customers - public function goToTheAdminAllCustomersGrid() - { - $I = $this; - $I->amOnPage(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminAllCustomersGrid); - $I->waitForPageLoad(); - } - - public function goToTheAdminCustomersNowOnlineGrid() - { - $I = $this; - $I->amOnPage(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminCustomersNowOnlineGrid); - $I->waitForPageLoad(); - } - - public function goToTheAdminCustomerForIdPage($customerId) - { - $I = $this; - $I->amOnPage((\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminCustomerForCustomerIdPage . $customerId)); - $I->waitForPageLoad(); - } - - public function goToTheAdminAddCustomerPage() - { - $I = $this; - $I->amOnPage(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminAddCustomerPage); - $I->waitForPageLoad(); - } - - // Marketing - public function goToTheAdminCatalogPriceRuleGrid() - { - $I = $this; - $I->amOnPage(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminCatalogPriceRuleGrid); - $I->waitForPageLoad(); - } - - public function goToTheAdminCatalogPriceRuleForIdPage($catalogPriceRuleId) - { - $I = $this; - $I->amOnPage((\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminCatalogPriceRuleForIdPage . $catalogPriceRuleId)); - $I->waitForPageLoad(); - } - - public function goToTheAdminAddCatalogPriceRulePage() - { - $I = $this; - $I->amOnPage(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminAddCatalogPriceRulePage); - $I->waitForPageLoad(); - } - - public function goToTheAdminCartPriceRulesGrid() - { - $I = $this; - $I->amOnPage(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminCartPriceRulesGrid); - $I->waitForPageLoad(); - } - - public function goToTheAdminCartPriceRuleForIdPage($cartPriceRuleId) - { - $I = $this; - $I->amOnPage((\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminCartPriceRuleForIdPage . $cartPriceRuleId)); - $I->waitForPageLoad(); - } - - public function goToTheAdminAddCartPriceRulePage() - { - $I = $this; - $I->amOnPage(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminAddCartPriceRulePage); - $I->waitForPageLoad(); - } - - public function goToTheAdminEmailTemplatesGrid() - { - $I = $this; - $I->amOnPage(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminEmailTemplatesGrid); - $I->waitForPageLoad(); - } - - public function goToTheAdminEmailTemplateForIdPage($emailTemplateId) - { - $I = $this; - $I->amOnPage((\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminEmailTemplateForIdPage . $emailTemplateId)); - $I->waitForPageLoad(); - } - - public function goToTheAdminAddEmailTemplatePage() - { - $I = $this; - $I->amOnPage(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminAddEmailTemplatePage); - $I->waitForPageLoad(); - } - - public function goToTheAdminNewsletterTemplateGrid() - { - $I = $this; - $I->amOnPage(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminNewsletterTemplateGrid); - $I->waitForPageLoad(); - } - - public function goToTheAdminNewsletterTemplateByIdPage($newsletterTemplateId) - { - $I = $this; - $I->amOnPage((\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminNewsletterTemplateForIdPage . $newsletterTemplateId)); - $I->waitForPageLoad(); - } - - public function goToTheAdminAddNewsletterTemplatePage() - { - $I = $this; - $I->amOnPage(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminAddNewsletterTemplatePage); - $I->waitForPageLoad(); - } - - public function goToTheAdminNewsletterQueueGrid() - { - $I = $this; - $I->amOnPage(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminNewsletterQueueGrid); - $I->waitForPageLoad(); - } - - public function goToTheAdminNewsletterSubscribersGrid() - { - $I = $this; - $I->amOnPage(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminNewsletterSubscribersGrid); - $I->waitForPageLoad(); - } - - public function goToTheAdminURLRewritesGrid() - { - $I = $this; - $I->amOnPage(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminURLRewritesGrid); - $I->waitForPageLoad(); - } - - public function goToTheAdminURLRewriteForId($urlRewriteId) - { - $I = $this; - $I->amOnPage((\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminURLRewriteForIdPage . $urlRewriteId)); - $I->waitForPageLoad(); - } - - public function goToTheAdminAddURLRewritePage() - { - $I = $this; - $I->amOnPage(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminAddURLRewritePage); - $I->waitForPageLoad(); - } - - public function goToTheAdminSearchTermsGrid() - { - $I = $this; - $I->amOnPage(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminSearchTermsGrid); - $I->waitForPageLoad(); - } - - public function goToTheAdminSearchTermForIdPage($searchTermId) - { - $I = $this; - $I->amOnPage((\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminSearchTermForIdPage . $searchTermId)); - $I->waitForPageLoad(); - } - - public function goToTheAdminAddSearchTermPage() - { - $I = $this; - $I->amOnPage(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminAddSearchTermPage); - $I->waitForPageLoad(); - } - - public function goToTheAdminSearchSynonymsGrid() - { - $I = $this; - $I->amOnPage(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminSearchSynonymsGrid); - $I->waitForPageLoad(); - } - - public function goToTheAdminSearchSynonymGroupByIdPage($searchSynonymId) - { - $I = $this; - $I->amOnPage((\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminSearchSynonymGroupForIdPage . $searchSynonymId)); - $I->waitForPageLoad(); - } - - public function goToTheAdminAddSearchSynonymGroupPage() - { - $I = $this; - $I->amOnPage(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminAddSearchSynonymGroupPage); - $I->waitForPageLoad(); - } - - public function goToTheAdminSiteMapGrid() - { - $I = $this; - $I->amOnPage(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminSiteMapGrid); - $I->waitForPageLoad(); - } - - public function goToTheAdminSiteMapForIdPage($siteMapId) - { - $I = $this; - $I->amOnPage((\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminSiteMapForIdPage . $siteMapId)); - $I->waitForPageLoad(); - } - - public function goToTheAdminAddSiteMapPage() - { - $I = $this; - $I->amOnPage(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminAddSiteMapPage); - $I->waitForPageLoad(); - } - - public function goToTheAdminReviewsGrid() - { - $I = $this; - $I->amOnPage(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminReviewsGrid); - $I->waitForPageLoad(); - } - - public function goToTheAdminReviewForIdPage($reviewId) - { - $I = $this; - $I->amOnPage((\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminReviewByIdPage . $reviewId)); - $I->waitForPageLoad(); - } - - public function goToTheAdminAddReviewPage() - { - $I = $this; - $I->amOnPage(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminAddReviewPage); - $I->waitForPageLoad(); - } - - // Content - public function goToTheAdminPagesGrid() - { - $I = $this; - $I->amOnPage(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminPagesGrid); - $I->waitForPageLoad(); - } - - public function goToTheAdminPageForIdPage($pageId) - { - $I = $this; - $I->amOnPage((\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminPageForIdPage . $pageId)); - $I->waitForPageLoad(); - } - - public function goToTheAdminAddPagePage() - { - $I = $this; - $I->amOnPage((\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminAddPagePage)); - $I->waitForPageLoad(); - } - - public function goToTheAdminBlocksGrid() - { - $I = $this; - $I->amOnPage(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminBlocksGrid); - $I->waitForPageLoad(); - } - - public function goToTheAdminBlockForIdPage($blockId) - { - $I = $this; - $I->amOnPage((\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminBlockForIdPage . $blockId)); - $I->waitForPageLoad(); - } - - public function goToTheAdminAddBlockPage() - { - $I = $this; - $I->amOnPage(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminAddBlockPage); - $I->waitForPageLoad(); - } - - public function goToTheAdminWidgetsGrid() - { - $I = $this; - $I->amOnPage(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminWidgetsGrid); - $I->waitForPageLoad(); - } - - public function goToTheAdminAddWidgetPage() - { - $I = $this; - $I->amOnPage(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminAddWidgetPage); - $I->waitForPageLoad(); - } - - public function goToTheAdminDesignConfigurationGrid() - { - $I = $this; - $I->amOnPage(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminDesignConfigurationGrid); - $I->waitForPageLoad(); - } - - public function goToTheAdminThemesGrid() - { - $I = $this; - $I->amOnPage(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminThemesGrid); - $I->waitForPageLoad(); - } - - public function goToTheAdminThemeByIdPage($themeId) - { - $I = $this; - $I->amOnPage((\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminThemeByIdPage . $themeId)); - $I->waitForPageLoad(); - } - - public function goToTheAdminStoreContentScheduleGrid() - { - $I = $this; - $I->amOnPage(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminStoreContentScheduleGrid); - $I->waitForPageLoad(); - } - - public function goToTheAdminStoreContentScheduleForIdPage($storeContentScheduleId) - { - $I = $this; - $I->amOnPage((\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminStoreContentScheduleForIdPage . $storeContentScheduleId)); - $I->waitForPageLoad(); - } - - public function goToTheAdminAddStoreDesignChangePage() - { - $I = $this; - $I->amOnPage(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminAddStoreDesignChangePage); - $I->waitForPageLoad(); - } - - // Reports - public function goToTheAdminProductsInCartGrid() - { - $I = $this; - $I->amOnPage(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminProductsInCartGrid); - $I->waitForPageLoad(); - } - - public function goToTheAdminSearchTermsReportGrid() - { - $I = $this; - $I->amOnPage(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminSearchTermsReportGrid); - $I->waitForPageLoad(); - } - - public function goToTheAdminAbandonedCartsGrid() - { - $I = $this; - $I->amOnPage(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminAbandonedCartsGrid); - $I->waitForPageLoad(); - } - - public function goToTheAdminNewsletterProblemsReportGrid() - { - $I = $this; - $I->amOnPage(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminNewsletterProblemsReportGrid); - $I->waitForPageLoad(); - } - - public function goToTheAdminCustomerReviewsReportGrid() - { - $I = $this; - $I->amOnPage(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminCustomerReviewsReportGrid); - $I->waitForPageLoad(); - } - - public function goToTheAdminProductReviewsReportGrid() - { - $I = $this; - $I->amOnPage(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminProductReviewsReportGrid); - $I->waitForPageLoad(); - } - - public function goToTheAdminProductReviewsForProductIdPage($productId) - { - $I = $this; - $I->amOnPage((\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminProductReviewsForProductIdPage . $productId)); - $I->waitForPageLoad(); - } - - public function goToTheAdminProductReviewIdForProductIdPage($productReviewId, $productId) - { - $I = $this; - $I->amOnPage(('/admin/review/product/edit/id/' . $productReviewId . '/productId/' . $productId)); - $I->waitForPageLoad(); - } - - public function goToTheAdminOrdersReportGrid() - { - $I = $this; - $I->amOnPage(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminOrdersReportGrid); - $I->waitForPageLoad(); - } - - public function goToTheAdminTaxReportGrid() - { - $I = $this; - $I->amOnPage(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminTaxReportGrid); - $I->waitForPageLoad(); - } - - public function goToTheAdminInvoiceReportGrid() - { - $I = $this; - $I->amOnPage(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminInvoiceReportGrid); - $I->waitForPageLoad(); - } - - public function goToTheAdminShippingReportGrid() - { - $I = $this; - $I->amOnPage(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminShippingReportGrid); - $I->waitForPageLoad(); - } - - public function goToTheAdminRefundsReportGrid() - { - $I = $this; - $I->amOnPage(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminRefundsReportGrid); - $I->waitForPageLoad(); - } - - public function goToTheAdminCouponsReportGrid() - { - $I = $this; - $I->amOnPage(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminCouponsReportGrid); - $I->waitForPageLoad(); - } - - public function goToTheAdminPayPalSettlementReportsGrid() - { - $I = $this; - $I->amOnPage(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminPayPalSettlementReportsGrid); - $I->waitForPageLoad(); - } - - public function goToTheAdminBraintreeSettlementReportGrid() - { - $I = $this; - $I->amOnPage(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminBraintreeSettlementReportGrid); - $I->waitForPageLoad(); - } - - public function goToTheAdminOrderTotalReportGrid() - { - $I = $this; - $I->amOnPage(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminOrderTotalReportGrid); - $I->waitForPageLoad(); - } - - public function goToTheAdminOrderCountReportGrid() - { - $I = $this; - $I->amOnPage(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminOrderCountReportGrid); - $I->waitForPageLoad(); - } - - public function goToTheAdminNewAccountsReportGrid() - { - $I = $this; - $I->amOnPage(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminNewAccountsReportGrid); - $I->waitForPageLoad(); - } - - public function goToTheAdminProductViewsReportGrid() - { - $I = $this; - $I->amOnPage(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminProductViewsReportGrid); - $I->waitForPageLoad(); - } - - public function goToTheAdminBestsellersReportGrid() - { - $I = $this; - $I->amOnPage(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminBestsellersReportGrid); - $I->waitForPageLoad(); - } - - public function goToTheAdminLowStockReportGrid() - { - $I = $this; - $I->amOnPage(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminLowStockReportGrid); - $I->waitForPageLoad(); - } - - public function goToTheAdminOrderedProductsReportGrid() - { - $I = $this; - $I->amOnPage(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminOrderedProductsReportGrid); - $I->waitForPageLoad(); - } - - public function goToTheAdminDownloadsReportGrid() - { - $I = $this; - $I->amOnPage(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminDownloadsReportGrid); - $I->waitForPageLoad(); - } - - public function goToTheAdminRefreshStatisticsGrid() - { - $I = $this; - $I->amOnPage(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminRefreshStatisticsGrid); - $I->waitForPageLoad(); - } - - // Stores - public function goToTheAdminAllStoresGrid() - { - $I = $this; - $I->amOnPage(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminAllStoresGrid); - $I->waitForPageLoad(); - } - - public function goToTheAdminCreateStoreViewPage() - { - $I = $this; - $I->amOnPage(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminCreateStoreViewPage); - $I->waitForPageLoad(); - } - - public function goToTheAdminCreateStorePage() - { - $I = $this; - $I->amOnPage(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminCreateStorePage); - $I->waitForPageLoad(); - } - - public function goToTheAdminCreateWebsitePage() - { - $I = $this; - $I->amOnPage(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminCreateWebsitePage); - $I->waitForPageLoad(); - } - - public function goToTheAdminWebsiteForIdPage($websiteId) - { - $I = $this; - $I->amOnPage((\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminWebsiteByIdPage . $websiteId)); - $I->waitForPageLoad(); - } - - public function goToTheAdminStoreViewForIdPage($storeViewId) - { - $I = $this; - $I->amOnPage((\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminStoreViewByIdPage . $storeViewId)); - $I->waitForPageLoad(); - } - - public function goToTheAdminStoreForIdPage($storeId) - { - $I = $this; - $I->amOnPage((\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminStoreByIdPage . $storeId)); - $I->waitForPageLoad(); - } - - public function goToTheAdminConfigurationGrid() - { - $I = $this; - $I->amOnPage(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminConfigurationGrid); - $I->waitForPageLoad(); - } - - public function goToTheAdminTermsAndConditionsGrid() - { - $I = $this; - $I->amOnPage(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminTermsAndConditionsGrid); - $I->waitForPageLoad(); - } - - public function goToTheAdminTermsAndConditionForIdPage($termsAndConditionsId) - { - $I = $this; - $I->amOnPage((\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminTermsAndConditionByIdPage . $termsAndConditionsId)); - $I->waitForPageLoad(); - } - - public function goToTheAdminAddNewTermsAndConditionsPage() - { - $I = $this; - $I->amOnPage(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminAddNewTermsAndConditionPage); - $I->waitForPageLoad(); - } - - public function goToTheAdminOrderStatusGrid() - { - $I = $this; - $I->amOnPage(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminOrderStatusGrid); - $I->waitForPageLoad(); - } - - public function goToTheAdminAddOrderStatusPage() - { - $I = $this; - $I->amOnPage(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminAddOrderStatusPage); - $I->waitForPageLoad(); - } - - public function goToTheAdminTaxRulesGrid() - { - $I = $this; - $I->amOnPage(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminTaxRulesGrid); - $I->waitForPageLoad(); - } - - public function goToTheAdminTaxRuleForIdPage($taxRuleId) - { - $I = $this; - $I->amOnPage((\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminTaxRuleByIdPage . $taxRuleId)); - $I->waitForPageLoad(); - } - - public function goToTheAdminAddTaxRulePage() - { - $I = $this; - $I->amOnPage(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminAddTaxRulePage); - $I->waitForPageLoad(); - } - - public function goToTheAdminTaxZonesAndRatesGrid() - { - $I = $this; - $I->amOnPage(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminTaxZonesAndRatesGrid); - $I->waitForPageLoad(); - } - - public function goToTheAdminTaxZoneAndRateForIdPage($taxZoneAndRateId) - { - $I = $this; - $I->amOnPage((\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminTaxZoneAndRateByIdPage . $taxZoneAndRateId)); - $I->waitForPageLoad(); - } - - public function goToTheAdminAddTaxZoneAndRatePage() - { - $I = $this; - $I->amOnPage(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminAddTaxZoneAndRatePage); - $I->waitForPageLoad(); - } - - public function goToTheAdminCurrencyRatesPage() - { - $I = $this; - $I->amOnPage(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminCurrencyRatesPage); - $I->waitForPageLoad(); - } - - public function goToTheAdminCurrencySymbolsPage() - { - $I = $this; - $I->amOnPage(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminCurrencySymbolsPage); - $I->waitForPageLoad(); - } - - public function goToTheAdminProductAttributesGrid() - { - $I = $this; - $I->amOnPage(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminProductAttributesGrid); - $I->waitForPageLoad(); - } - - public function goToTheAdminProductAttributeForIdPage($productAttributeId) - { - $I = $this; - $I->amOnPage((\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminProductAttributeForIdPage . $productAttributeId)); - $I->waitForPageLoad(); - } - - public function goToTheAdminAddProductAttributePage() - { - $I = $this; - $I->amOnPage(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminAddProductAttributePage); - $I->waitForPageLoad(); - } - - public function goToTheAdminAttributeSetGrid() - { - $I = $this; - $I->amOnPage(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminAttributeSetsGrid); - $I->waitForPageLoad(); - } - - public function goToTheAdminAttributeSetByIdPage($attributeSetId) - { - $I = $this; - $I->amOnPage((\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminAttributeSetByIdPage . $attributeSetId)); - $I->waitForPageLoad(); - } - - public function goToTheAdminAddAttributeSetPage() - { - $I = $this; - $I->amOnPage(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminAddAttributeSetPage); - $I->waitForPageLoad(); - } - - public function goToTheAdminRatingGrid() - { - $I = $this; - $I->amOnPage(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminRatingsGrid); - $I->waitForPageLoad(); - } - - public function goToTheAdminRatingForIdPage($ratingId) - { - $I = $this; - $I->amOnPage((\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminRatingForIdPage . $ratingId)); - $I->waitForPageLoad(); - } - - public function goToTheAdminAddRatingPage() - { - $I = $this; - $I->amOnPage(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminAddRatingPage); - $I->waitForPageLoad(); - } - - public function goToTheAdminCustomerGroupsGrid() - { - $I = $this; - $I->amOnPage(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminCustomerGroupsGrid); - $I->waitForPageLoad(); - } - - public function goToTheAdminCustomerGroupForIdPage($customerGroupId) - { - $I = $this; - $I->amOnPage((\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminCustomerGroupByIdPage . $customerGroupId)); - $I->waitForPageLoad(); - } - - public function goToTheAdminAddCustomerGroupPage() - { - $I = $this; - $I->amOnPage(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminAddCustomerGroupPage); - $I->waitForPageLoad(); - } - - // System - public function goToTheAdminImportPage() - { - $I = $this; - $I->amOnPage(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminImportPage); - $I->waitForPageLoad(); - } - - public function goToTheAdminExportPage() - { - $I = $this; - $I->amOnPage(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminExportPage); - $I->waitForPageLoad(); - } - - public function goToTheAdminImportAndExportTaxRatesPage() - { - $I = $this; - $I->amOnPage(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminImportAndExportTaxRatesPage); - $I->waitForPageLoad(); - } - - public function goToTheAdminImportHistoryGrid() - { - $I = $this; - $I->amOnPage(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminImportHistoryGrid); - $I->waitForPageLoad(); - } - - public function goToTheAdminIntegrationsGrid() - { - $I = $this; - $I->amOnPage(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminIntegrationsGrid); - $I->waitForPageLoad(); - } - - public function goToTheAdminIntegrationForIdPage($integrationId) - { - $I = $this; - $I->amOnPage((\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminIntegrationByIdPage . $integrationId)); - $I->waitForPageLoad(); - } - - public function goToTheAdminAddIntegrationPage() - { - $I = $this; - $I->amOnPage(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminAddIntegrationPage); - $I->waitForPageLoad(); - } - - public function goToTheAdminCacheManagementGrid() - { - $I = $this; - $I->amOnPage(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminCacheManagementGrid); - $I->waitForPageLoad(); - } - - public function goToTheAdminBackupsGrid() - { - $I = $this; - $I->amOnPage(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminBackupsGrid); - $I->waitForPageLoad(); - } - - public function goToTheAdminIndexManagementGrid() - { - $I = $this; - $I->amOnPage(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminIndexManagementGrid); - $I->waitForPageLoad(); - } - - public function goToTheAdminWebSetupWizardPage() - { - $I = $this; - $I->amOnPage(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminWebSetupWizardPage); - $I->waitForPageLoad(); - } - - public function goToTheAdminAllUsersGrid() - { - $I = $this; - $I->amOnPage(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminAllUsersGrid); - $I->waitForPageLoad(); - } - - public function goToTheAdminUserForIdPage($userId) - { - $I = $this; - $I->amOnPage((\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminUserByIdPage . $userId)); - $I->waitForPageLoad(); - } - - public function goToTheAdminAddUserPage() - { - $I = $this; - $I->amOnPage(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminAddNewUserPage); - $I->waitForPageLoad(); - } - - public function goToTheAdminLockedUsersGrid() - { - $I = $this; - $I->amOnPage(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminLockedUsersGrid); - $I->waitForPageLoad(); - } - - public function goToTheAdminUserRolesGrid() - { - $I = $this; - $I->amOnPage(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminUserRolesGrid); - $I->waitForPageLoad(); - } - - public function goToTheAdminUserRoleForIdPage($userRoleId) - { - $I = $this; - $I->amOnPage((\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminUserRoleByIdPage . $userRoleId)); - $I->waitForPageLoad(); - } - - public function goToTheAdminAddUserRolePage() - { - $I = $this; - $I->amOnPage(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminAddUserRolePage); - $I->waitForPageLoad(); - } - - public function goToTheAdminNotificationsGrid() - { - $I = $this; - $I->amOnPage(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminNotificationsGrid); - $I->waitForPageLoad(); - } - - public function goToTheAdminCustomVariablesGrid() - { - $I = $this; - $I->amOnPage(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminCustomVariablesGrid); - $I->waitForPageLoad(); - } - - public function goToTheAdminCustomVariableForId($customVariableId) - { - $I = $this; - $I->amOnPage((\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminCustomVariableByIdPage . $customVariableId)); - $I->waitForPageLoad(); - } - - public function goToTheAdminAddCustomVariablePage() - { - $I = $this; - $I->amOnPage(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminAddCustomVariablePage); - $I->waitForPageLoad(); - } - - public function goToTheAdminEncryptionKeyPage() - { - $I = $this; - $I->amOnPage(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminEncryptionKeyPage); - $I->waitForPageLoad(); - } - - public function goToTheAdminFindPartnersAndExtensionsPage() - { - $I = $this; - $I->amOnPage(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminFindPartnersAndExtensions); - $I->waitForPageLoad(); - } - - // Key Admin Pages - public function shouldBeOnTheAdminLoginPage() - { - $I = $this; - $I->seeInCurrentUrl(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminLoginPage); - } - - public function shouldBeOnTheAdminDashboardPage() - { - $I = $this; - $I->seeInCurrentUrl(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminDashboardPage); - $I->see('Dashboard', self::$adminPageTitle); - } - - public function shouldBeOnTheAdminForgotYourPasswordPage() - { - $I = $this; - $I->seeInCurrentUrl(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminForgotYourPasswordPage); - } - - // Sales - public function shouldBeOnTheAdminOrdersGrid() - { - $I = $this; - $I->seeInCurrentUrl(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminOrdersGrid); - $I->see('Orders', self::$adminPageTitle); - } - - public function shouldBeOnTheAdminOrderForIdPage($orderId) - { - $I = $this; - $I->seeInCurrentUrl((\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminOrderByIdPage . $orderId)); - $I->see($orderId, self::$adminPageTitle); - } - - public function shouldBeOnTheAdminAddOrderPage() - { - $I = $this; - $I->seeInCurrentUrl(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminAddOrderPage); - $I->see('Create New Order', self::$adminPageTitle); - } - - public function shouldBeOnTheAdminAddOrderForCustomerIdPage($customerId) - { - $I = $this; - $I->seeInCurrentUrl((\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminAddOrderForCustomerIdPage . $customerId)); - $I->see('Create New Order', self::$adminPageTitle); - } - - public function shouldBeOnTheAdminInvoicesGrid() - { - $I = $this; - $I->seeInCurrentUrl(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminInvoicesGrid); - $I->see('Invoices', self::$adminPageTitle); - } - - public function shouldBeOnTheAdminAddInvoiceForOrderIdPage($orderId) - { - $I = $this; - $I->seeInCurrentUrl((\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminAddInvoiceForOrderIdPage . $orderId)); - $I->see('New Invoice', self::$adminPageTitle); - } - - public function shouldBeOnTheAdminShipmentsGrid() - { - $I = $this; - $I->seeInCurrentUrl(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminShipmentsGrid); - $I->see('Shipments', self::$adminPageTitle); - } - - public function shouldBeOnTheAdminShipmentForIdPage($shipmentId) - { - $I = $this; - $I->seeInCurrentUrl((\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminShipmentForIdPage . $shipmentId)); - $I->see('New Shipment'); - } - - public function shouldBeOnTheAdminCreditMemosGrid() - { - $I = $this; - $I->seeInCurrentUrl(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminCreditMemosGrid); - $I->see('Credit Memos', self::$adminPageTitle); - } - - public function shouldBeOnTheAdminCreditMemoForIdPage($creditMemoId) - { - $I = $this; - $I->seeInCurrentUrl((\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminCreditMemoForIdPage . $creditMemoId)); - $I->see('View Memo', self::$adminPageTitle); - } - - public function shouldBeOnTheAdminBillingAgreementsGrid() - { - $I = $this; - $I->seeInCurrentUrl(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminBillingAgreementsGrid); - $I->see('Billing Agreements', self::$adminPageTitle); - } - - public function shouldBeOnTheAdminTransactionsGrid() - { - $I = $this; - $I->seeInCurrentUrl(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminTransactionsGrid); - $I->see('Transactions', self::$adminPageTitle); - } - - // Products - public function shouldBeOnTheAdminCatalogGrid() - { - $I = $this; - $I->seeInCurrentUrl(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminCatalogGrid); - $I->see('Catalog', self::$adminPageTitle); - } - - public function shouldBeOnTheAdminProductForIdPage($productId, $productName) - { - $I = $this; - $I->seeInCurrentUrl((\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminProductForIdPage . $productId)); - $I->see($productName, self::$adminPageTitle); - } - - public function shouldBeOnTheAdminAddSimpleProductPage() - { - $I = $this; - $I->seeInCurrentUrl(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminAddSimpleProductPage); - $I->see('New Product', self::$adminPageTitle); - } - - public function shouldBeOnTheAdminAddConfigurableProductPage() - { - $I = $this; - $I->seeInCurrentUrl(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminAddConfigurableProductPage); - $I->see('New Product', self::$adminPageTitle); - } - - public function shouldBeOnTheAdminAddGroupedProductPage() - { - $I = $this; - $I->seeInCurrentUrl(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminAddGroupedProductPage); - $I->see('New Product', self::$adminPageTitle); - } - - public function shouldBeOnTheAdminAddVirtualProductPage() - { - $I = $this; - $I->seeInCurrentUrl(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminAddVirtualProductPage); - $I->see('New Product', self::$adminPageTitle); - } - - public function shouldBeOnTheAdminAddBundledProductPage() - { - $I = $this; - $I->seeInCurrentUrl(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminAddBundleProductPage); - $I->see('New Product', self::$adminPageTitle); - } - - public function shouldBeOnTheAdminAddDownloadableProductPage() - { - $I = $this; - $I->seeInCurrentUrl(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminAddDownloadableProductPage); - $I->see('New Product', self::$adminPageTitle); - } - - public function shouldBeOnTheAdminCategoriesPage() - { - $I = $this; - $I->seeInCurrentUrl(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminCategoriesPage); - $I->see('Default Category', self::$adminPageTitle); - } - - public function shouldBeOnTheAdminCategoryForIdPage($categoryId, $categoryName) - { - $I = $this; - $I->seeInCurrentUrl((\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminCategoryForIdPage . $categoryId)); - $I->see($categoryName, self::$adminPageTitle); - } - - public function shouldBeOnTheAdminAddRootCategoryForStoreIdPage($storeId) - { - $I = $this; - $I->seeInCurrentUrl(('/admin/catalog/category/add/store/' . $storeId . '/parent/1')); - $I->see('New Category', self::$adminPageTitle); - } - - public function shouldBeOnTheAdminAddSubCategoryForStoreIdPage($storeId) - { - $I = $this; - $I->seeInCurrentUrl(('/admin/catalog/category/add/store/' . $storeId . '/parent/2')); - $I->see('New Category', self::$adminPageTitle); - } - - // Customers - public function shouldBeOnTheAdminAllCustomersGrid() - { - $I = $this; - $I->seeInCurrentUrl(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminAllCustomersGrid); - $I->see('Customers', self::$adminPageTitle); - } - - public function shouldBeOnTheAdminCustomersNowOnlineGrid() - { - $I = $this; - $I->seeInCurrentUrl(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminCustomersNowOnlineGrid); - $I->see('Customers Now Online', self::$adminPageTitle); - } - - public function shouldBeOnTheAdminCustomerForIdPage($customerId, $customerName) - { - $I = $this; - $I->seeInCurrentUrl((\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminCustomerForCustomerIdPage . $customerId)); - $I->see($customerName, self::$adminPageTitle); - } - - public function shouldBeOnTheAdminAddCustomerPage() - { - $I = $this; - $I->seeInCurrentUrl(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminAddCustomerPage); - $I->see('New Customer', self::$adminPageTitle); - } - - // Marketing - public function shouldBeOnTheAdminCatalogPriceRuleGrid() - { - $I = $this; - $I->seeInCurrentUrl(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminCatalogPriceRuleGrid); - $I->see('Catalog Price Rule', self::$adminPageTitle); - } - - public function shouldBeOnTheAdminCatalogPriceRuleForIdPage($catalogPriceRuleId, $catalogPriceRuleName) - { - $I = $this; - $I->seeInCurrentUrl((\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminCatalogPriceRuleForIdPage . $catalogPriceRuleId)); - $I->see($catalogPriceRuleName, self::$adminPageTitle); - } - - public function shouldBeOnTheAdminAddCatalogPriceRulePage() - { - $I = $this; - $I->seeInCurrentUrl(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminAddCatalogPriceRulePage); - $I->see('New Catalog Price Rule', self::$adminPageTitle); - } - - public function shouldBeOnTheAdminCartPriceRulesGrid() - { - $I = $this; - $I->seeInCurrentUrl(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminCartPriceRulesGrid); - $I->see('Cart Price Rules', self::$adminPageTitle); - } - - public function shouldBeOnTheAdminCartPriceRuleForIdPage($cartPriceRuleId, $cartPriceRuleName) - { - $I = $this; - $I->seeInCurrentUrl((\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminCartPriceRuleForIdPage . $cartPriceRuleId)); - $I->see($cartPriceRuleName, self::$adminPageTitle); - } - - public function shouldBeOnTheAdminAddCartPriceRulePage() - { - $I = $this; - $I->seeInCurrentUrl(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminAddCartPriceRulePage); - $I->see('New Cart Price Rule', self::$adminPageTitle); - } - - public function shouldBeOnTheAdminEmailTemplatesGrid() - { - $I = $this; - $I->seeInCurrentUrl(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminEmailTemplatesGrid); - $I->see('Email Templates', self::$adminPageTitle); - } - - public function shouldBeOnTheAdminEmailTemplateForIdPage($emailTemplateId, $templateName) - { - $I = $this; - $I->seeInCurrentUrl((\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminEmailTemplateForIdPage . $emailTemplateId)); - $I->see($templateName, self::$adminPageTitle); - } - - public function shouldBeOnTheAdminAddEmailTemplatePage() - { - $I = $this; - $I->seeInCurrentUrl(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminAddEmailTemplatePage); - $I->see('New Template', self::$adminPageTitle); - } - - public function shouldBeOnTheAdminNewsletterTemplateGrid() - { - $I = $this; - $I->seeInCurrentUrl(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminNewsletterTemplateGrid); - $I->see('Newsletter Templates', self::$adminPageTitle); - } - - public function shouldBeOnTheAdminNewsletterTemplateByIdPage($newsletterTemplateId, $templateName) - { - $I = $this; - $I->seeInCurrentUrl((\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminNewsletterTemplateForIdPage . $newsletterTemplateId)); - $I->see($templateName, self::$adminPageTitle); - } - - public function shouldBeOnTheAdminAddNewsletterTemplatePage() - { - $I = $this; - $I->seeInCurrentUrl(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminAddNewsletterTemplatePage); - $I->see('New Template', self::$adminPageTitle); - } - - public function shouldBeOnTheAdminNewsletterQueueGrid() - { - $I = $this; - $I->seeInCurrentUrl(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminNewsletterQueueGrid); - $I->see('Newsletter Queue', self::$adminPageTitle); - } - - public function shouldBeOnTheAdminNewsletterSubscribersGrid() - { - $I = $this; - $I->seeInCurrentUrl(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminNewsletterSubscribersGrid); - $I->see('Newsletter Subscribers', self::$adminPageTitle); - } - - public function shouldBeOnTheAdminURLRewritesGrid() - { - $I = $this; - $I->seeInCurrentUrl(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminURLRewritesGrid); - $I->see('URL Rewrites', self::$adminPageTitle); - } - - public function shouldBeOnTheAdminURLRewriteForId($urlRewriteId) - { - $I = $this; - $I->seeInCurrentUrl((\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminURLRewriteForIdPage . $urlRewriteId)); - $I->see('Edit URL Rewrite for a', self::$adminPageTitle); - } - - public function shouldBeOnTheAdminAddURLRewritePage() - { - $I = $this; - $I->seeInCurrentUrl(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminAddURLRewritePage); - $I->see('Add New URL Rewrite', self::$adminPageTitle); - } - - public function shouldBeOnTheAdminSearchTermsGrid() - { - $I = $this; - $I->seeInCurrentUrl(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminSearchTermsGrid); - $I->see('Search Terms', self::$adminPageTitle); - } - - public function shouldBeOnTheAdminSearchTermForIdPage($searchTermId, $searchQuery) - { - $I = $this; - $I->seeInCurrentUrl((\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminSearchTermForIdPage . $searchTermId)); - $I->see($searchQuery, self::$adminPageTitle); - } - - public function shouldBeOnTheAdminAddSearchTermPage() - { - $I = $this; - $I->seeInCurrentUrl(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminAddSearchTermPage); - $I->see('New Search', self::$adminPageTitle); - } - - public function shouldBeOnTheAdminSearchSynonymsGrid() - { - $I = $this; - $I->seeInCurrentUrl(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminSearchSynonymsGrid); - $I->see('Search Synonyms', self::$adminPageTitle); - } - - public function shouldBeOnTheAdminSearchSynonymGroupByIdPage($searchSynonymId, $synonyms) - { - $I = $this; - $I->seeInCurrentUrl((\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminSearchSynonymGroupForIdPage . $searchSynonymId)); - $I->see($synonyms, self::$adminPageTitle); - } - - public function shouldBeOnTheAdminAddSearchSynonymGroupPage() - { - $I = $this; - $I->seeInCurrentUrl(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminAddSearchSynonymGroupPage); - $I->see('New Synonym Group', self::$adminPageTitle); - } - - public function shouldBeOnTheAdminSiteMapGrid() - { - $I = $this; - $I->seeInCurrentUrl(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminSiteMapGrid); - $I->see('Site Map', self::$adminPageTitle); - } - - public function shouldBeOnTheAdminSiteMapForIdPage($siteMapId, $fileName) - { - $I = $this; - $I->seeInCurrentUrl((\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminSiteMapForIdPage . $siteMapId)); - $I->see($fileName, self::$adminPageTitle); - } - - public function shouldBeOnTheAdminAddSiteMapPage() - { - $I = $this; - $I->seeInCurrentUrl(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminAddSiteMapPage); - $I->see('New Site Map', self::$adminPageTitle); - } - - public function shouldBeOnTheAdminReviewsGrid() - { - $I = $this; - $I->seeInCurrentUrl(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminReviewsGrid); - $I->see('Reviews', self::$adminPageTitle); - } - - public function shouldBeOnTheAdminReviewForIdPage($reviewId) - { - $I = $this; - $I->seeInCurrentUrl((\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminReviewByIdPage . $reviewId)); - $I->see('Edit Review', self::$adminPageTitle); - } - - public function shouldBeOnTheAdminAddReviewPage() - { - $I = $this; - $I->seeInCurrentUrl(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminAddReviewPage); - $I->see('New Review', self::$adminPageTitle); - } - - // Content - public function shouldBeOnTheAdminPagesGrid() - { - $I = $this; - $I->seeInCurrentUrl(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminPagesGrid); - $I->see('Pages', self::$adminPageTitle); - } - - public function shouldBeOnTheAdminPageForIdPage($pageId, $pageTitle) - { - $I = $this; - $I->seeInCurrentUrl((\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminPageForIdPage . $pageId)); - $I->see($pageTitle, self::$adminPageTitle); - } - - public function shouldBeOnTheAdminAddPagePage() - { - $I = $this; - $I->seeInCurrentUrl((\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminAddPagePage)); - $I->see('New Page', self::$adminPageTitle); - } - - public function shouldBeOnTheAdminBlocksGrid() - { - $I = $this; - $I->seeInCurrentUrl(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminBlocksGrid); - $I->see('Blocks', self::$adminPageTitle); - } - - public function shouldBeOnTheAdminBlockForIdPage($blockId, $blockTitle) - { - $I = $this; - $I->seeInCurrentUrl((\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminBlockForIdPage . $blockId)); - $I->see($blockTitle, self::$adminPageTitle); - } - - public function shouldBeOnTheAdminAddBlockPage() - { - $I = $this; - $I->seeInCurrentUrl(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminAddBlockPage); - $I->see('New Block', self::$adminPageTitle); - } - - public function shouldBeOnTheAdminWidgetsGrid() - { - $I = $this; - $I->seeInCurrentUrl(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminWidgetsGrid); - $I->see('Widgets', self::$adminPageTitle); - } - - public function shouldBeOnTheAdminAddWidgetPage() - { - $I = $this; - $I->seeInCurrentUrl(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminAddWidgetPage); - $I->see('Widgets', self::$adminPageTitle); - } - - public function shouldBeOnTheAdminDesignConfigurationGrid() - { - $I = $this; - $I->seeInCurrentUrl(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminDesignConfigurationGrid); - $I->see('Design Configuration', self::$adminPageTitle); - } - - public function shouldBeOnTheAdminThemesGrid() - { - $I = $this; - $I->seeInCurrentUrl(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminThemesGrid); - $I->see('Themes', self::$adminPageTitle); - } - - public function shouldBeOnTheAdminThemeByIdPage($themeId, $themeTitle) - { - $I = $this; - $I->seeInCurrentUrl((\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminThemeByIdPage . $themeId)); - $I->see($themeTitle); - } - - public function shouldBeOnTheAdminStoreContentScheduleGrid() - { - $I = $this; - $I->seeInCurrentUrl(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminStoreContentScheduleGrid); - $I->see('Store Design Schedule', self::$adminPageTitle); - } - - public function shouldBeOnTheAdminStoreContentScheduleForIdPage($storeContentScheduleId) - { - $I = $this; - $I->seeInCurrentUrl((\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminStoreContentScheduleForIdPage . $storeContentScheduleId)); - $I->see('Edit Store Design Change', self::$adminPageTitle); - } - - public function shouldBeOnTheAdminAddStoreDesignChangePage() - { - $I = $this; - $I->seeInCurrentUrl(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminAddStoreDesignChangePage); - $I->see('New Store Design Change'); - } - - // Reports - public function shouldBeOnTheAdminProductsInCartGrid() - { - $I = $this; - $I->seeInCurrentUrl(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminProductsInCartGrid); - $I->see('Products in Carts', self::$adminPageTitle); - } - - public function shouldBeOnTheAdminSearchTermsReportGrid() - { - $I = $this; - $I->seeInCurrentUrl(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminSearchTermsReportGrid); - $I->see('Search Terms Report', self::$adminPageTitle); - } - - public function shouldBeOnTheAdminAbandonedCartsGrid() - { - $I = $this; - $I->seeInCurrentUrl(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminAbandonedCartsGrid); - $I->see('Abandoned Carts', self::$adminPageTitle); - } - - public function shouldBeOnTheAdminNewsletterProblemsReportGrid() - { - $I = $this; - $I->seeInCurrentUrl(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminNewsletterProblemsReportGrid); - $I->see('Newsletter Problems Report', self::$adminPageTitle); - } - - public function shouldBeOnTheAdminCustomerReviewsReportGrid() - { - $I = $this; - $I->seeInCurrentUrl(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminCustomerReviewsReportGrid); - $I->see('Customer Reviews Report', self::$adminPageTitle); - } - - public function shouldBeOnTheAdminProductReviewsReportGrid() - { - $I = $this; - $I->seeInCurrentUrl(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminProductReviewsReportGrid); - $I->see('Product Reviews Report', self::$adminPageTitle); - } - - public function shouldBeOnTheAdminProductReviewsForProductIdPage($productId) - { - $I = $this; - $I->seeInCurrentUrl((\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminProductReviewsForProductIdPage . $productId)); - $I->see('Reviews', self::$adminPageTitle); - } - - public function shouldBeOnTheAdminProductReviewIdForProductIdPage($productReviewId, $productId) - { - $I = $this; - $I->seeInCurrentUrl(('/admin/review/product/edit/id/' . $productReviewId . '/productId/' . $productId)); - $I->see('Edit Review', self::$adminPageTitle); - } - - public function shouldBeOnTheAdminOrdersReportGrid() - { - $I = $this; - $I->seeInCurrentUrl(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminOrdersReportGrid); - $I->see('Orders Report', self::$adminPageTitle); - } - - public function shouldBeOnTheAdminTaxReportGrid() - { - $I = $this; - $I->seeInCurrentUrl(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminTaxReportGrid); - $I->see('Tax Report', self::$adminPageTitle); - } - - public function shouldBeOnTheAdminInvoiceReportGrid() - { - $I = $this; - $I->seeInCurrentUrl(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminInvoiceReportGrid); - $I->see('Invoice Report', self::$adminPageTitle); - } - - public function shouldBeOnTheAdminShippingReportGrid() - { - $I = $this; - $I->seeInCurrentUrl(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminShippingReportGrid); - $I->see('Shipping Report', self::$adminPageTitle); - } - - public function shouldBeOnTheAdminRefundsReportGrid() - { - $I = $this; - $I->seeInCurrentUrl(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminRefundsReportGrid); - $I->see('Refunds Report', self::$adminPageTitle); - } - - public function shouldBeOnTheAdminCouponsReportGrid() - { - $I = $this; - $I->seeInCurrentUrl(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminCouponsReportGrid); - $I->see('Coupons Report', self::$adminPageTitle); - } - - public function shouldBeOnTheAdminPayPalSettlementReportsGrid() - { - $I = $this; - $I->seeInCurrentUrl(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminPayPalSettlementReportsGrid); - $I->see('PayPal Settlement Reports', self::$adminPageTitle); - } - - public function shouldBeOnTheAdminBraintreeSettlementReportGrid() - { - $I = $this; - $I->seeInCurrentUrl(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminBraintreeSettlementReportGrid); - $I->see('Braintree Settlement Report', self::$adminPageTitle); - } - - public function shouldBeOnTheAdminOrderTotalReportGrid() - { - $I = $this; - $I->seeInCurrentUrl(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminOrderTotalReportGrid); - $I->see('Order Total Report', self::$adminPageTitle); - } - - public function shouldBeOnTheAdminOrderCountReportGrid() - { - $I = $this; - $I->seeInCurrentUrl(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminOrderCountReportGrid); - $I->see('Order Count Report', self::$adminPageTitle); - } - - public function shouldBeOnTheAdminNewAccountsReportGrid() - { - $I = $this; - $I->seeInCurrentUrl(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminNewAccountsReportGrid); - $I->see('New Accounts Report', self::$adminPageTitle); - } - - public function shouldBeOnTheAdminProductViewsReportGrid() - { - $I = $this; - $I->seeInCurrentUrl(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminProductViewsReportGrid); - $I->see('Product Views Report', self::$adminPageTitle); - } - - public function shouldBeOnTheAdminBestsellersReportGrid() - { - $I = $this; - $I->seeInCurrentUrl(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminBestsellersReportGrid); - $I->see('Bestsellers Report', self::$adminPageTitle); - } - - public function shouldBeOnTheAdminLowStockReportGrid() - { - $I = $this; - $I->seeInCurrentUrl(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminLowStockReportGrid); - $I->see('Low Stock Report', self::$adminPageTitle); - } - - public function shouldBeOnTheAdminOrderedProductsGrid() - { - $I = $this; - $I->seeInCurrentUrl(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminOrderedProductsReportGrid); - $I->see('Ordered Products Report', self::$adminPageTitle); - } - - public function shouldBeOnTheAdminDownloadsReportGrid() - { - $I = $this; - $I->seeInCurrentUrl(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminDownloadsReportGrid); - $I->see('Downloads Report', self::$adminPageTitle); - } - - public function shouldBeOnTheAdminRefreshStatisticsGrid() - { - $I = $this; - $I->seeInCurrentUrl(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminRefreshStatisticsGrid); - $I->see('Refresh Statistics', self::$adminPageTitle); - } - - // Stores - public function shouldBeOnTheAdminAllStoresGrid() - { - $I = $this; - $I->seeInCurrentUrl(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminAllStoresGrid); - $I->see('Stores', self::$adminPageTitle); - } - - public function shouldBeOnTheAdminCreateStoreViewPage() - { - $I = $this; - $I->seeInCurrentUrl(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminCreateStoreViewPage); - $I->see('Stores', self::$adminPageTitle); - } - - public function shouldBeOnTheAdminCreateStorePage() - { - $I = $this; - $I->seeInCurrentUrl(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminCreateStorePage); - $I->see('Stores', self::$adminPageTitle); - } - - public function shouldBeOnTheAdminCreateWebsitePage() - { - $I = $this; - $I->seeInCurrentUrl(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminCreateWebsitePage); - $I->see('Stores'); - } - - public function shouldBeOnTheAdminWebsiteForIdPage($websiteId) - { - $I = $this; - $I->seeInCurrentUrl((\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminWebsiteByIdPage . $websiteId)); - } - - public function shouldBeOnTheAdminStoreViewForIdPage($storeViewId) - { - $I = $this; - $I->seeInCurrentUrl((\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminStoreViewByIdPage . $storeViewId)); - } - - public function shouldBeOnTheAdminStoreForIdPage($storeId) - { - $I = $this; - $I->seeInCurrentUrl((\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminStoreByIdPage . $storeId)); - } - - public function shouldBeOnTheAdminConfigurationGrid() - { - $I = $this; - $I->seeInCurrentUrl(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminConfigurationGrid); - $I->see('Configuration', self::$adminPageTitle); - } - - public function shouldBeOnTheAdminTermsAndConditionsGrid() - { - $I = $this; - $I->seeInCurrentUrl(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminTermsAndConditionsGrid); - $I->see('Terms and Conditions', self::$adminPageTitle); - } - - public function shouldBeOnTheAdminTermsAndConditionForIdPage($termsAndConditionsId, $conditionName) - { - $I = $this; - $I->seeInCurrentUrl((\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminTermsAndConditionByIdPage . $termsAndConditionsId)); - $I->see($conditionName, self::$adminPageTitle); - } - - public function shouldBeOnTheAdminAddNewTermsAndConditionsPage() - { - $I = $this; - $I->seeInCurrentUrl(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminAddNewTermsAndConditionPage); - $I->see('New Condition', self::$adminPageTitle); - } - - public function shouldBeOnTheAdminOrderStatusGrid() - { - $I = $this; - $I->seeInCurrentUrl(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminOrderStatusGrid); - $I->see('Order Status', self::$adminPageTitle); - } - - public function shouldBeOnTheAdminAddOrderStatusPage() - { - $I = $this; - $I->seeInCurrentUrl(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminAddOrderStatusPage); - $I->see('Create New Order Status', self::$adminPageTitle); - } - - public function shouldBeOnTheAdminTaxRulesGrid() - { - $I = $this; - $I->seeInCurrentUrl(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminTaxRulesGrid); - $I->see('Tax Rules', self::$adminPageTitle); - } - - public function shouldBeOnTheAdminTaxRuleForIdPage($taxRuleId, $taxRuleName) - { - $I = $this; - $I->seeInCurrentUrl((\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminTaxRuleByIdPage . $taxRuleId)); - $I->see($taxRuleName, self::$adminPageTitle); - } - - public function shouldBeOnTheAdminAddTaxRulePage() - { - $I = $this; - $I->seeInCurrentUrl(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminAddTaxRulePage); - $I->see('New Tax Rule', self::$adminPageTitle); - } - - public function shouldBeOnTheAdminTaxZonesAndRatesGrid() - { - $I = $this; - $I->seeInCurrentUrl(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminTaxZonesAndRatesGrid); - $I->see('Tax Zones and Rates', self::$adminPageTitle); - } - - public function shouldBeOnTheAdminTaxZoneAndRateForIdPage($taxZoneAndRateId, $taxIdentifier) - { - $I = $this; - $I->seeInCurrentUrl((\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminTaxZoneAndRateByIdPage . $taxZoneAndRateId)); - $I->see($taxIdentifier, self::$adminPageTitle); - } - - public function shouldBeOnTheAdminAddTaxZoneAndRatePage() - { - $I = $this; - $I->seeInCurrentUrl(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminAddTaxZoneAndRatePage); - $I->see('New Tax Rate', self::$adminPageTitle); - } - - public function shouldBeOnTheAdminCurrencyRatesPage() - { - $I = $this; - $I->seeInCurrentUrl(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminCurrencyRatesPage); - $I->see('Currency Rates', self::$adminPageTitle); - } - - public function shouldBeOnTheAdminCurrencySymbolsPage() - { - $I = $this; - $I->seeInCurrentUrl(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminCurrencySymbolsPage); - $I->see('Currency Symbols', self::$adminPageTitle); - } - - public function shouldBeOnTheAdminProductAttributesGrid() - { - $I = $this; - $I->seeInCurrentUrl(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminProductAttributesGrid); - $I->see('Product Attributes', self::$adminPageTitle); - } - - public function shouldBeOnTheAdminProductAttributeForIdPage($productAttributeId, $productAttributeDefaultLabel) - { - $I = $this; - $I->seeInCurrentUrl((\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminProductAttributeForIdPage . $productAttributeId)); - $I->see($productAttributeDefaultLabel, self::$adminPageTitle); - } - - public function shouldBeOnTheAdminAddProductAttributePage() - { - $I = $this; - $I->seeInCurrentUrl(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminAddProductAttributePage); - $I->see('New Product Attribute', self::$adminPageTitle); - } - - public function shouldBeOnTheAdminAttributeSetsGrid() - { - $I = $this; - $I->seeInCurrentUrl(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminAttributeSetsGrid); - $I->see('Attribute Sets', self::$adminPageTitle); - } - - public function shouldBeOnTheAdminAttributeSetByIdPage($attributeSetId, $attributeSetName) - { - $I = $this; - $I->seeInCurrentUrl((\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminAttributeSetByIdPage . $attributeSetId)); - $I->see($attributeSetName, self::$adminPageTitle); - } - - public function shouldBeOnTheAdminAddAttributeSetPage() - { - $I = $this; - $I->seeInCurrentUrl(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminAddAttributeSetPage); - $I->see('New Attribute Set', self::$adminPageTitle); - } - - public function shouldBeOnTheAdminRatingsGrid() - { - $I = $this; - $I->seeInCurrentUrl(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminRatingsGrid); - $I->see('Ratings', self::$adminPageTitle); - } - - public function shouldBeOnTheAdminRatingForIdPage($ratingId, $ratingDefaultValue) - { - $I = $this; - $I->seeInCurrentUrl((\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminRatingForIdPage . $ratingId)); - $I->see($ratingDefaultValue, self::$adminPageTitle); - } - - public function shouldBeOnTheAdminAddRatingPage() - { - $I = $this; - $I->seeInCurrentUrl(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminAddRatingPage); - $I->see('New Rating', self::$adminPageTitle); - } - - public function shouldBeOnTheAdminCustomerGroupsGrid() - { - $I = $this; - $I->seeInCurrentUrl(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminCustomerGroupsGrid); - $I->see('Customer Groups', self::$adminPageTitle); - } - - public function shouldBeOnTheAdminCustomerGroupForIdPage($customerGroupId, $customerGroupName) - { - $I = $this; - $I->seeInCurrentUrl((\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminCustomerGroupByIdPage . $customerGroupId)); - $I->see($customerGroupName, self::$adminPageTitle); - } - - public function shouldBeOnTheAdminAddCustomerGroupPage() - { - $I = $this; - $I->seeInCurrentUrl(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminAddCustomerGroupPage); - $I->see('New Customer Group', self::$adminPageTitle); - } - - // System - public function shouldBeOnTheAdminImportPage() - { - $I = $this; - $I->seeInCurrentUrl(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminImportPage); - $I->see('Import', self::$adminPageTitle); - } - - public function shouldBeOnTheAdminExportPage() - { - $I = $this; - $I->seeInCurrentUrl(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminExportPage); - $I->see('Export', self::$adminPageTitle); - } - - public function shouldBeOnTheAdminImportAndExportTaxRatesPage() - { - $I = $this; - $I->seeInCurrentUrl(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminImportAndExportTaxRatesPage); - $I->see('Import and Export Tax Rates', self::$adminPageTitle); - } - - public function shouldBeOnTheAdminImportHistoryGrid() - { - $I = $this; - $I->seeInCurrentUrl(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminImportHistoryGrid); - $I->see('Import History', self::$adminPageTitle); - } - - public function shouldBeOnTheAdminIntegrationsGrid() - { - $I = $this; - $I->seeInCurrentUrl(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminIntegrationsGrid); - $I->see('Integrations', self::$adminPageTitle); - } - - public function shouldBeOnTheAdminIntegrationForIdPage($integrationId, $integrationName) - { - $I = $this; - $I->seeInCurrentUrl((\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminIntegrationByIdPage . $integrationId)); - $I->see('Edit', self::$adminPageTitle); - $I->see($integrationName, self::$adminPageTitle); - $I->see('Integration', self::$adminPageTitle); - } - - public function shouldBeOnTheAdminAddIntegrationPage() - { - $I = $this; - $I->seeInCurrentUrl(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminAddIntegrationPage); - $I->see('New Integration', self::$adminPageTitle); - } - - public function shouldBeOnTheAdminCacheManagementGrid() - { - $I = $this; - $I->seeInCurrentUrl(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminCacheManagementGrid); - $I->see('Cache Management', self::$adminPageTitle); - } - - public function shouldBeOnTheAdminBackupsGrid() - { - $I = $this; - $I->seeInCurrentUrl(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminBackupsGrid); - $I->see('Backups', self::$adminPageTitle); - } - - public function shouldBeOnTheAdminIndexManagementGrid() - { - $I = $this; - $I->seeInCurrentUrl(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminIndexManagementGrid); - $I->see('Index Management', self::$adminPageTitle); - } - - public function shouldBeOnTheAdminWebSetupWizardPage() - { - $I = $this; - $I->seeInCurrentUrl(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminWebSetupWizardPage); - $I->see('Setup Wizard', self::$adminPageTitle); - } - - public function shouldBeOnTheAdminAllUsersGrid() - { - $I = $this; - $I->seeInCurrentUrl(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminAllUsersGrid); - $I->see('Users', self::$adminPageTitle); - } - - public function shouldBeOnTheAdminUserForIdPage($userId, $userFirstAndLastName) - { - $I = $this; - $I->seeInCurrentUrl((\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminUserByIdPage . $userId)); - $I->see($userFirstAndLastName, self::$adminPageTitle); - } - - public function shouldBeOnTheAdminAddUserPage() - { - $I = $this; - $I->seeInCurrentUrl(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminAddNewUserPage); - $I->see('New User', self::$adminPageTitle); - } - - public function shouldBeOnTheAdminLockedUsersGrid() - { - $I = $this; - $I->seeInCurrentUrl(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminLockedUsersGrid); - $I->see('Locked Users', self::$adminPageTitle); - } - - public function shouldBeOnTheAdminUserRolesGrid() - { - $I = $this; - $I->seeInCurrentUrl(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminUserRolesGrid); - $I->see('Roles', self::$adminPageTitle); - } - - public function shouldBeOnTheAdminUserRoleForIdPage($userRoleId, $userRoleName) - { - $I = $this; - $I->seeInCurrentUrl((\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminUserRoleByIdPage . $userRoleId)); - $I->see($userRoleName, self::$adminPageTitle); - } - - public function shouldBeOnTheAdminAddUserRolePage() - { - $I = $this; - $I->seeInCurrentUrl(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminAddUserRolePage); - $I->see('New Role', self::$adminPageTitle); - } - - public function shouldBeOnTheAdminNotificationsGrid() - { - $I = $this; - $I->seeInCurrentUrl(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminNotificationsGrid); - $I->see('Notifications', self::$adminPageTitle); - } - - public function shouldBeOnTheAdminCustomVariablesGrid() - { - $I = $this; - $I->seeInCurrentUrl(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminCustomVariablesGrid); - $I->see('Custom Variables', self::$adminPageTitle); - } - - public function shouldBeOnTheAdminCustomVariableForId($customVariableId, $customVariableCode) - { - $I = $this; - $I->seeInCurrentUrl((\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminCustomVariableByIdPage . $customVariableId)); - $I->see($customVariableCode, self::$adminPageTitle); - } - - public function shouldBeOnTheAdminAddCustomVariablePage() - { - $I = $this; - $I->seeInCurrentUrl(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminAddCustomVariablePage); - $I->see('New Custom Variable', self::$adminPageTitle); - } - - public function shouldBeOnTheAdminEncryptionKeyPage() - { - $I = $this; - $I->seeInCurrentUrl(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminEncryptionKeyPage); - $I->see('Encryption Key', self::$adminPageTitle); - } - - public function shouldBeOnTheAdminFindPartnersAndExtensionsPage() { - $I = $this; - $I->seeInCurrentUrl(\Magento\FunctionalTestingFramework\Helper\AdminUrlList::$adminFindPartnersAndExtensions); - $I->see('Magento Marketplace', self::$adminPageTitle); - } -} diff --git a/src/Magento/FunctionalTestingFramework/Suite/Config/SuiteDom.php b/src/Magento/FunctionalTestingFramework/Suite/Config/SuiteDom.php new file mode 100644 index 000000000..c06c62024 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Suite/Config/SuiteDom.php @@ -0,0 +1,84 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\FunctionalTestingFramework\Suite\Config; + +use Magento\FunctionalTestingFramework\Exceptions\Collector\ExceptionCollector; +use Magento\FunctionalTestingFramework\Util\Validation\SingleNodePerFileValidationUtil; + +/** + * MFTF suite.xml configuration XML DOM utility + * @package Magento\FunctionalTestingFramework\Suite\Config + */ +class SuiteDom extends \Magento\FunctionalTestingFramework\Config\MftfDom +{ + const SUITE_META_FILENAME_ATTRIBUTE = "filename"; + + /** SingleNodePerFileValidationUtil + * + * @var SingleNodePerFileValidationUtil + */ + private $singleNodePerFileValidationUtil; + + /** + * Entity Dom constructor. + * @param string $xml + * @param string $filename + * @param ExceptionCollector $exceptionCollector + * @param array $idAttributes + * @param string $typeAttributeName + * @param string $schemaFile + * @param string $errorFormat + */ + public function __construct( + $xml, + $filename, + $exceptionCollector, + array $idAttributes = [], + $typeAttributeName = null, + $schemaFile = null, + $errorFormat = self::ERROR_FORMAT_DEFAULT + ) { + $this->singleNodePerFileValidationUtil = new SingleNodePerFileValidationUtil($exceptionCollector); + parent::__construct( + $xml, + $filename, + $exceptionCollector, + $idAttributes, + $typeAttributeName, + $schemaFile, + $errorFormat + ); + } + + /** + * Takes a dom element from xml and appends the filename based on location + * + * @param string $xml + * @param string|null $filename + * @return \DOMDocument + */ + public function initDom($xml, $filename = null) + { + $dom = parent::initDom($xml, $filename); + + if ($dom->getElementsByTagName('suites')->length > 0) { + // Validate single suite node per file + $this->singleNodePerFileValidationUtil->validateSingleNodeForTag( + $dom, + 'suite', + $filename + ); + if ($dom->getElementsByTagName('suite')->length > 0) { + /** @var \DOMElement $suiteNode */ + $suiteNode = $dom->getElementsByTagName('suite')[0]; + $suiteNode->setAttribute(self::SUITE_META_FILENAME_ATTRIBUTE, $filename); + } + } + + return $dom; + } +} diff --git a/src/Magento/FunctionalTestingFramework/Suite/Generators/GroupClassGenerator.php b/src/Magento/FunctionalTestingFramework/Suite/Generators/GroupClassGenerator.php index 415d884b6..e160c4875 100644 --- a/src/Magento/FunctionalTestingFramework/Suite/Generators/GroupClassGenerator.php +++ b/src/Magento/FunctionalTestingFramework/Suite/Generators/GroupClassGenerator.php @@ -6,6 +6,7 @@ namespace Magento\FunctionalTestingFramework\Suite\Generators; +use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; use Magento\FunctionalTestingFramework\Exceptions\TestReferenceException; use Magento\FunctionalTestingFramework\Suite\Objects\SuiteObject; use Magento\FunctionalTestingFramework\Test\Objects\ActionObject; @@ -32,6 +33,10 @@ class GroupClassGenerator 'comment' => 'print' ]; const GROUP_DIR_NAME = 'Group'; + const FUNCTION_PLACEHOLDER = 'PLACEHOLDER'; + const FUNCTION_START = 'this->getModuleForAction("'; + const FUNCTION_END = '")'; + const FUNCTION_REPLACE_REGEX = '/(PLACEHOLDER->([^\(]+))\(/'; /** * Mustache_Engine instance for template loading @@ -131,19 +136,23 @@ private function buildHookMustacheArray($hookObj) { $actions = []; $mustacheHookArray['actions'][] = ['webDriverInit' => true]; + $mustacheHookArray['helpers'] = []; foreach ($hookObj->getActions() as $action) { /** @var ActionObject $action */ $index = count($actions); + if ($action->getType() === ActionObject::ACTION_TYPE_HELPER) { + $mustacheHookArray['helpers'][] = $action->getCustomActionAttributes()['class']; + } //deleteData contains either url or createDataKey, if it contains the former it needs special formatting if ($action->getType() !== "createData" && !array_key_exists(TestGenerator::REQUIRED_ENTITY_REFERENCE, $action->getCustomActionAttributes())) { - $actions = $this->buildWebDriverActionsMustacheArray($action, $actions, $index); + $actions = $this->buildModuleActionsMustacheArray($action, $actions); continue; } // add these as vars to be created a class level in the template - if ($action->getType() == 'createData') { + if ($action->getType() === 'createData') { $mustacheHookArray[self::MUSTACHE_VAR_TAG][] = [self::ENTITY_MERGE_KEY => $action->getStepKey()]; } @@ -161,7 +170,7 @@ private function buildHookMustacheArray($hookObj) } /** - * Takes an action object and array of generated action steps. Converst the action object into generated php and + * Takes an action object and array of generated action steps. Convert the action object into generated php and * appends the entry to the given array. The result is returned by the function. * * @param ActionObject $action @@ -169,14 +178,22 @@ private function buildHookMustacheArray($hookObj) * @return array * @throws TestReferenceException */ - private function buildWebDriverActionsMustacheArray($action, $actionEntries) + private function buildModuleActionsMustacheArray($action, $actionEntries) { - $step = TestGenerator::getInstance()->generateStepsPhp([$action], TestGenerator::SUITE_SCOPE, 'webDriver'); + $step = TestGenerator::getInstance()->generateStepsPhp( + [$action], + TestGenerator::SUITE_SCOPE, + self::FUNCTION_PLACEHOLDER + ); $rawPhp = str_replace(["\t"], "", $step); $multipleCommands = explode(PHP_EOL, $rawPhp, -1); $multipleCommands = array_filter($multipleCommands); foreach ($multipleCommands as $command) { - $actionEntries = $this->replaceReservedTesterFunctions($command . PHP_EOL, $actionEntries, 'webDriver'); + $actionEntries = $this->replaceReservedTesterFunctions( + $command . PHP_EOL, + $actionEntries, + self::FUNCTION_PLACEHOLDER + ); } return $actionEntries; @@ -196,11 +213,21 @@ private function replaceReservedTesterFunctions($formattedStep, $actionEntries, $formattedStep = rtrim($formattedStep); foreach (self::REPLACEMENT_ACTIONS as $testAction => $replacement) { $testActionCall = "\${$actor}->{$testAction}"; - if (substr($formattedStep, 0, strlen($testActionCall)) == $testActionCall) { + if (substr($formattedStep, 0, strlen($testActionCall)) === $testActionCall) { $resultingStep = str_replace($testActionCall, $replacement, $formattedStep); $actionEntries[] = ['action' => $resultingStep]; } else { - $actionEntries[] = ['action' => $formattedStep]; + $placeholder = self::FUNCTION_PLACEHOLDER; + $begin = self::FUNCTION_START; + $end = self::FUNCTION_END; + $resultingStep = preg_replace_callback( + self::FUNCTION_REPLACE_REGEX, + function ($matches) use ($placeholder, $begin, $end) { + return str_replace($placeholder, $begin . $matches[2] . $end, $matches[1]) . '('; + }, + $formattedStep + ); + $actionEntries[] = ['action' => $resultingStep]; } } @@ -221,16 +248,16 @@ private function buildPersistenceMustacheArray($action, $entityArray) $action->getCustomActionAttributes()[TestGenerator::REQUIRED_ENTITY_REFERENCE]; // append entries for any required entities to this entry - if (array_key_exists('requiredEntities', $action->getCustomActionAttributes())) { - $entityArray[self::REQUIRED_ENTITY_KEY] = - $this->buildReqEntitiesMustacheArray($action->getCustomActionAttributes()); + $requiredEntities = $this->buildReqEntitiesMustacheArray($action->getCustomActionAttributes()); + if (!array_key_exists(-1, $requiredEntities)) { + $entityArray[self::REQUIRED_ENTITY_KEY] = $requiredEntities; } // append entries for customFields if specified by the user. if (array_key_exists('customFields', $action->getCustomActionAttributes())) { $entityArray['customFields'] = $action->getStepKey() . 'Fields'; } - + return $entityArray; } @@ -251,7 +278,7 @@ private function buildReqEntitiesMustacheArray($customAttributes) continue; } - if ($attribute[ActionObjectExtractor::NODE_NAME] == 'requiredEntity') { + if ($attribute[ActionObjectExtractor::NODE_NAME] === 'requiredEntity') { $requiredEntities[] = [self::ENTITY_NAME_TAG => $attribute[TestGenerator::REQUIRED_ENTITY_REFERENCE]]; } } diff --git a/src/Magento/FunctionalTestingFramework/Suite/Handlers/SuiteObjectHandler.php b/src/Magento/FunctionalTestingFramework/Suite/Handlers/SuiteObjectHandler.php index 30a67c31d..7583f2472 100644 --- a/src/Magento/FunctionalTestingFramework/Suite/Handlers/SuiteObjectHandler.php +++ b/src/Magento/FunctionalTestingFramework/Suite/Handlers/SuiteObjectHandler.php @@ -5,6 +5,10 @@ */ namespace Magento\FunctionalTestingFramework\Suite\Handlers; +use Magento\FunctionalTestingFramework\Config\MftfApplicationConfig; +use Magento\FunctionalTestingFramework\Exceptions\FastFailException; +use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; +use Magento\FunctionalTestingFramework\Exceptions\TestReferenceException; use Magento\FunctionalTestingFramework\Exceptions\XmlException; use Magento\FunctionalTestingFramework\ObjectManager\ObjectHandlerInterface; use Magento\FunctionalTestingFramework\ObjectManagerFactory; @@ -12,6 +16,8 @@ use Magento\FunctionalTestingFramework\Suite\Parsers\SuiteDataParser; use Magento\FunctionalTestingFramework\Suite\Util\SuiteObjectExtractor; use Magento\FunctionalTestingFramework\Test\Util\ObjectExtractor; +use Magento\FunctionalTestingFramework\Util\GenerationErrorHandler; +use Magento\FunctionalTestingFramework\Util\Logger\LoggingUtil; /** * Class SuiteObjectHandler @@ -52,11 +58,11 @@ private function __clone() * Function to enforce singleton design pattern * * @return ObjectHandlerInterface - * @throws XmlException + * @throws FastFailException */ public static function getInstance(): ObjectHandlerInterface { - if (self::$instance == null) { + if (self::$instance === null) { self::$instance = new SuiteObjectHandler(); self::$instance->initSuiteData(); } @@ -73,7 +79,9 @@ public static function getInstance(): ObjectHandlerInterface public function getObject($objectName): SuiteObject { if (!array_key_exists($objectName, $this->suiteObjects)) { - trigger_error("Suite ${objectName} is not defined.", E_USER_ERROR); + throw new TestReferenceException( + "Suite ${objectName} is not defined in xml or is invalid." + ); } return $this->suiteObjects[$objectName]; } @@ -92,6 +100,7 @@ public function getAllObjects(): array * Function which return all tests referenced by suites. * * @return array + * @throws TestFrameworkException */ public function getAllTestReferences(): array { @@ -113,11 +122,16 @@ public function getAllTestReferences(): array * * @return void * @SuppressWarnings(PHPMD.UnusedPrivateMethod) - * @throws XmlException + * @throws FastFailException */ private function initSuiteData() { - $suiteDataParser = ObjectManagerFactory::getObjectManager()->create(SuiteDataParser::class); + try { + $suiteDataParser = ObjectManagerFactory::getObjectManager()->create(SuiteDataParser::class); + } catch (\Exception $e) { + throw new FastFailException("Suite Data Parser Error: " . $e->getMessage()); + } + $suiteObjectExtractor = new SuiteObjectExtractor(); $this->suiteObjects = $suiteObjectExtractor->parseSuiteDataIntoObjects($suiteDataParser->readSuiteData()); } diff --git a/src/Magento/FunctionalTestingFramework/Suite/Objects/SuiteObject.php b/src/Magento/FunctionalTestingFramework/Suite/Objects/SuiteObject.php index d7f6bd18a..57d5c11f0 100644 --- a/src/Magento/FunctionalTestingFramework/Suite/Objects/SuiteObject.php +++ b/src/Magento/FunctionalTestingFramework/Suite/Objects/SuiteObject.php @@ -5,8 +5,12 @@ */ namespace Magento\FunctionalTestingFramework\Suite\Objects; +use Magento\FunctionalTestingFramework\Config\MftfApplicationConfig; +use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; +use Magento\FunctionalTestingFramework\Filter\FilterInterface; use Magento\FunctionalTestingFramework\Test\Objects\TestHookObject; use Magento\FunctionalTestingFramework\Test\Objects\TestObject; +use Magento\FunctionalTestingFramework\Util\Logger\LoggingUtil; /** * Class SuiteObject @@ -41,19 +45,28 @@ class SuiteObject */ private $hooks; + /** + * Filename of where the suite came from + * + * @var string + */ + private $filename; + /** * SuiteObject constructor. * @param string $name * @param TestObject[] $includeTests * @param TestObject[] $excludeTests * @param TestHookObject[] $hooks + * @param string $filename */ - public function __construct($name, $includeTests, $excludeTests, $hooks) + public function __construct($name, $includeTests, $excludeTests, $hooks, $filename = null) { $this->name = $name; $this->includeTests = $includeTests; $this->excludeTests = $excludeTests; $this->hooks = $hooks; + $this->filename = $filename; } /** @@ -70,6 +83,7 @@ public function getName() * Returns an array of Test Objects based on specifications in exclude and include arrays. * * @return array + * @throws TestFrameworkException */ public function getTests() { @@ -84,6 +98,7 @@ public function getTests() * @param TestObject[] $includeTests * @param TestObject[] $excludeTests * @return TestObject[] + * @throws TestFrameworkException */ private function resolveTests($includeTests, $excludeTests) { @@ -95,12 +110,10 @@ private function resolveTests($includeTests, $excludeTests) unset($finalTestList[$testName]); } - if (empty($finalTestList)) { - trigger_error( - "Current suite configuration for " . - $this->name . " contains no tests.", - E_USER_WARNING - ); + $filters = MftfApplicationConfig::getConfig()->getFilterList()->getFilters(); + /** @var FilterInterface $filter */ + foreach ($filters as $filter) { + $filter->filter($finalTestList); } return $finalTestList; @@ -146,4 +159,14 @@ public function getAfterHook() { return $this->hooks['after'] ?? null; } + + /** + * Getter for the Suite Filename + * + * @return string + */ + public function getFilename() + { + return $this->filename; + } } diff --git a/src/Magento/FunctionalTestingFramework/Suite/Service/SuiteGeneratorService.php b/src/Magento/FunctionalTestingFramework/Suite/Service/SuiteGeneratorService.php new file mode 100644 index 000000000..58823e195 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Suite/Service/SuiteGeneratorService.php @@ -0,0 +1,160 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\FunctionalTestingFramework\Suite\Service; + +use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; +use Magento\FunctionalTestingFramework\Exceptions\TestReferenceException; +use Magento\FunctionalTestingFramework\Suite\SuiteGenerator; +use Magento\FunctionalTestingFramework\Util\Path\FilePathFormatter; +use Magento\FunctionalTestingFramework\Util\TestGenerator; +use Symfony\Component\Yaml\Yaml; + +/** + * Class SuiteGeneratorService + */ +class SuiteGeneratorService +{ + /** + * Singleton SuiteGeneratorService Instance. + * + * @var SuiteGeneratorService + */ + private static $INSTANCE; + + /** + * SuiteGeneratorService constructor. + */ + private function __construct() + { + } + + /** + * Get CestFileCreatorUtil instance. + * + * @return SuiteGeneratorService + */ + public static function getInstance(): SuiteGeneratorService + { + if (!self::$INSTANCE) { + self::$INSTANCE = new SuiteGeneratorService(); + } + + return self::$INSTANCE; + } + + /** + * Function which takes the current config.yml array and clears any previous configuration for suite group object + * files. + * + * @return void + * @throws TestFrameworkException + */ + public function clearPreviousSessionConfigEntries(): void + { + $ymlArray = self::getYamlFileContents(); + $newYmlArray = $ymlArray; + // if the yaml entries haven't already been cleared + if (array_key_exists(SuiteGenerator::YAML_EXTENSIONS_TAG, $ymlArray)) { + $ymlEntries = $ymlArray[SuiteGenerator::YAML_EXTENSIONS_TAG][SuiteGenerator::YAML_ENABLED_TAG]; + + foreach ($ymlEntries as $key => $entry) { + if (preg_match('/(Group\\\\.*)/', $entry)) { + unset($newYmlArray[SuiteGenerator::YAML_EXTENSIONS_TAG][SuiteGenerator::YAML_ENABLED_TAG][$key]); + } + } + // needed for proper yml file generation based on indices + $newYmlArray[SuiteGenerator::YAML_EXTENSIONS_TAG][SuiteGenerator::YAML_ENABLED_TAG] = + array_values($newYmlArray[SuiteGenerator::YAML_EXTENSIONS_TAG][SuiteGenerator::YAML_ENABLED_TAG]); + } + + if (array_key_exists(SuiteGenerator::YAML_GROUPS_TAG, $newYmlArray)) { + unset($newYmlArray[SuiteGenerator::YAML_GROUPS_TAG]); + } + $ymlText = SuiteGenerator::YAML_COPYRIGHT_TEXT . Yaml::dump($newYmlArray, 10); + file_put_contents(self::getYamlConfigFilePath() . SuiteGenerator::YAML_CODECEPTION_CONFIG_FILENAME, $ymlText); + } + + /** + * Function which accepts a suite name and suite path and appends a new group entry to the codeception.yml.dist + * file in order to register the set of tests as a new group. Also appends group object location if required + * by suite. + * + * @param string $suiteName + * @param string $suitePath + * @param string|null $groupNamespace + * + * @return void + * @throws TestFrameworkException + */ + public function appendEntriesToConfig(string $suiteName, string $suitePath, ?string $groupNamespace): void + { + $relativeSuitePath = substr($suitePath, strlen(TESTS_BP)); + $relativeSuitePath = ltrim($relativeSuitePath, DIRECTORY_SEPARATOR); + $ymlArray = self::getYamlFileContents(); + + if (!array_key_exists(SuiteGenerator::YAML_GROUPS_TAG, $ymlArray)) { + $ymlArray[SuiteGenerator::YAML_GROUPS_TAG] = []; + } + + if ($groupNamespace) { + $ymlArray[SuiteGenerator::YAML_EXTENSIONS_TAG][SuiteGenerator::YAML_ENABLED_TAG][] = $groupNamespace; + } + + $ymlArray[SuiteGenerator::YAML_GROUPS_TAG][$suiteName] = [$relativeSuitePath]; + $ymlText = SuiteGenerator::YAML_COPYRIGHT_TEXT . Yaml::dump($ymlArray, 10); + file_put_contents(self::getYamlConfigFilePath() . SuiteGenerator::YAML_CODECEPTION_CONFIG_FILENAME, $ymlText); + } + + /** + * Function which takes a string which is the desired output directory (under _generated) and an array of tests + * relevant to the suite to be generated. The function takes this information and creates a new instance of the + * test generator which is then called to create all the test files for the suite. + * + * @param string $path + * @param array $tests + * + * @return void + * @throws TestReferenceException + */ + public function generateRelevantGroupTests(string $path, array $tests): void + { + $testGenerator = TestGenerator::getInstance($path, $tests); + $testGenerator->createAllTestFiles(null, []); + } + + /** + * Function to return contents of codeception.yml file for config changes. + * + * @return array + * @throws TestFrameworkException + */ + private static function getYamlFileContents(): array + { + $configYmlFile = self::getYamlConfigFilePath() . SuiteGenerator::YAML_CODECEPTION_CONFIG_FILENAME; + $defaultConfigYmlFile = self::getYamlConfigFilePath() . SuiteGenerator::YAML_CODECEPTION_DIST_FILENAME; + + if (file_exists($configYmlFile)) { + $ymlContents = file_get_contents($configYmlFile); + } else { + $ymlContents = file_get_contents($defaultConfigYmlFile); + } + + return Yaml::parse($ymlContents) ?? []; + } + + /** + * Static getter for the Config yml filepath (as path cannot be stored in a const). + * + * @return string + * @throws TestFrameworkException + */ + private static function getYamlConfigFilePath(): string + { + return FilePathFormatter::format(TESTS_BP); + } +} diff --git a/src/Magento/FunctionalTestingFramework/Suite/SuiteGenerator.php b/src/Magento/FunctionalTestingFramework/Suite/SuiteGenerator.php index 9f0045d19..44e4e0b45 100644 --- a/src/Magento/FunctionalTestingFramework/Suite/SuiteGenerator.php +++ b/src/Magento/FunctionalTestingFramework/Suite/SuiteGenerator.php @@ -6,18 +6,27 @@ namespace Magento\FunctionalTestingFramework\Suite; +use Magento\FunctionalTestingFramework\Exceptions\Collector\ExceptionCollector; +use Magento\FunctionalTestingFramework\Exceptions\FastFailException; use Magento\FunctionalTestingFramework\Exceptions\TestReferenceException; use Magento\FunctionalTestingFramework\Exceptions\XmlException; use Magento\FunctionalTestingFramework\Suite\Generators\GroupClassGenerator; use Magento\FunctionalTestingFramework\Suite\Handlers\SuiteObjectHandler; use Magento\FunctionalTestingFramework\Suite\Objects\SuiteObject; +use Magento\FunctionalTestingFramework\Suite\Service\SuiteGeneratorService; use Magento\FunctionalTestingFramework\Test\Handlers\TestObjectHandler; use Magento\FunctionalTestingFramework\Util\Filesystem\DirSetupUtil; +use Magento\FunctionalTestingFramework\Util\GenerationErrorHandler; use Magento\FunctionalTestingFramework\Util\Logger\LoggingUtil; use Magento\FunctionalTestingFramework\Util\Manifest\BaseTestManifest; +use Magento\FunctionalTestingFramework\Util\Path\FilePathFormatter; use Magento\FunctionalTestingFramework\Util\TestGenerator; -use Symfony\Component\Yaml\Yaml; +use Magento\FunctionalTestingFramework\Config\MftfApplicationConfig; +/** + * Class SuiteGenerator + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ class SuiteGenerator { const YAML_CODECEPTION_DIST_FILENAME = 'codeception.dist.yml'; @@ -82,23 +91,36 @@ public static function getInstance(): SuiteGenerator * * @param BaseTestManifest $testManifest * @return void - * @throws \Exception + * @throws FastFailException */ public function generateAllSuites($testManifest) { $suites = $testManifest->getSuiteConfig(); foreach ($suites as $suiteName => $suiteContent) { - $firstElement = array_values($suiteContent)[0]; + try { + if (empty($suiteContent)) { + LoggingUtil::getInstance()->getLogger(self::class)->notification( + "Suite '" . $suiteName . "' contains no tests and won't be generated.", + [], + true + ); + continue; + } + $firstElement = array_values($suiteContent)[0]; - // if the first element is a string we know that we simply have an array of tests - if (is_string($firstElement)) { - $this->generateSuiteFromTest($suiteName, $suiteContent); - } + // if the first element is a string we know that we simply have an array of tests + if (is_string($firstElement)) { + $this->generateSuiteFromTest($suiteName, $suiteContent); + } - // if our first element is an array we know that we have split the suites - if (is_array($firstElement)) { - $this->generateSplitSuiteFromTest($suiteName, $suiteContent); + // if our first element is an array we know that we have split the suites + if (is_array($firstElement)) { + $this->generateSplitSuiteFromTest($suiteName, $suiteContent); + } + } catch (FastFailException $e) { + throw $e; + } catch (\Exception $e) { } } } @@ -126,34 +148,82 @@ public function generateSuite($suiteName) * @param array $tests * @param string $originalSuiteName * @return void - * @throws TestReferenceException - * @throws XmlException + * @throws \Exception + * + * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ private function generateSuiteFromTest($suiteName, $tests = [], $originalSuiteName = null) { $relativePath = TestGenerator::GENERATED_DIR . DIRECTORY_SEPARATOR . $suiteName; - $fullPath = TESTS_MODULE_PATH . DIRECTORY_SEPARATOR . $relativePath . DIRECTORY_SEPARATOR; + $fullPath = FilePathFormatter::format(TESTS_MODULE_PATH) . $relativePath . DIRECTORY_SEPARATOR; DirSetupUtil::createGroupDir($fullPath); + $exceptionCollector = new ExceptionCollector(); + try { + $relevantTests = []; + if (!empty($tests)) { + $this->validateTestsReferencedInSuite($suiteName, $tests, $originalSuiteName); + foreach ($tests as $testName) { + try { + $relevantTests[$testName] = TestObjectHandler::getInstance()->getObject($testName); + } catch (FastFailException $e) { + throw $e; + } catch (\Exception $e) { + $exceptionCollector->addError( + self::class, + "Unable to find relevant test \"{$testName}\" for suite \"{$suiteName}\"" + ); + } + } + } else { + $relevantTests = SuiteObjectHandler::getInstance()->getObject($suiteName)->getTests(); + } - $relevantTests = []; - if (!empty($tests)) { - $this->validateTestsReferencedInSuite($suiteName, $tests, $originalSuiteName); - foreach ($tests as $testName) { - $relevantTests[$testName] = TestObjectHandler::getInstance()->getObject($testName); + if (empty($relevantTests)) { + $exceptionCollector->reset(); + // There are suites that include no test on purpose for certain Magento edition. + // To keep backward compatibility, we will return with no error. + // This might inevitably hide some suite errors that are resulted by real broken tests. + if (file_exists($fullPath)) { + DirSetupUtil::rmdirRecursive($fullPath); + } + return; } - } else { - $relevantTests = SuiteObjectHandler::getInstance()->getObject($suiteName)->getTests(); - } - $this->generateRelevantGroupTests($suiteName, $relevantTests); - $groupNamespace = $this->generateGroupFile($suiteName, $relevantTests, $originalSuiteName); + try { + $this->generateRelevantGroupTests($suiteName, $relevantTests); + } catch (FastFailException $e) { + throw $e; + } catch (\Exception $e) { + $exceptionCollector->addError( + self::class, + "Failed to generate tests for suite \"{$suiteName}\"" + ); + } - $this->appendEntriesToConfig($suiteName, $fullPath, $groupNamespace); - LoggingUtil::getInstance()->getLogger(SuiteGenerator::class)->info( - "suite generated", - ['suite' => $suiteName, 'relative_path' => $relativePath] - ); + $groupNamespace = $this->generateGroupFile($suiteName, $relevantTests, $originalSuiteName); + + $this->appendEntriesToConfig($suiteName, $fullPath, $groupNamespace); + + if (MftfApplicationConfig::getConfig()->verboseEnabled() + && MftfApplicationConfig::getConfig()->getPhase() === MftfApplicationConfig::GENERATION_PHASE) { + print("suite {$suiteName} generated\n"); + } + LoggingUtil::getInstance()->getLogger(self::class)->info( + "suite generated", + ['suite' => $suiteName, 'relative_path' => $relativePath] + ); + } catch (FastFailException $e) { + throw $e; + } catch (\Exception $e) { + if (file_exists($fullPath)) { + DirSetupUtil::rmdirRecursive($fullPath); + } + $exceptionCollector->addError(self::class, $e->getMessage()); + GenerationErrorHandler::getInstance()->addError('suite', $suiteName, self::class . ': ' . $e->getMessage()); + } + + $this->throwCollectedExceptions($exceptionCollector); } /** @@ -171,12 +241,14 @@ private function validateTestsReferencedInSuite($suiteName, $testsReferenced, $o { $suiteRef = $originalSuiteName ?? $suiteName; $possibleTestRef = SuiteObjectHandler::getInstance()->getObject($suiteRef)->getTests(); - $errorMsg = "Cannot reference tests whcih are not declared as part of suite."; + $errorMsg = "Cannot reference tests which are not declared as part of suite"; $invalidTestRef = array_diff($testsReferenced, array_keys($possibleTestRef)); if (!empty($invalidTestRef)) { - throw new TestReferenceException($errorMsg, ['suite' => $suiteRef, 'test' => $invalidTestRef]); + $testList = implode("\", \"", $invalidTestRef); + $fullError = $errorMsg . " (Suite: \"{$suiteRef}\" Tests: \"{$testList}\")"; + throw new TestReferenceException($fullError, ['suite' => $suiteRef, 'test' => $invalidTestRef]); } } @@ -192,7 +264,16 @@ private function validateTestsReferencedInSuite($suiteName, $testsReferenced, $o private function generateSplitSuiteFromTest($suiteName, $suiteContent) { foreach ($suiteContent as $suiteSplitName => $tests) { - $this->generateSuiteFromTest($suiteSplitName, $tests, $suiteName); + try { + $this->generateSuiteFromTest($suiteSplitName, $tests, $suiteName); + } catch (FastFailException $e) { + throw $e; + } catch (\Exception $e) { + // There are suites that include tests that reference tests from other Magento editions + // To keep backward compatibility, we will catch such exceptions with no error. + // This might inevitably hide some suite errors that are resulted by tests with broken references + //TODO MQE-2484 + } } } @@ -223,7 +304,7 @@ private function generateGroupFile($suiteName, $tests, $originalSuiteName) } else { $suiteObject = SuiteObjectHandler::getInstance()->getObject($suiteName); // we have to handle the case when there is a custom configuration for an existing suite. - if (count($suiteObject->getTests()) != count($tests)) { + if (count($suiteObject->getTests()) !== count($tests)) { return $this->generateGroupFile($suiteName, $tests, $suiteName); } } @@ -249,21 +330,7 @@ private function generateGroupFile($suiteName, $tests, $originalSuiteName) */ private function appendEntriesToConfig($suiteName, $suitePath, $groupNamespace) { - $relativeSuitePath = substr($suitePath, strlen(TESTS_BP)); - $relativeSuitePath = ltrim($relativeSuitePath, DIRECTORY_SEPARATOR); - - $ymlArray = self::getYamlFileContents(); - if (!array_key_exists(self::YAML_GROUPS_TAG, $ymlArray)) { - $ymlArray[self::YAML_GROUPS_TAG]= []; - } - - if ($groupNamespace) { - $ymlArray[self::YAML_EXTENSIONS_TAG][self::YAML_ENABLED_TAG][] = $groupNamespace; - } - $ymlArray[self::YAML_GROUPS_TAG][$suiteName] = [$relativeSuitePath]; - - $ymlText = self::YAML_COPYRIGHT_TEXT . Yaml::dump($ymlArray, 10); - file_put_contents(self::getYamlConfigFilePath() . self::YAML_CODECEPTION_CONFIG_FILENAME, $ymlText); + SuiteGeneratorService::getInstance()->appendEntriesToConfig($suiteName, $suitePath, $groupNamespace); } /** @@ -274,43 +341,23 @@ private function appendEntriesToConfig($suiteName, $suitePath, $groupNamespace) */ private static function clearPreviousSessionConfigEntries() { - $ymlArray = self::getYamlFileContents(); - $newYmlArray = $ymlArray; - // if the yaml entries haven't already been cleared - if (array_key_exists(self::YAML_EXTENSIONS_TAG, $ymlArray)) { - foreach ($ymlArray[self::YAML_EXTENSIONS_TAG][self::YAML_ENABLED_TAG] as $key => $entry) { - if (preg_match('/(Group\\\\.*)/', $entry)) { - unset($newYmlArray[self::YAML_EXTENSIONS_TAG][self::YAML_ENABLED_TAG][$key]); - } - } - - // needed for proper yml file generation based on indices - $newYmlArray[self::YAML_EXTENSIONS_TAG][self::YAML_ENABLED_TAG] = - array_values($newYmlArray[self::YAML_EXTENSIONS_TAG][self::YAML_ENABLED_TAG]); - } - - if (array_key_exists(self::YAML_GROUPS_TAG, $newYmlArray)) { - unset($newYmlArray[self::YAML_GROUPS_TAG]); - } - - $ymlText = self::YAML_COPYRIGHT_TEXT . Yaml::dump($newYmlArray, 10); - file_put_contents(self::getYamlConfigFilePath() . self::YAML_CODECEPTION_CONFIG_FILENAME, $ymlText); + SuiteGeneratorService::getInstance()->clearPreviousSessionConfigEntries(); } /** * Function which takes a string which is the desired output directory (under _generated) and an array of tests - * relevant to the suite to be generated. The function takes this information and creates a new instance of the test - * generator which is then called to create all the test files for the suite. + * relevant to the suite to be generated. The function takes this information and creates a new instance of the + * test generator which is then called to create all the test files for the suite. * * @param string $path * @param array $tests + * * @return void * @throws TestReferenceException */ private function generateRelevantGroupTests($path, $tests) { - $testGenerator = TestGenerator::getInstance($path, $tests); - $testGenerator->createAllTestFiles(null, []); + SuiteGeneratorService::getInstance()->generateRelevantGroupTests($path, $tests); } /** @@ -325,32 +372,25 @@ private static function clearPreviousGroupPreconditions() } /** - * Function to return contents of codeception.yml file for config changes. + * Log error and throw collected exceptions * - * @return array + * @param ExceptionCollector $exceptionCollector + * @return void + * @throws \Exception */ - private static function getYamlFileContents() + private function throwCollectedExceptions($exceptionCollector) { - $configYmlFile = self::getYamlConfigFilePath() . self::YAML_CODECEPTION_CONFIG_FILENAME; - $defaultConfigYmlFile = self::getYamlConfigFilePath() . self::YAML_CODECEPTION_DIST_FILENAME; - - $ymlContents = null; - if (file_exists($configYmlFile)) { - $ymlContents = file_get_contents($configYmlFile); - } else { - $ymlContents = file_get_contents($defaultConfigYmlFile); + if (!empty($exceptionCollector->getErrors())) { + foreach ($exceptionCollector->getErrors() as $file => $errorMessage) { + if (is_array($errorMessage)) { + foreach (array_unique($errorMessage) as $message) { + LoggingUtil::getInstance()->getLogger(self::class)->error(trim($message)); + } + } else { + LoggingUtil::getInstance()->getLogger(self::class)->error(trim($errorMessage)); + } + } + $exceptionCollector->throwException(); } - - return Yaml::parse($ymlContents) ?? []; - } - - /** - * Static getter for the Config yml filepath (as path cannot be stored in a const) - * - * @return string - */ - private static function getYamlConfigFilePath() - { - return TESTS_BP . DIRECTORY_SEPARATOR; } } diff --git a/src/Magento/FunctionalTestingFramework/Suite/Util/SuiteObjectExtractor.php b/src/Magento/FunctionalTestingFramework/Suite/Util/SuiteObjectExtractor.php index 7ef3d5e8c..6af9070ba 100644 --- a/src/Magento/FunctionalTestingFramework/Suite/Util/SuiteObjectExtractor.php +++ b/src/Magento/FunctionalTestingFramework/Suite/Util/SuiteObjectExtractor.php @@ -6,6 +6,8 @@ namespace Magento\FunctionalTestingFramework\Suite\Util; use Exception; +use Magento\FunctionalTestingFramework\Exceptions\FastFailException; +use Magento\FunctionalTestingFramework\Exceptions\GenerationErrorCollector; use Magento\FunctionalTestingFramework\Exceptions\XmlException; use Magento\FunctionalTestingFramework\Suite\Objects\SuiteObject; use Magento\FunctionalTestingFramework\Test\Handlers\TestObjectHandler; @@ -13,8 +15,19 @@ use Magento\FunctionalTestingFramework\Test\Util\BaseObjectExtractor; use Magento\FunctionalTestingFramework\Test\Util\TestHookObjectExtractor; use Magento\FunctionalTestingFramework\Test\Util\TestObjectExtractor; +use Magento\FunctionalTestingFramework\Util\GenerationErrorHandler; +use Magento\FunctionalTestingFramework\Util\Logger\LoggingUtil; use Magento\FunctionalTestingFramework\Util\Validation\NameValidationUtil; +use Magento\FunctionalTestingFramework\Util\ModulePathExtractor; +use Magento\FunctionalTestingFramework\Exceptions\TestReferenceException; +use Magento\FunctionalTestingFramework\Config\MftfApplicationConfig; +/** + * Class SuiteObjectExtractor + * @package Magento\FunctionalTestingFramework\Suite\Util + * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ class SuiteObjectExtractor extends BaseObjectExtractor { const SUITE_ROOT_TAG = 'suites'; @@ -22,7 +35,6 @@ class SuiteObjectExtractor extends BaseObjectExtractor const INCLUDE_TAG_NAME = 'include'; const EXCLUDE_TAG_NAME = 'exclude'; const MODULE_TAG_NAME = 'module'; - const MODULE_TAG_FILE_ATTRIBUTE = 'file'; const TEST_TAG_NAME = 'test'; const GROUP_TAG_NAME = 'group'; @@ -46,10 +58,11 @@ public function __construct() * * @param array $parsedSuiteData * @return array - * @throws XmlException - * @throws \Exception + * @throws FastFailException * * @SuppressWarnings(PHPMD.NPathComplexity) + * @SuppressWarnings(PHPMD.CyclomaticComplexity) + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ public function parseSuiteDataIntoObjects($parsedSuiteData) { @@ -68,28 +81,82 @@ public function parseSuiteDataIntoObjects($parsedSuiteData) $this->validateSuiteName($parsedSuite); - //extract include and exclude references - $groupTestsToInclude = $parsedSuite[self::INCLUDE_TAG_NAME] ?? []; - $groupTestsToExclude = $parsedSuite[self::EXCLUDE_TAG_NAME] ?? []; - - //resolve references as test objects - $includeTests = $this->extractTestObjectsFromSuiteRef($groupTestsToInclude); - $excludeTests = $this->extractTestObjectsFromSuiteRef($groupTestsToExclude); - - // parse any object hooks - $suiteHooks = $this->parseObjectHooks($parsedSuite); + try { + // extract include and exclude references + $groupTestsToInclude = $parsedSuite[self::INCLUDE_TAG_NAME] ?? []; + $groupTestsToExclude = $parsedSuite[self::EXCLUDE_TAG_NAME] ?? []; + + // resolve references as test objects + // continue if failed in include + $include = $this->extractTestObjectsFromSuiteRef($groupTestsToInclude); + $includeTests = $include['objects'] ?? []; + $stepError = $include['status'] ?? 0; + $includeMessage = ''; + if ($stepError !== 0) { + $includeMessage = "ERROR: " . strval($stepError) . " test(s) not included for suite " + . $parsedSuite[self::NAME]; + } + + // it's ok if failed in exclude + $exclude = $this->extractTestObjectsFromSuiteRef($groupTestsToExclude); + $excludeTests = $exclude['objects'] ?? []; + + // parse any object hooks + $suiteHooks = $this->parseObjectHooks($parsedSuite); + + // log error if suite is empty + if ($this->isSuiteEmpty($suiteHooks, $includeTests, $excludeTests)) { + LoggingUtil::getInstance()->getLogger(self::class)->error( + "Unable to parse suite " . $parsedSuite[self::NAME] . ". Suite must not be empty." + ); + + GenerationErrorHandler::getInstance()->addError( + 'suite', + $parsedSuite[self::NAME], + self::class . ': ' . 'Suite must not be empty.' + ); + + continue; + }; + + // add all test if include tests is completely empty + if (empty($includeTests)) { + $includeTests = TestObjectHandler::getInstance()->getAllObjects(); + } + + if (!empty($includeMessage)) { + LoggingUtil::getInstance()->getLogger(self::class)->error($includeMessage); + if (MftfApplicationConfig::getConfig()->verboseEnabled() + && MftfApplicationConfig::getConfig()->getPhase() === MftfApplicationConfig::GENERATION_PHASE + ) { + print($includeMessage); + } + + GenerationErrorHandler::getInstance()->addError( + 'suite', + $parsedSuite[self::NAME], + self::class . ': ' . $includeMessage, + true + ); + } + } catch (FastFailException $e) { + throw $e; + } catch (\Exception $e) { + LoggingUtil::getInstance()->getLogger(self::class)->error( + "Unable to parse suite " . $parsedSuite[self::NAME] . "\n" . $e->getMessage() + ); + if (MftfApplicationConfig::getConfig()->verboseEnabled() + && MftfApplicationConfig::getConfig()->getPhase() === MftfApplicationConfig::GENERATION_PHASE) { + print("ERROR: Unable to parse suite " . $parsedSuite[self::NAME] . "\n"); + } + + GenerationErrorHandler::getInstance()->addError( + 'suite', + $parsedSuite[self::NAME], + self::class . ': Unable to parse suite ' . $e->getMessage() + ); - //throw an exception if suite is empty - if ($this->isSuiteEmpty($suiteHooks, $includeTests, $excludeTests)) { - throw new XmlException(sprintf( - "Suites must not be empty. Suite: \"%s\"", - $parsedSuite[self::NAME] - )); - }; - - // add all test if include tests is completely empty - if (empty($includeTests)) { - $includeTests = TestObjectHandler::getInstance()->getAllObjects(); + continue; } // create the new suite object @@ -111,14 +178,14 @@ public function parseSuiteDataIntoObjects($parsedSuiteData) * * @param array $parsedSuite * @return void - * @throws XmlException + * @throws FastFailException */ private function validateSuiteName($parsedSuite) { //check if name used is using special char or the "default" reserved name NameValidationUtil::validateName($parsedSuite[self::NAME], 'Suite'); - if ($parsedSuite[self::NAME] == 'default') { - throw new XmlException("A Suite can not have the name \"default\""); + if ($parsedSuite[self::NAME] === 'default') { + throw new FastFailException("A Suite can not have the name \"default\""); } $suiteName = $parsedSuite[self::NAME]; @@ -131,7 +198,7 @@ private function validateSuiteName($parsedSuite) } $exceptionmessage = "\"Suite names and Group names can not have the same value. \t\n" . "Suite: \"{$suiteName}\" also exists as a group annotation in: \n{$testGroupConflictsFileNames}"; - throw new XmlException($exceptionmessage); + throw new FastFailException($exceptionmessage); } } @@ -141,27 +208,35 @@ private function validateSuiteName($parsedSuite) * @param array $parsedSuite * @return array * @throws XmlException + * @throws TestReferenceException */ private function parseObjectHooks($parsedSuite) { $suiteHooks = []; if (array_key_exists(TestObjectExtractor::TEST_BEFORE_HOOK, $parsedSuite)) { - $suiteHooks[TestObjectExtractor::TEST_BEFORE_HOOK] = $this->testHookObjectExtractor->extractHook( + $hookObject = $this->testHookObjectExtractor->extractHook( $parsedSuite[self::NAME], TestObjectExtractor::TEST_BEFORE_HOOK, $parsedSuite[TestObjectExtractor::TEST_BEFORE_HOOK] ); + // Validate hook actions + $hookObject->getActions(); + $suiteHooks[TestObjectExtractor::TEST_BEFORE_HOOK] = $hookObject; } + if (array_key_exists(TestObjectExtractor::TEST_AFTER_HOOK, $parsedSuite)) { - $suiteHooks[TestObjectExtractor::TEST_AFTER_HOOK] = $this->testHookObjectExtractor->extractHook( + $hookObject = $this->testHookObjectExtractor->extractHook( $parsedSuite[self::NAME], TestObjectExtractor::TEST_AFTER_HOOK, $parsedSuite[TestObjectExtractor::TEST_AFTER_HOOK] ); + // Validate hook actions + $hookObject->getActions(); + $suiteHooks[TestObjectExtractor::TEST_AFTER_HOOK] = $hookObject; } - if (count($suiteHooks) == 1) { + if (count($suiteHooks) === 1) { throw new XmlException(sprintf( "Suites that contain hooks must contain both a 'before' and an 'after' hook. Suite: \"%s\"", $parsedSuite[self::NAME] @@ -180,8 +255,7 @@ private function parseObjectHooks($parsedSuite) */ private function isSuiteEmpty($suiteHooks, $includeTests, $excludeTests) { - - $noHooks = count($suiteHooks) == 0 || + $noHooks = count($suiteHooks) === 0 || ( empty($suiteHooks['before']->getActions()) && empty($suiteHooks['after']->getActions()) @@ -199,117 +273,74 @@ private function isSuiteEmpty($suiteHooks, $includeTests, $excludeTests) * * @param array $suiteReferences * @return array - * @throws \Exception + * @throws FastFailException */ private function extractTestObjectsFromSuiteRef($suiteReferences) { $testObjectList = []; + $errCount = 0; foreach ($suiteReferences as $suiteRefName => $suiteRefData) { if (!is_array($suiteRefData)) { continue; } - switch ($suiteRefData[self::NODE_NAME]) { - case self::TEST_TAG_NAME: - $testObject = TestObjectHandler::getInstance()->getObject($suiteRefData[self::NAME]); - $testObjectList[$testObject->getName()] = $testObject; - break; - case self::GROUP_TAG_NAME: - $testObjectList = $testObjectList + - TestObjectHandler::getInstance()->getTestsByGroup($suiteRefData[self::NAME]); - break; - case self::MODULE_TAG_NAME: - $testObjectList = array_merge($testObjectList, $this->extractModuleAndFiles( - $suiteRefData[self::NAME], - $suiteRefData[self::MODULE_TAG_FILE_ATTRIBUTE] ?? null - )); - break; + try { + switch ($suiteRefData[self::NODE_NAME]) { + case self::TEST_TAG_NAME: + $testObject = TestObjectHandler::getInstance()->getObject($suiteRefData[self::NAME]); + $testObjectList[$testObject->getName()] = $testObject; + break; + case self::GROUP_TAG_NAME: + $testObjectList = $testObjectList + + TestObjectHandler::getInstance()->getTestsByGroup($suiteRefData[self::NAME]); + break; + case self::MODULE_TAG_NAME: + $testObjectList = array_merge( + $testObjectList, + $this->getTestsByModuleName($suiteRefData[self::NAME]) + ); + break; + } + } catch (FastFailException $e) { + throw $e; + } catch (\Exception $e) { + $errCount++; + LoggingUtil::getInstance()->getLogger(self::class)->error( + "Unable to find <" + . $suiteRefData[self::NODE_NAME] + . "> reference " + . $suiteRefData[self::NAME] + . " for suite " + . $suiteRefData[self::NAME] + ); } } - return $testObjectList; + return [ + 'status' => $errCount, + 'objects' => $testObjectList + ]; } /** - * Takes an array of modules/files and resolves to an array of test objects. + * Return all test objects for a module * * @param string $moduleName - * @param string $moduleFilePath - * @return array - * @throws \Exception - */ - private function extractModuleAndFiles($moduleName, $moduleFilePath) - { - if (empty($moduleFilePath)) { - return $this->resolveModulePathTestNames($moduleName); - } - - return $this->resolveFilePathTestNames($moduleFilePath, $moduleName); - } - - /** - * Takes a filepath (and optionally a module name) and resolves to a test object. - * - * @param string $filename - * @param null $moduleName * @return TestObject[] * @throws Exception */ - private function resolveFilePathTestNames($filename, $moduleName = null) - { - $filepath = $filename; - if (!strstr($filepath, DIRECTORY_SEPARATOR)) { - $filepath = TESTS_MODULE_PATH . - DIRECTORY_SEPARATOR . - $moduleName . - DIRECTORY_SEPARATOR . - 'Test' . - DIRECTORY_SEPARATOR . - $filename; - } - - if (!file_exists($filepath)) { - throw new Exception("Could not find file ${filename}"); - } - - $testObjects = []; - $xml = simplexml_load_file($filepath); - for ($i = 0; $i < $xml->count(); $i++) { - $testName = (string)$xml->test[$i]->attributes()->name; - $testObjects[$testName] = TestObjectHandler::getInstance()->getObject($testName); - } - - return $testObjects; - } - - /** - * Takes a single module name and resolves to an array of tests contained within specified module. - * - * @param string $moduleName - * @return array - * @throws \Exception - */ - private function resolveModulePathTestNames($moduleName) + private function getTestsByModuleName($moduleName) { $testObjects = []; - $xmlFiles = glob( - TESTS_MODULE_PATH . - DIRECTORY_SEPARATOR . - $moduleName . - DIRECTORY_SEPARATOR . - 'Test' . - DIRECTORY_SEPARATOR . - '*.xml' - ); - - foreach ($xmlFiles as $xmlFile) { - $testObjs = $this->resolveFilePathTestNames($xmlFile); - - foreach ($testObjs as $testObj) { - $testObjects[$testObj->getName()] = $testObj; + $pathExtractor = new ModulePathExtractor(); + $allTestObjects = TestObjectHandler::getInstance()->getAllObjects(); + foreach ($allTestObjects as $testName => $testObject) { + /** @var TestObject $testObject */ + $filename = $testObject->getFilename(); + if ($pathExtractor->extractModuleName($filename) === $moduleName) { + $testObjects[$testName] = $testObject; } } - return $testObjects; } } diff --git a/src/Magento/FunctionalTestingFramework/Suite/etc/mergedSuiteSchema.xsd b/src/Magento/FunctionalTestingFramework/Suite/etc/mergedSuiteSchema.xsd new file mode 100644 index 000000000..fee742df5 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Suite/etc/mergedSuiteSchema.xsd @@ -0,0 +1,72 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<xs:schema attributeFormDefault="unqualified" elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema"> + <xs:include schemaLocation="../../Test/etc/testSchema.xsd"/> + <xs:element name="suites" type="suiteConfigType"/> + <xs:complexType name="groupSuiteOptionType"> + <xs:simpleContent> + <xs:extension base="xs:string"> + <xs:attribute type="xs:string" name="name" use="required"/> + <xs:attribute type="xs:boolean" name="remove" use="optional"/> + </xs:extension> + </xs:simpleContent> + </xs:complexType> + <xs:complexType name="testSuiteOptionType"> + <xs:simpleContent> + <xs:extension base="xs:string"> + <xs:attribute type="xs:string" name="name" use="required"/> + <xs:attribute type="xs:boolean" name="remove" use="optional"/> + </xs:extension> + </xs:simpleContent> + </xs:complexType> + <xs:complexType name="moduleSuiteOptionType"> + <xs:simpleContent> + <xs:extension base="xs:string"> + <xs:attribute type="xs:string" name="name" use="optional"/> + <xs:attribute type="xs:boolean" name="remove" use="optional"/> + </xs:extension> + </xs:simpleContent> + </xs:complexType> + <xs:complexType name="includeType"> + <xs:choice minOccurs="0" maxOccurs="unbounded"> + <xs:element type="groupSuiteOptionType" name="group" minOccurs="0"/> + <xs:element type="testSuiteOptionType" name="test" minOccurs="0"/> + <xs:element type="moduleSuiteOptionType" name="module" minOccurs="0"/> + </xs:choice> + </xs:complexType> + <xs:complexType name="excludeType"> + <xs:choice minOccurs="0" maxOccurs="unbounded"> + <xs:element type="groupSuiteOptionType" name="group" minOccurs="0"/> + <xs:element type="testSuiteOptionType" name="test" minOccurs="0"/> + <xs:element type="moduleSuiteOptionType" name="module" minOccurs="0"/> + </xs:choice> + </xs:complexType> + <xs:complexType name="suiteType"> + <xs:choice minOccurs="0" maxOccurs="unbounded"> + <xs:element type="includeType" name="include" maxOccurs="1"/> + <xs:element type="excludeType" name="exclude" maxOccurs="1"/> + <xs:element type="hookType" name="before" maxOccurs="1"/> + <xs:element type="hookType" name="after" maxOccurs="1"/> + </xs:choice> + <xs:attribute type="xs:string" name="name"/> + <xs:attribute type="xs:string" name="deprecated"> + <xs:annotation> + <xs:documentation> + Message and flag which shows that entity is deprecated. + </xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attribute type="xs:string" name="filename"/> + </xs:complexType> + <xs:complexType name="suiteConfigType"> + <xs:choice minOccurs="0" maxOccurs="unbounded"> + <xs:element type="suiteType" name="suite"/> + </xs:choice> + </xs:complexType> +</xs:schema> diff --git a/src/Magento/FunctionalTestingFramework/Suite/etc/suiteSchema.xsd b/src/Magento/FunctionalTestingFramework/Suite/etc/suiteSchema.xsd index 34ef37594..8dc9d573d 100644 --- a/src/Magento/FunctionalTestingFramework/Suite/etc/suiteSchema.xsd +++ b/src/Magento/FunctionalTestingFramework/Suite/etc/suiteSchema.xsd @@ -7,64 +7,11 @@ --> <xs:schema attributeFormDefault="unqualified" elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema"> - <xs:include schemaLocation="../../Test/etc/testSchema.xsd"/> - <xs:element name="suites" type="suiteConfigType"/> - <xs:complexType name="groupSuiteOptionType"> - <xs:simpleContent> - <xs:extension base="xs:string"> - <xs:attribute type="xs:string" name="name" use="required"/> - <xs:attribute type="xs:boolean" name="remove" use="optional"/> - </xs:extension> - </xs:simpleContent> - </xs:complexType> - <xs:complexType name="testSuiteOptionType"> - <xs:simpleContent> - <xs:extension base="xs:string"> - <xs:attribute type="xs:string" name="name" use="required"/> - <xs:attribute type="xs:boolean" name="remove" use="optional"/> - </xs:extension> - </xs:simpleContent> - </xs:complexType> - <xs:complexType name="moduleSuiteOptionType"> - <xs:simpleContent> - <xs:extension base="xs:string"> - <xs:attribute type="xs:string" name="name" use="optional"/> - <xs:attribute type="xs:string" name="file" use="optional"/> - <xs:attribute type="xs:boolean" name="remove" use="optional"/> - </xs:extension> - </xs:simpleContent> - </xs:complexType> - <xs:complexType name="includeType"> - <xs:choice minOccurs="0" maxOccurs="unbounded"> - <xs:element type="groupSuiteOptionType" name="group" minOccurs="0"/> - <xs:element type="testSuiteOptionType" name="test" minOccurs="0"/> - <xs:element type="moduleSuiteOptionType" name="module" minOccurs="0"/> - </xs:choice> - </xs:complexType> - <xs:complexType name="excludeType"> - <xs:choice minOccurs="0" maxOccurs="unbounded"> - <xs:element type="groupSuiteOptionType" name="group" minOccurs="0"/> - <xs:element type="testSuiteOptionType" name="test" minOccurs="0"/> - <xs:element type="moduleSuiteOptionType" name="module" minOccurs="0"/> - </xs:choice> - </xs:complexType> - <!--<xs:complexType name="suiteHookType">--> - <!--<xs:choice minOccurs="0" maxOccurs="unbounded">--> - <!--<xs:group ref="dataOperationTags" maxOccurs="unbounded" minOccurs="0"/>--> - <!--</xs:choice>--> - <!--</xs:complexType>--> - <xs:complexType name="suiteType"> - <xs:choice minOccurs="0" maxOccurs="unbounded"> - <xs:element type="includeType" name="include" maxOccurs="1"/> - <xs:element type="excludeType" name="exclude" maxOccurs="1"/> - <xs:element type="hookType" name="before" maxOccurs="1"/> - <xs:element type="hookType" name="after" maxOccurs="1"/> - </xs:choice> - <xs:attribute type="xs:string" name="name"/> - </xs:complexType> - <xs:complexType name="suiteConfigType"> - <xs:choice minOccurs="0" maxOccurs="unbounded"> - <xs:element type="suiteType" name="suite"/> - </xs:choice> - </xs:complexType> + <xs:redefine schemaLocation="mergedSuiteSchema.xsd"> + <xs:complexType name="suiteConfigType"> + <xs:choice minOccurs="0" maxOccurs="1"> + <xs:element type="suiteType" name="suite"/> + </xs:choice> + </xs:complexType> + </xs:redefine> </xs:schema> diff --git a/src/Magento/FunctionalTestingFramework/Suite/views/SuiteClass.mustache b/src/Magento/FunctionalTestingFramework/Suite/views/SuiteClass.mustache index b98405c03..cf8fdd917 100644 --- a/src/Magento/FunctionalTestingFramework/Suite/views/SuiteClass.mustache +++ b/src/Magento/FunctionalTestingFramework/Suite/views/SuiteClass.mustache @@ -2,8 +2,15 @@ namespace Group; +use Facebook\WebDriver\Remote\RemoteWebDriver; use Magento\FunctionalTestingFramework\DataGenerator\Handlers\PersistedObjectHandler; use Magento\FunctionalTestingFramework\DataGenerator\Handlers\CredentialStore; +use Magento\FunctionalTestingFramework\Module\MagentoWebDriver; +use Magento\FunctionalTestingFramework\Module\MagentoAssert; +use Magento\FunctionalTestingFramework\Module\MagentoActionProxies; +use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; +use Codeception\Lib\ModuleContainer; +use Codeception\Module; /** * Group class is Codeception Extension which is allowed to handle to all internal events. @@ -20,12 +27,31 @@ class {{suiteName}} extends \Codeception\GroupObject private $testCount = {{testCount}}; private $preconditionFailure = null; private $currentTestRun = 0; + {{#helpers}} + /** + * @var \Magento\FunctionalTestingFramework\Helper\HelperContainer $helperContainer + */ + private $helperContainer; + {{/helpers}} private static $HOOK_EXECUTION_INIT = "\n/******** Beginning execution of {{suiteName}} suite %s block ********/\n"; private static $HOOK_EXECUTION_END = "\n/******** Execution of {{suiteName}} suite %s block complete ********/\n"; + /** @var MagentoWebDriver */ + private $webDriver; + /** @var ModuleContainer */ + private $moduleContainer; {{#before}} public function _before(\Codeception\Event\TestEvent $e) { + $this->webDriver = $this->getModule('\Magento\FunctionalTestingFramework\Module\MagentoWebDriver'); + $this->moduleContainer = $this->webDriver->getModuleContainer(); + {{#helpers}} + /** @var \Magento\FunctionalTestingFramework\Helper\HelperContainer $helperContainer */ + $this->helperContainer = $this->getModule('\Magento\FunctionalTestingFramework\Helper\HelperContainer'); + {{/helpers}} + {{#helpers}} + $this->helperContainer->create("{{ . }}"); + {{/helpers}} // increment test count per execution $this->currentTestRun++; $this->executePreConditions(); @@ -36,24 +62,24 @@ class {{suiteName}} extends \Codeception\GroupObject } } - private function executePreConditions() { if ($this->currentTestRun == 1) { + $this->testCount = $this->getTestCount(); + print sprintf(self::$HOOK_EXECUTION_INIT, "before"); try { {{> testActions}} - - // reset configuration and close session - $this->getModule('\Magento\FunctionalTestingFramework\Module\MagentoWebDriver')->_resetConfig(); - $webDriver->webDriver->close(); - $webDriver->webDriver = null; - } catch (\Exception $exception) { $this->preconditionFailure = $exception->getMessage(); } + // reset configuration and close session + $this->webDriver->_resetConfig(); + $this->webDriver->webDriver->close(); + $this->webDriver->webDriver = null; + print sprintf(self::$HOOK_EXECUTION_END, "before"); } } @@ -65,7 +91,6 @@ class {{suiteName}} extends \Codeception\GroupObject $this->executePostConditions($e); } - private function executePostConditions(\Codeception\Event\TestEvent $e) { if ($this->currentTestRun == $this->testCount) { @@ -99,8 +124,77 @@ class {{suiteName}} extends \Codeception\GroupObject } PersistedObjectHandler::getInstance()->clearSuiteObjects(); + + $this->closeSession($this->webDriver); + print sprintf(self::$HOOK_EXECUTION_END, "after"); } } {{/after}} + + /** + * Close session method closes current session. + * If config 'close_all_sessions' is set to 'true' all sessions will be closed. + * + * return void + */ + private function closeSession(): void + { + $webDriverConfig = $this->webDriver->_getConfig(); + $this->webDriver->_closeSession(); + if (isset($webDriverConfig['close_all_sessions']) && $webDriverConfig['close_all_sessions'] === "true") { + $wdHost = sprintf( + '%s://%s:%s%s', + $webDriverConfig['protocol'], + $webDriverConfig['host'], + $webDriverConfig['port'], + $webDriverConfig['path'] + ); + $availableSessions = RemoteWebDriver::getAllSessions($wdHost); + foreach ($availableSessions as $session) { + $remoteWebDriver = RemoteWebDriver::createBySessionID($session['id'], $wdHost); + $remoteWebDriver->quit(); + } + } + } + + /** + * Return the module for an action. + * + * @param string $action + * @return Module + * @throws \Exception + */ + private function getModuleForAction($action) + { + $module = $this->moduleContainer->moduleForAction($action); + if ($module === null) { + throw new TestFrameworkException('Invalid action "' . $action . '"' . PHP_EOL); + } + return $module; + } + + /** + * Counts how many tests in group. + * + * @return integer + */ + private function getTestCount() + { + $config = $this->getGlobalConfig(); + if (empty($config['groups']) || empty($config['groups'][self::$group])) { + return $this->testCount; + } + $pathToGroupDir = TESTS_BP . DIRECTORY_SEPARATOR . array_first($config['groups'][self::$group]); + $pathToGroupCests = $pathToGroupDir . "*Cest.php"; + + $files = glob($pathToGroupCests); + if (is_array($files)) { + $qty = count($files); + print('In a group "' . self::$group . '" suite executor found ' . $qty . ' tests.' . PHP_EOL); + return $qty; + } + + return $this->testCount; + } } diff --git a/src/Magento/FunctionalTestingFramework/Suite/views/partials/testActions.mustache b/src/Magento/FunctionalTestingFramework/Suite/views/partials/testActions.mustache index 2ed5e8d5e..c07958778 100644 --- a/src/Magento/FunctionalTestingFramework/Suite/views/partials/testActions.mustache +++ b/src/Magento/FunctionalTestingFramework/Suite/views/partials/testActions.mustache @@ -1,15 +1,10 @@ {{#actions}} {{#webDriverInit}} -$webDriver = $this->getModule('\Magento\FunctionalTestingFramework\Module\MagentoWebDriver'); - -// close any open sessions -if ($webDriver->webDriver != null) { - $webDriver->webDriver->close(); - $webDriver->webDriver = null; +if ($this->webDriver->webDriver != null) { + $this->webDriver->_restart(); +} else { + $this->webDriver->_initializeSession(); } - -// initialize the webdriver session -$webDriver->_initializeSession(); {{/webDriverInit}} {{#action}} {{{action}}} @@ -18,9 +13,9 @@ $webDriver->_initializeSession(); PersistedObjectHandler::getInstance()->createEntity( "{{stepKey}}", "suite", - "{{entityName}}"{{#requiredEntities}}, - [$this->{{entityName}}{{^last}}, {{/last}}]{{/requiredEntities}}{{#customFields}}, - ${{customFields}}{{/customFields}} + "{{entityName}}", + [{{#requiredEntities}}"{{entityName}}"{{^last}}, {{/last}}{{/requiredEntities}}]{{#customFields}}, + ${{customFields}}{{/customFields}} ); {{/createData}} {{#deleteData}} diff --git a/src/Magento/FunctionalTestingFramework/System/Code/ClassReader.php b/src/Magento/FunctionalTestingFramework/System/Code/ClassReader.php index 5f44ea104..8408f361b 100644 --- a/src/Magento/FunctionalTestingFramework/System/Code/ClassReader.php +++ b/src/Magento/FunctionalTestingFramework/System/Code/ClassReader.php @@ -20,6 +20,7 @@ class ClassReader * @param string $method * @return array|null * @throws \ReflectionException + * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ public function getParameters($className, $method) { @@ -31,9 +32,13 @@ public function getParameters($className, $method) /** @var $parameter \ReflectionParameter */ foreach ($method->getParameters() as $parameter) { try { + $paramType = $parameter->getType(); + $name = ($paramType && method_exists($paramType, 'isBuiltin') && !$paramType->isBuiltin()) + ? new \ReflectionClass($paramType->getName()) + : null; $result[$parameter->getName()] = [ $parameter->getName(), - ($parameter->getClass() !== null) ? $parameter->getClass()->getName() : null, + $name !== null ? $name->getName() : null, !$parameter->isOptional(), $parameter->isOptional() ? $parameter->isDefaultValueAvailable() ? $parameter->getDefaultValue() : null : diff --git a/src/Magento/FunctionalTestingFramework/Test/Config/ActionGroupDom.php b/src/Magento/FunctionalTestingFramework/Test/Config/ActionGroupDom.php index 7eec3286d..a99432e40 100644 --- a/src/Magento/FunctionalTestingFramework/Test/Config/ActionGroupDom.php +++ b/src/Magento/FunctionalTestingFramework/Test/Config/ActionGroupDom.php @@ -31,15 +31,23 @@ public function initDom($xml, $filename = null) if ($this->checkFilenameSuffix($filename, self::ACTION_GROUP_FILE_NAME_ENDING)) { $actionGroupsNode = $dom->getElementsByTagName('actionGroups')[0]; - $actionGroupNodes = $dom->getElementsByTagName('actionGroup'); $this->testsValidationUtil->validateChildUniqueness( $actionGroupsNode, $filename, null ); - foreach ($actionGroupNodes as $actionGroupNode) { + + // Validate single action group node per file + $this->singleNodePerFileValidationUtil->validateSingleNodeForTag( + $dom, + 'actionGroup', + $filename + ); + + if ($dom->getElementsByTagName('actionGroup')->length > 0) { /** @var \DOMElement $actionGroupNode */ + $actionGroupNode = $dom->getElementsByTagName('actionGroup')[0]; $actionGroupNode->setAttribute(self::TEST_META_FILENAME_ATTRIBUTE, $filename); $this->actionsValidationUtil->validateChildUniqueness( $actionGroupNode, diff --git a/src/Magento/FunctionalTestingFramework/Test/Config/Converter/Dom/Flat.php b/src/Magento/FunctionalTestingFramework/Test/Config/Converter/Dom/Flat.php index 0769f5ebf..23d8d5331 100644 --- a/src/Magento/FunctionalTestingFramework/Test/Config/Converter/Dom/Flat.php +++ b/src/Magento/FunctionalTestingFramework/Test/Config/Converter/Dom/Flat.php @@ -77,7 +77,7 @@ public function convertXml(\DOMNode $source, $basePath = '') $value = []; /** @var \DOMNode $node */ foreach ($source->childNodes as $node) { - if ($node->nodeType == XML_ELEMENT_NODE) { + if ($node->nodeType === XML_ELEMENT_NODE) { $nodeName = $node->nodeName; $nodePath = $basePath . '/' . $nodeName; $arrayKeyAttribute = $this->arrayNodeConfig->getAssocArrayKeyAttribute($nodePath); @@ -90,7 +90,7 @@ public function convertXml(\DOMNode $source, $basePath = '') ); } - if ($nodeName == self::REMOVE_ACTION) { + if ($nodeName === self::REMOVE_ACTION) { // Check to see if the test extends for this remove action $parentHookExtends = in_array($node->parentNode->nodeName, self::TEST_HOOKS) && !empty($node->parentNode->parentNode->getAttribute('extends')); @@ -121,12 +121,12 @@ public function convertXml(\DOMNode $source, $basePath = '') } else { $value[$nodeName] = $nodeData; } - } elseif ($node->nodeType == XML_CDATA_SECTION_NODE - || ($node->nodeType == XML_TEXT_NODE && trim($node->nodeValue) != '') + } elseif ($node->nodeType === XML_CDATA_SECTION_NODE + || ($node->nodeType === XML_TEXT_NODE && trim($node->nodeValue) !== '') ) { $value = $node->nodeValue; break; - } elseif ($node->nodeType == XML_COMMENT_NODE && + } elseif ($node->nodeType === XML_COMMENT_NODE && in_array($node->parentNode->nodeName, self::VALID_COMMENT_PARENT)) { $uniqid = uniqid($node->nodeName); $value[$uniqid] = [ @@ -163,7 +163,7 @@ protected function getNodeAttributes(\DOMNode $node) $attributes = $node->attributes ?: []; /** @var \DOMNode $attribute */ foreach ($attributes as $attribute) { - if ($attribute->nodeType == XML_ATTRIBUTE_NODE) { + if ($attribute->nodeType === XML_ATTRIBUTE_NODE) { $result[$attribute->nodeName] = $attribute->nodeValue; } } diff --git a/src/Magento/FunctionalTestingFramework/Test/Config/Dom.php b/src/Magento/FunctionalTestingFramework/Test/Config/Dom.php index 12127ef32..89f779968 100644 --- a/src/Magento/FunctionalTestingFramework/Test/Config/Dom.php +++ b/src/Magento/FunctionalTestingFramework/Test/Config/Dom.php @@ -12,6 +12,7 @@ use Magento\FunctionalTestingFramework\Config\Dom\NodePathMatcher; use Magento\FunctionalTestingFramework\Test\Objects\ActionObject; use Magento\FunctionalTestingFramework\Util\Validation\DuplicateNodeValidationUtil; +use Magento\FunctionalTestingFramework\Util\Validation\SingleNodePerFileValidationUtil; /** * MFTF test.xml configuration XML DOM utility @@ -38,6 +39,12 @@ class Dom extends \Magento\FunctionalTestingFramework\Config\MftfDom */ protected $testsValidationUtil; + /** + * SingleNodePerFileValidationUtil + * @var SingleNodePerFileValidationUtil + */ + protected $singleNodePerFileValidationUtil; + /** * ExceptionCollector * @var ExceptionCollector @@ -65,6 +72,7 @@ public function __construct( ) { $this->actionsValidationUtil = new DuplicateNodeValidationUtil('stepKey', $exceptionCollector); $this->testsValidationUtil = new DuplicateNodeValidationUtil('name', $exceptionCollector); + $this->singleNodePerFileValidationUtil = new SingleNodePerFileValidationUtil($exceptionCollector); $this->exceptionCollector = $exceptionCollector; parent::__construct( $xml, @@ -91,14 +99,21 @@ public function initDom($xml, $filename = null) // Cannot rely on filename to ensure this file is a Test.xml if ($dom->getElementsByTagName('tests')->length > 0) { $testsNode = $dom->getElementsByTagName('tests')[0]; - $testNodes = $dom->getElementsByTagName('test'); $this->testsValidationUtil->validateChildUniqueness( $testsNode, $filename, null ); - foreach ($testNodes as $testNode) { + // Validate single test node per file + $this->singleNodePerFileValidationUtil->validateSingleNodeForTag( + $dom, + 'test', + $filename + ); + + if ($dom->getElementsByTagName('test')->length > 0) { /** @var \DOMElement $testNode */ + $testNode = $dom->getElementsByTagName('test')[0]; $testNode->setAttribute(self::TEST_META_FILENAME_ATTRIBUTE, $filename); if ($testNode->getAttribute(self::TEST_MERGE_POINTER_AFTER) !== "") { $this->appendMergePointerToActions( @@ -158,7 +173,7 @@ protected function appendMergePointerToActions($testNode, $insertType, $insertKe $childNodes = $testNode->childNodes; $previousStepKey = $insertKey; $actionInsertType = ActionObject::MERGE_ACTION_ORDER_AFTER; - if ($insertType == self::TEST_MERGE_POINTER_BEFORE) { + if ($insertType === self::TEST_MERGE_POINTER_BEFORE) { $actionInsertType = ActionObject::MERGE_ACTION_ORDER_BEFORE; } for ($i = 0; $i < $childNodes->length; $i++) { diff --git a/src/Magento/FunctionalTestingFramework/Test/Handlers/ActionGroupObjectHandler.php b/src/Magento/FunctionalTestingFramework/Test/Handlers/ActionGroupObjectHandler.php index 131ae0a26..fb4dac688 100644 --- a/src/Magento/FunctionalTestingFramework/Test/Handlers/ActionGroupObjectHandler.php +++ b/src/Magento/FunctionalTestingFramework/Test/Handlers/ActionGroupObjectHandler.php @@ -6,13 +6,14 @@ namespace Magento\FunctionalTestingFramework\Test\Handlers; use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; +use Magento\FunctionalTestingFramework\Exceptions\XmlException; use Magento\FunctionalTestingFramework\ObjectManager\ObjectHandlerInterface; use Magento\FunctionalTestingFramework\ObjectManagerFactory; use Magento\FunctionalTestingFramework\Test\Objects\ActionGroupObject; -use Magento\FunctionalTestingFramework\Test\Objects\TestObject; use Magento\FunctionalTestingFramework\Test\Parsers\ActionGroupDataParser; use Magento\FunctionalTestingFramework\Test\Util\ActionGroupObjectExtractor; use Magento\FunctionalTestingFramework\Test\Util\ObjectExtensionUtil; +use Magento\FunctionalTestingFramework\Util\Validation\NameValidationUtil; /** * Class ActionGroupObjectHandler @@ -22,6 +23,7 @@ class ActionGroupObjectHandler implements ObjectHandlerInterface const BEFORE_AFTER_ERROR_MSG = "Merge Error - Steps cannot have both before and after attributes.\tTestStep='%s'"; const ACTION_GROUP_ROOT = 'actionGroups'; const ACTION_GROUP = 'actionGroup'; + const ACTION_GROUP_FILENAME_ATTRIBUTE = 'filename'; /** * Single instance of class var @@ -48,6 +50,7 @@ class ActionGroupObjectHandler implements ObjectHandlerInterface * Singleton getter for instance of ActionGroupObjectHandler * * @return ActionGroupObjectHandler + * @throws XmlException */ public static function getInstance(): ActionGroupObjectHandler { @@ -60,6 +63,7 @@ public static function getInstance(): ActionGroupObjectHandler /** * ActionGroupObjectHandler constructor. + * @throws XmlException */ private function __construct() { @@ -72,6 +76,8 @@ private function __construct() * * @param string $actionGroupName * @return ActionGroupObject + * @throws TestFrameworkException + * @throws XmlException */ public function getObject($actionGroupName) { @@ -87,6 +93,8 @@ public function getObject($actionGroupName) * Function to return all objects for which the handler is responsible * * @return array + * @throws TestFrameworkException + * @throws XmlException */ public function getAllObjects(): array { @@ -100,6 +108,7 @@ public function getAllObjects(): array * Method which populates field array with objects from parsed action_group.xml * * @return void + * @throws XmlException * @SuppressWarnings(PHPMD.UnusedPrivateMethod) */ private function initActionGroups() @@ -110,7 +119,17 @@ private function initActionGroups() $actionGroupObjectExtractor = new ActionGroupObjectExtractor(); $neededActionGroup = $parsedActionGroups[ActionGroupObjectHandler::ACTION_GROUP_ROOT]; + $actionGroupNameValidator = new NameValidationUtil(); foreach ($neededActionGroup as $actionGroupName => $actionGroupData) { + if (!in_array($actionGroupName, ["nodeName", "xsi:noNamespaceSchemaLocation"])) { + $filename = $actionGroupData[ActionGroupObjectHandler::ACTION_GROUP_FILENAME_ATTRIBUTE]; + $actionGroupNameValidator->validatePascalCase( + $actionGroupName, + NameValidationUtil::ACTION_GROUP_NAME, + $filename + ); + } + if (!is_array($actionGroupData)) { continue; } @@ -118,6 +137,7 @@ private function initActionGroups() $this->actionGroups[$actionGroupName] = $actionGroupObjectExtractor->extractActionGroup($actionGroupData); } + $actionGroupNameValidator->summarize(NameValidationUtil::ACTION_GROUP_NAME); } /** @@ -125,14 +145,15 @@ private function initActionGroups() * * @param ActionGroupObject $actionGroupObject * @return ActionGroupObject + * @throws XmlException * @throws TestFrameworkException */ private function extendActionGroup($actionGroupObject): ActionGroupObject { if ($actionGroupObject->getParentName() !== null) { - if ($actionGroupObject->getParentName() == $actionGroupObject->getName()) { + if ($actionGroupObject->getParentName() === $actionGroupObject->getName()) { throw new TestFrameworkException( - "Mftf Action Group can not extend from itself: " . $actionGroupObject->getName() + 'Mftf Action Group can not extend from itself: ' . $actionGroupObject->getName() ); } return $this->extendUtil->extendActionGroup($actionGroupObject); diff --git a/src/Magento/FunctionalTestingFramework/Test/Handlers/TestObjectHandler.php b/src/Magento/FunctionalTestingFramework/Test/Handlers/TestObjectHandler.php index a0d490df5..287e5306e 100644 --- a/src/Magento/FunctionalTestingFramework/Test/Handlers/TestObjectHandler.php +++ b/src/Magento/FunctionalTestingFramework/Test/Handlers/TestObjectHandler.php @@ -5,7 +5,8 @@ */ namespace Magento\FunctionalTestingFramework\Test\Handlers; -use Magento\FunctionalTestingFramework\Exceptions\Collector\ExceptionCollector; +use Magento\FunctionalTestingFramework\Config\MftfApplicationConfig; +use Magento\FunctionalTestingFramework\Exceptions\FastFailException; use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; use Magento\FunctionalTestingFramework\Exceptions\TestReferenceException; use Magento\FunctionalTestingFramework\Exceptions\XmlException; @@ -15,14 +16,19 @@ use Magento\FunctionalTestingFramework\Test\Parsers\TestDataParser; use Magento\FunctionalTestingFramework\Test\Util\ObjectExtensionUtil; use Magento\FunctionalTestingFramework\Test\Util\TestObjectExtractor; -use Magento\FunctionalTestingFramework\Test\Util\AnnotationExtractor; +use Magento\FunctionalTestingFramework\Util\GenerationErrorHandler; +use Magento\FunctionalTestingFramework\Util\Logger\LoggingUtil; +use Magento\FunctionalTestingFramework\Util\Validation\NameValidationUtil; /** * Class TestObjectHandler + * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class TestObjectHandler implements ObjectHandlerInterface { const XML_ROOT = 'tests'; + const TEST_FILENAME_ATTRIBUTE = 'filename'; /** * Test Object Handler @@ -49,13 +55,14 @@ class TestObjectHandler implements ObjectHandlerInterface * Singleton method to return TestObjectHandler. * * @return TestObjectHandler - * @throws XmlException + * @throws FastFailException + * @throws TestFrameworkException */ - public static function getInstance() + public static function getInstance($validateAnnotations = true) { if (!self::$testObjectHandler) { self::$testObjectHandler = new TestObjectHandler(); - self::$testObjectHandler->initTestData(); + self::$testObjectHandler->initTestData($validateAnnotations); } return self::$testObjectHandler; @@ -90,13 +97,41 @@ public function getObject($testName) * Returns all tests parsed from xml indexed by testName. * * @return array + * @throws FastFailException + * @throws TestFrameworkException */ public function getAllObjects() { + $errCount = 0; $testObjects = []; foreach ($this->tests as $testName => $test) { - $testObjects[$testName] = $this->extendTest($test); + try { + $testObjects[$testName] = $this->extendTest($test); + } catch (FastFailException $exception) { + throw $exception; + } catch (\Exception $exception) { + $errCount++; + LoggingUtil::getInstance()->getLogger(self::class)->error( + "Unable to extend test " . $testName . "\n" . $exception->getMessage() + ); + GenerationErrorHandler::getInstance()->addError( + 'test', + $testName, + self::class . ': Unable to extend test ' . $exception->getMessage() + ); + } + } + + if ($errCount > 0 + && MftfApplicationConfig::getConfig()->verboseEnabled() + && MftfApplicationConfig::getConfig()->getPhase() === MftfApplicationConfig::GENERATION_PHASE) { + print( + "ERROR: " + . strval($errCount) + . " Test(s) cannot be extended in TestObjectHandler::getAllObjects(). See mftf.log for details." + ); } + return $testObjects; } @@ -105,55 +140,129 @@ public function getAllObjects() * * @param string $groupName * @return TestObject[] + * @throws FastFailException + * @throws TestFrameworkException */ public function getTestsByGroup($groupName) { + $errCount = 0; $relevantTests = []; foreach ($this->tests as $test) { - /** @var TestObject $test */ - if (in_array($groupName, $test->getAnnotationByName('group'))) { - $relevantTests[$test->getName()] = $this->extendTest($test); - continue; + try { + /** @var TestObject $test */ + if (in_array($groupName, $test->getAnnotationByName('group'))) { + $relevantTests[$test->getName()] = $this->extendTest($test); + } + } catch (FastFailException $exception) { + throw $exception; + } catch (\Exception $exception) { + $errCount++; + $message = "Unable to reference test " + . $test->getName() + . " for group {$groupName}\n" + . $exception->getMessage(); + LoggingUtil::getInstance()->getLogger(self::class)->error($message); + + GenerationErrorHandler::getInstance()->addError( + 'test', + $test->getName(), + self::class . ': ' . $message + ); } } + if ($errCount > 0 + && MftfApplicationConfig::getConfig()->verboseEnabled() + && MftfApplicationConfig::getConfig()->getPhase() === MftfApplicationConfig::GENERATION_PHASE) { + print( + "ERROR: " + . strval($errCount) + . " Test(s) cannot be referenced for group {$groupName} in TestObjectHandler::getTestsByGroup()." + . " See mftf.log for details." + ); + } + return $relevantTests; } + /** + * Sanitize test objects + * + * @param array $testsToRemove + * @return void + */ + public function sanitizeTests($testsToRemove) + { + foreach ($testsToRemove as $name) { + unset($this->tests[$name]); + LoggingUtil::getInstance()->getLogger(self::class)->error( + "Removed invalid test object {$name}" + ); + } + } + /** * This method reads all Test.xml files into objects and stores them in an array for future access. * + * @param boolean $validateAnnotations * @return void + * @throws FastFailException + * @throws TestFrameworkException + * * @SuppressWarnings(PHPMD.UnusedPrivateMethod) - * @throws XmlException + * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ - private function initTestData() + private function initTestData($validateAnnotations = true) { - $testDataParser = ObjectManagerFactory::getObjectManager()->create(TestDataParser::class); - $parsedTestArray = $testDataParser->readTestData(); + $parserErrorMessage = null; + try { + $testDataParser = ObjectManagerFactory::getObjectManager()->create(TestDataParser::class); + $parsedTestArray = $testDataParser->readTestData(); - $testObjectExtractor = new TestObjectExtractor(); + if (!$parsedTestArray) { + $parserErrorMessage = "Could not parse any test in xml."; + } + } catch (\Exception $e) { + $parserErrorMessage = $e->getMessage(); + } - if (!$parsedTestArray) { - trigger_error("Could not parse any test in xml.", E_USER_NOTICE); - return; + if ($parserErrorMessage) { + throw new FastFailException("Test Data Parser Error: " . $parserErrorMessage); } - $exceptionCollector = new ExceptionCollector(); - foreach ($parsedTestArray[TestObjectHandler::XML_ROOT] as $testName => $testData) { - if (!is_array($testData)) { - continue; - } + $testObjectExtractor = new TestObjectExtractor(); + + $testNameValidator = new NameValidationUtil(); + foreach ($parsedTestArray as $testName => $testData) { try { - $this->tests[$testName] = $testObjectExtractor->extractTestData($testData); - } catch (XmlException $exception) { - $exceptionCollector->addError(self::class, $exception->getMessage()); + $filename = $testData[TestObjectHandler::TEST_FILENAME_ATTRIBUTE]; + $testNameValidator->validatePascalCase($testName, NameValidationUtil::TEST_NAME, $filename); + if (!is_array($testData)) { + continue; + } + $this->tests[$testName] = $testObjectExtractor->extractTestData($testData, $validateAnnotations); + } catch (FastFailException $exception) { + throw $exception; + } catch (\Exception $exception) { + LoggingUtil::getInstance()->getLogger(self::class)->error( + "Unable to parse test " . $testName . "\n" . $exception->getMessage() + ); + if (MftfApplicationConfig::getConfig()->verboseEnabled() + && MftfApplicationConfig::getConfig()->getPhase() === MftfApplicationConfig::GENERATION_PHASE) { + print("ERROR: Unable to parse test " . $testName . "\n"); + } + GenerationErrorHandler::getInstance()->addError( + 'test', + $testName, + self::class . ': Unable to parse test ' . $exception->getMessage() + ); } } - $exceptionCollector->throwException(); - - $testObjectExtractor->getAnnotationExtractor()->validateStoryTitleUniqueness(); - $testObjectExtractor->getAnnotationExtractor()->validateTestCaseIdTitleUniqueness(); + $testNameValidator->summarize(NameValidationUtil::TEST_NAME); + if ($validateAnnotations) { + $testObjectExtractor->getAnnotationExtractor()->validateStoryTitleUniqueness(); + $testObjectExtractor->getAnnotationExtractor()->validateTestCaseIdTitleUniqueness(); + } } /** @@ -162,12 +271,15 @@ private function initTestData() * @param TestObject $testObject * @return TestObject * @throws TestFrameworkException + * @throws XmlException */ private function extendTest($testObject) { if ($testObject->getParentName() !== null) { - if ($testObject->getParentName() == $testObject->getName()) { - throw new TestFrameworkException("Mftf Test can not extend from itself: " . $testObject->getName()); + if ($testObject->getParentName() === $testObject->getName()) { + throw new TestFrameworkException( + "Mftf Test can not extend from itself: " . $testObject->getName() + ); } return $this->extendUtil->extendTest($testObject); } diff --git a/src/Magento/FunctionalTestingFramework/Test/Objects/ActionGroupObject.php b/src/Magento/FunctionalTestingFramework/Test/Objects/ActionGroupObject.php index fc38a7fce..593320d44 100644 --- a/src/Magento/FunctionalTestingFramework/Test/Objects/ActionGroupObject.php +++ b/src/Magento/FunctionalTestingFramework/Test/Objects/ActionGroupObject.php @@ -8,7 +8,6 @@ use Magento\FunctionalTestingFramework\Exceptions\TestReferenceException; use Magento\FunctionalTestingFramework\Test\Util\ActionMergeUtil; -use Magento\FunctionalTestingFramework\Test\Util\ObjectExtension; /** * Class ActionGroupObject @@ -25,18 +24,20 @@ class ActionGroupObject "executeJS", "magentoCLI", "generateDate", - "formatMoney", + "formatCurrency", "deleteData", "getData", "updateData", "createData", "grabAttributeFrom", "grabCookie", + "grabCookieAttributes", "grabFromCurrentUrl", "grabMultiple", "grabPageSource", "grabTextFrom", - "grabValueFrom" + "grabValueFrom", + "getOTP" ]; /** @@ -94,6 +95,13 @@ class ActionGroupObject */ private $cachedStepKeys = null; + /** + * Deprecation message. + * + * @var string|null + */ + private $deprecated; + /** * ActionGroupObject constructor. * @@ -103,9 +111,17 @@ class ActionGroupObject * @param array $actions * @param string $parentActionGroup * @param string $filename + * @param string|null $deprecated */ - public function __construct($name, $annotations, $arguments, $actions, $parentActionGroup, $filename = null) - { + public function __construct( + $name, + $annotations, + $arguments, + $actions, + $parentActionGroup, + $filename = null, + $deprecated = null + ) { $this->varAttributes = array_merge( ActionObject::SELECTOR_ENABLED_ATTRIBUTES, ActionObject::DATA_ENABLED_ATTRIBUTES @@ -117,6 +133,17 @@ public function __construct($name, $annotations, $arguments, $actions, $parentAc $this->parsedActions = $actions; $this->parentActionGroup = $parentActionGroup; $this->filename = $filename; + $this->deprecated = $deprecated; + } + + /** + * Returns deprecated messages. + * + * @return string|null + */ + public function getDeprecated() + { + return $this->deprecated; } /** @@ -185,6 +212,9 @@ private function getResolvedActionsWithArgs($arguments, $actionReferenceKey) foreach ($this->parsedActions as $action) { $replacementStepKeys[$action->getStepKey()] = $action->getStepKey() . ucfirst($actionReferenceKey); $varAttributes = array_intersect($this->varAttributes, array_keys($action->getCustomActionAttributes())); + if ($action->getType() === ActionObject::ACTION_TYPE_HELPER) { + $varAttributes = array_keys($action->getCustomActionAttributes()); + } // replace createDataKey attributes inside the action group $resolvedActionAttributes = $this->replaceCreateDataKeys($action, $replacementStepKeys); @@ -210,10 +240,11 @@ private function getResolvedActionsWithArgs($arguments, $actionReferenceKey) $action->getStepKey() . ucfirst($actionReferenceKey), $action->getType(), array_replace_recursive($resolvedActionAttributes, $newActionAttributes), - $action->getLinkedAction() == null ? null : $action->getLinkedAction() . ucfirst($actionReferenceKey), + $action->getLinkedAction() === null ? null : $action->getLinkedAction() . ucfirst($actionReferenceKey), $orderOffset, [self::ACTION_GROUP_ORIGIN_NAME => $this->name, - self::ACTION_GROUP_ORIGIN_TEST_REF => $actionReferenceKey] + self::ACTION_GROUP_ORIGIN_TEST_REF => $actionReferenceKey], + $action->getDeprecatedUsages() ); } @@ -501,7 +532,7 @@ private function findArgumentByName($name, $argumentList) $matchedArgument = array_filter( $argumentList, function ($e) use ($name) { - return $e->getName() == $name; + return $e->getName() === $name; } ); if (isset(array_values($matchedArgument)[0])) { @@ -543,10 +574,20 @@ private function addContextCommentsToActionList($actionList, $actionReferenceKey { $actionStartComment = self::ACTION_GROUP_CONTEXT_START . "[" . $actionReferenceKey . "] " . $this->name; $actionEndComment = self::ACTION_GROUP_CONTEXT_END . "[" . $actionReferenceKey . "] " . $this->name; + + $deprecationNotices = []; + if ($this->getDeprecated() !== null) { + $deprecationNotices[] = "DEPRECATED ACTION GROUP in Test: " . $this->name . ' ' . $this->getDeprecated(); + } + $startAction = new ActionObject( $actionStartComment, ActionObject::ACTION_TYPE_COMMENT, - [ActionObject::ACTION_ATTRIBUTE_USERINPUT => $actionStartComment] + [ActionObject::ACTION_ATTRIBUTE_USERINPUT => $actionStartComment], + null, + ActionObject::MERGE_ACTION_ORDER_BEFORE, + null, + $deprecationNotices ); $endAction = new ActionObject( $actionEndComment, diff --git a/src/Magento/FunctionalTestingFramework/Test/Objects/ActionObject.php b/src/Magento/FunctionalTestingFramework/Test/Objects/ActionObject.php index 9b1cdf1af..451b58aa0 100644 --- a/src/Magento/FunctionalTestingFramework/Test/Objects/ActionObject.php +++ b/src/Magento/FunctionalTestingFramework/Test/Objects/ActionObject.php @@ -56,13 +56,14 @@ class ActionObject "command", "html" ]; - const OLD_ASSERTION_ATTRIBUTES = ["expected", "expectedType", "actual", "actualType"]; const ASSERTION_ATTRIBUTES = ["expectedResult" => "expected", "actualResult" => "actual"]; const ASSERTION_TYPE_ATTRIBUTE = "type"; const ASSERTION_VALUE_ATTRIBUTE = "value"; + const ASSERTION_ELEMENT_ATTRIBUTES = ["selector", "attribute"]; const DELETE_DATA_MUTUAL_EXCLUSIVE_ATTRIBUTES = ["url", "createDataKey"]; const EXTERNAL_URL_AREA_INVALID_ACTIONS = ['amOnPage']; - const FUNCTION_CLOSURE_ACTIONS = ['waitForElementChange', 'performOn', 'executeInSelenium']; + const FUNCTION_CLOSURE_ACTIONS = ['waitForElementChange']; + const COMMAND_ACTION_ATTRIBUTES = ['magentoCLI', 'magentoCLISecret']; const MERGE_ACTION_ORDER_AFTER = 'after'; const MERGE_ACTION_ORDER_BEFORE = 'before'; const ACTION_ATTRIBUTE_TIMEZONE = 'timezone'; @@ -71,9 +72,12 @@ class ActionObject const ACTION_ATTRIBUTE_VARIABLE_REGEX_PARAMETER = '/\(.+\)/'; const ACTION_ATTRIBUTE_VARIABLE_REGEX_PATTERN = '/({{[\w]+\.[\w\[\]]+}})|({{[\w]+\.[\w]+\((?(?!}}).)+\)}})/'; const STRING_PARAMETER_REGEX = "/'[^']+'/"; - const DEFAULT_WAIT_TIMEOUT = 10; + const DEFAULT_COMMAND_WAIT_TIMEOUT = 60; const ACTION_ATTRIBUTE_USERINPUT = 'userInput'; const ACTION_TYPE_COMMENT = 'comment'; + const ACTION_TYPE_HELPER = 'helper'; + const INVISIBLE_STEP_ACTIONS = ['retrieveEntityField', 'getSecret']; + const PAUSE_ACTION_INTERNAL_ATTRIBUTE = 'pauseOnFail'; /** * The unique identifier for the action @@ -82,6 +86,13 @@ class ActionObject */ private $stepKey; + /** + * Array of deprecated entities used in action. + * + * @var array + */ + private $deprecatedUsage = []; + /** * The type of action (e.g. fillField, createData, etc) * @@ -140,6 +151,7 @@ class ActionObject * @param string|null $linkedAction * @param string $order * @param array $actionOrigin + * @param array $deprecatedUsage */ public function __construct( $stepKey, @@ -147,13 +159,15 @@ public function __construct( $actionAttributes, $linkedAction = null, $order = ActionObject::MERGE_ACTION_ORDER_BEFORE, - $actionOrigin = null + $actionOrigin = null, + $deprecatedUsage = [] ) { $this->stepKey = $stepKey; $this->type = $type === self::COMMENT_ACTION ? self::ACTION_TYPE_COMMENT : $type; $this->actionAttributes = $actionAttributes; $this->linkedAction = $linkedAction; $this->actionOrigin = $actionOrigin; + $this->deprecatedUsage = $deprecatedUsage; if ($order === ActionObject::MERGE_ACTION_ORDER_AFTER) { $this->orderOffset = 1; @@ -167,7 +181,7 @@ public function __construct( */ public static function getDefaultWaitTimeout() { - return getenv('WAIT_TIMEOUT') ?: self::DEFAULT_WAIT_TIMEOUT; + return getenv('WAIT_TIMEOUT'); } /** @@ -193,7 +207,7 @@ public function getType() /** * Getter for actionOrigin * - * @return string + * @return array */ public function getActionOrigin() { @@ -270,44 +284,87 @@ public function setTimeout($timeout) public function resolveReferences() { if (empty($this->resolvedCustomAttributes)) { + $this->resolveHelperReferences(); $this->trimAssertionAttributes(); $this->resolveSelectorReferenceAndTimeout(); $this->resolveUrlReference(); $this->resolveDataInputReferences(); + $this->detectCredentials(); $this->validateTimezoneAttribute(); - if ($this->getType() == "deleteData") { + if ($this->getType() === 'deleteData') { $this->validateMutuallyExclusiveAttributes(self::DELETE_DATA_MUTUAL_EXCLUSIVE_ATTRIBUTES); } } } /** - * Flattens expectedResult/actualResults/array nested elements, if necessary. - * e.g. expectedResults[] -> ["expectedType" => "string", "expected" => "value"] - * Warns user if they are using old Assertion syntax. + * Resolves references for helpers. * + * @throws TestReferenceException * @return void */ - public function trimAssertionAttributes() + private function resolveHelperReferences() { - $actionAttributeKeys = array_keys($this->actionAttributes); + if ($this->getType() !== 'helper') { + return; + } + $isResolved = false; - /** MQE-683 DEPRECATE OLD METHOD HERE - * Checks if action has any of the old, single line attributes - * Throws a warning and returns, assuming old syntax is used. - */ - $oldAttributes = array_intersect($actionAttributeKeys, ActionObject::OLD_ASSERTION_ATTRIBUTES); - if (!empty($oldAttributes)) { - $appConfig = MftfApplicationConfig::getConfig(); - if ($appConfig->getPhase() == MftfApplicationConfig::GENERATION_PHASE && $appConfig->verboseEnabled()) { - LoggingUtil::getInstance()->getLogger(ActionObject::class)->deprecation( - "use of one line Assertion actions will be deprecated in MFTF 3.0.0, please use nested syntax", - ["action" => $this->type, "stepKey" => $this->stepKey] + try { + foreach ($this->actionAttributes as $attrKey => $attrValue) { + $this->actionAttributes[$attrKey] = $this->findAndReplaceReferences( + SectionObjectHandler::getInstance(), + $attrValue ); } - return; + $isResolved = true; + } catch (\Exception $e) { + // catching exception to allow other entity type resolution to proceed + } + + try { + foreach ($this->actionAttributes as $attrKey => $attrValue) { + $this->actionAttributes[$attrKey] = $this->findAndReplaceReferences( + PageObjectHandler::getInstance(), + $attrValue + ); + } + $isResolved = true; + } catch (\Exception $e) { + // catching exception to allow other entity type resolution to proceed } + try { + foreach ($this->actionAttributes as $attrKey => $attrValue) { + $this->actionAttributes[$attrKey] = $this->findAndReplaceReferences( + DataObjectHandler::getInstance(), + $attrValue + ); + } + $isResolved = true; + } catch (\Exception $e) { + // catching exception to allow other entity type resolution to proceed + } + + if ($isResolved !== true) { + throw new TestReferenceException( + "Could not resolve entity reference \"{$attrValue}\" " + . "in Action with stepKey \"{$this->getStepKey()}\"", + ["input" => $attrValue, "stepKey" => $this->getStepKey()] + ); + } + } + + /** + * Flattens expectedResult/actualResults/array nested elements, if necessary. + * e.g. expectedResults[] -> ["expectedType" => "string", "expected" => "value"] + * Warns user if they are using old Assertion syntax. + * + * @return void + */ + public function trimAssertionAttributes() + { + $actionAttributeKeys = array_keys($this->actionAttributes); $relevantKeys = array_keys(ActionObject::ASSERTION_ATTRIBUTES); $relevantAssertionAttributes = array_intersect($actionAttributeKeys, $relevantKeys); @@ -315,47 +372,25 @@ public function trimAssertionAttributes() return; } - $this->validateAssertionSchema($relevantAssertionAttributes); - // Flatten nested Elements's type and value into key=>value entries + // Also, add selector/value attributes if they are present in nested Element foreach ($this->actionAttributes as $key => $subAttributes) { + foreach (self::ASSERTION_ELEMENT_ATTRIBUTES as $ATTRIBUTE) { + if (isset($subAttributes[$ATTRIBUTE])) { + $this->actionAttributes[$ATTRIBUTE] = $subAttributes[$ATTRIBUTE]; + } + } if (in_array($key, $relevantKeys)) { $prefix = ActionObject::ASSERTION_ATTRIBUTES[$key]; $this->actionAttributes[$prefix . ucfirst(ActionObject::ASSERTION_TYPE_ATTRIBUTE)] = - $subAttributes[ActionObject::ASSERTION_TYPE_ATTRIBUTE]; + $subAttributes[ActionObject::ASSERTION_TYPE_ATTRIBUTE] ?? "NO_TYPE"; $this->actionAttributes[$prefix] = - $subAttributes[ActionObject::ASSERTION_VALUE_ATTRIBUTE]; + $subAttributes[ActionObject::ASSERTION_VALUE_ATTRIBUTE] ?? ""; unset($this->actionAttributes[$key]); } } } - /** - * Validates that the given assertion attributes have valid schema according to nested assertion syntax. - * @param array $attributes - * @return void - * @throws TestReferenceException - */ - private function validateAssertionSchema($attributes) - { - /** MQE-683 DEPRECATE OLD METHOD HERE - * Unnecessary validation, only needed for backwards compatibility - */ - $singleChildTypes = ['assertEmpty', 'assertFalse', 'assertFileExists', 'assertFileNotExists', - 'assertIsEmpty', 'assertNotEmpty', 'assertNotNull', 'assertNull', 'assertTrue', - 'assertElementContainsAttribute']; - - if (!in_array($this->type, $singleChildTypes)) { - if (!in_array('expectedResult', $attributes) - || !in_array('actualResult', $attributes)) { - throw new TestReferenceException( - "{$this->type} must have both an expectedResult & actualResult defined (stepKey: {$this->stepKey})", - ["action" => $this->type, "stepKey" => $this->stepKey] - ); - } - } - } - /** * Look up the selector for SomeSectionName.ElementName and set it as the selector attribute in the * resolved custom attributes. Also set the timeout value. @@ -383,6 +418,22 @@ private function resolveSelectorReferenceAndTimeout() } } } + /** + * Sets requiredCredentials property + * + * @return void + * @throws TestReferenceException + */ + public function detectCredentials() + { + $requiredCredentials = ""; + $attributes = $this->getCustomActionAttributes(); + if (isset($attributes['userInput']) && stristr($attributes['userInput'], '_CREDS') == true) { + $credentials = explode(".", trim($attributes['userInput'], '{}')); + $requiredCredentials = $credentials[1]; + } + $this->resolvedCustomAttributes['requiredCredentials'] = $requiredCredentials; + } /** * Look up the url for SomePageName and set it, with MAGENTO_BASE_URL prepended, as the url attribute in the @@ -407,7 +458,7 @@ private function resolveUrlReference() $allPages = PageObjectHandler::getInstance()->getAllObjects(); if ($replacement === $url && array_key_exists(trim($url, "{}"), $allPages) ) { - LoggingUtil::getInstance()->getLogger(ActionObject::class)->warning( + throw new TestReferenceException( "page url attribute not found and is required", ["action" => $this->type, "url" => $url, "stepKey" => $this->stepKey] ); @@ -527,17 +578,24 @@ private function findAndReplaceReferences($objectHandler, $inputString) continue; } - if ($obj == null) { + if ($obj === null) { // keep initial values for subsequent logic $replacement = null; $parameterized = false; - } elseif (get_class($obj) == PageObject::class) { + } elseif (get_class($obj) === PageObject::class) { + if ($obj->getDeprecated() !== null) { + $this->deprecatedUsage[] = "DEPRECATED PAGE in Test: " . $match . ' ' . $obj->getDeprecated(); + } $this->validateUrlAreaAgainstActionType($obj); $replacement = $obj->getUrl(); $parameterized = $obj->isParameterized(); - } elseif (get_class($obj) == SectionObject::class) { + } elseif (get_class($obj) === SectionObject::class) { + if ($obj->getDeprecated() !== null) { + $this->deprecatedUsage[] = "DEPRECATED SECTION in Test: " . $match . ' ' . $obj->getDeprecated(); + } list(,$objField) = $this->stripAndSplitReference($match); - if ($obj->getElement($objField) == null) { + + if ($obj->getElement($objField) === null) { throw new TestReferenceException( "Could not resolve entity reference \"{$inputString}\" " . "in Action with stepKey \"{$this->getStepKey()}\"", @@ -547,7 +605,15 @@ private function findAndReplaceReferences($objectHandler, $inputString) $parameterized = $obj->getElement($objField)->isParameterized(); $replacement = $obj->getElement($objField)->getPrioritizedSelector(); $this->setTimeout($obj->getElement($objField)->getTimeout()); - } elseif (get_class($obj) == EntityDataObject::class) { + if ($obj->getElement($objField)->getDeprecated() !== null) { + $this->deprecatedUsage[] = "DEPRECATED ELEMENT in Test: " . $match . ' ' + . $obj->getElement($objField)->getDeprecated(); + } + } elseif (get_class($obj) === EntityDataObject::class) { + if ($obj->getDeprecated() !== null) { + $this->deprecatedUsage[] = "DEPRECATED DATA ENTITY in Test: " + . $match . ' ' . $obj->getDeprecated(); + } $replacement = $this->resolveEntityDataObjectReference($obj, $match); if (is_array($replacement)) { @@ -589,7 +655,7 @@ private function validateMutuallyExclusiveAttributes(array $attributes) . implode("', '", $attributes) . "'", ["type" => $this->getType(), "attributes" => $attributes] ); - } elseif (count($matches) == 0) { + } elseif (count($matches) === 0) { throw new TestReferenceException( "Actions of type '{$this->getType()}' must contain at least one attribute of types '" . implode("', '", $attributes) . "'", @@ -607,7 +673,7 @@ private function validateMutuallyExclusiveAttributes(array $attributes) */ private function validateUrlAreaAgainstActionType($obj) { - if ($obj->getArea() == 'external' && + if ($obj->getArea() === 'external' && in_array($this->getType(), self::EXTERNAL_URL_AREA_INVALID_ACTIONS)) { throw new TestReferenceException( "Page of type 'external' is not compatible with action type '{$this->getType()}'", @@ -648,7 +714,7 @@ private function resolveEntityDataObjectReference($obj, $match) { list(,$objField) = $this->stripAndSplitReference($match); - if (strpos($objField, '[') == true) { + if (strpos($objField, '[') !== false) { // Access <array>...</array> $parts = explode('[', $objField); $name = $parts[0]; @@ -677,8 +743,13 @@ private function resolveParameterization($isParameterized, $replacement, $match, } else { $resolvedReplacement = $replacement; } - if (get_class($object) == PageObject::class && $object->getArea() == PageObject::ADMIN_AREA) { - $resolvedReplacement = "/{{_ENV.MAGENTO_BACKEND_NAME}}/" . $resolvedReplacement; + if (get_class($object) === PageObject::class && $object->getArea() === PageObject::ADMIN_AREA) { + $urlSegments = [ + '{{_ENV.MAGENTO_BACKEND_BASE_URL}}', + '{{_ENV.MAGENTO_BACKEND_NAME}}', + $resolvedReplacement + ]; + $resolvedReplacement = implode('/', $urlSegments); } return $resolvedReplacement; } @@ -740,7 +811,7 @@ private function checkParameterCount($matches, $parameters, $reference) if (count($matches) > count($parameters)) { if (is_array($parameters)) { $parametersGiven = implode(",", $parameters); - } elseif ($parameters == null) { + } elseif ($parameters === null) { $parametersGiven = "NONE"; } else { $parametersGiven = $parameters; @@ -756,7 +827,7 @@ private function checkParameterCount($matches, $parameters, $reference) $reference . ". Parameters Given: " . implode(", ", $parameters), ["reference" => $reference, "parametersGiven" => $parameters] ); - } elseif (count($matches) == 0) { + } elseif (count($matches) === 0) { throw new TestReferenceException( "Parameter Resolution Failed: No parameter matches found in parameterized element with selector " . $reference, @@ -764,4 +835,14 @@ private function checkParameterCount($matches, $parameters, $reference) ); } } + + /** + * Returns array of deprecated usages in Action. + * + * @return array + */ + public function getDeprecatedUsages() + { + return $this->deprecatedUsage; + } } diff --git a/src/Magento/FunctionalTestingFramework/Test/Objects/TestObject.php b/src/Magento/FunctionalTestingFramework/Test/Objects/TestObject.php index 504d207b4..86b33285d 100644 --- a/src/Magento/FunctionalTestingFramework/Test/Objects/TestObject.php +++ b/src/Magento/FunctionalTestingFramework/Test/Objects/TestObject.php @@ -21,14 +21,36 @@ class TestObject const TEST_ACTION_WEIGHT = [ 'waitForPageLoad' => 1500, - 'amOnPage' => 1000, + 'amOnPage' => 1500, 'waitForLoadingMaskToDisappear' => 500, 'wait' => self::WAIT_TIME_ATTRIBUTE, + 'waitForAjaxLoad' => 500, + 'waitForElementNotVisible' => 500, + 'waitForElementVisible' => 500, + 'waitForText' => 500, + 'waitForElement' => 500, + 'waitForJS' => 500, 'comment' => 5, 'assertCount' => 5, - 'closeAdminNotification' => 10 + 'closeAdminNotification' => 10, + 'magentoCLI' => 1000, + 'magentoCron' => 3000, + 'createData' => 500, + 'deleteData' => 200, + 'updateData' => 200, + 'getOTP' => 1000, + 'startMessageQueue' => 700, ]; + const WEBAPI_AUTH_TEST_ACTIONS = [ + 'createData', + 'deleteData', + 'updateData', + 'getData', + ]; + + const WEBAPI_AUTH_TEST_ACTION_WEIGHT = 6000; + /** * Name of the test * @@ -78,6 +100,20 @@ class TestObject */ private $cachedOrderedActions = null; + /** + * Deprecation message. + * + * @var string|null + */ + private $deprecated; + + /** + * Indicates if a test contains an action that requires Web API authentication. + * + * @var boolean + */ + private $hasWebApiAuthAction; + /** * TestObject constructor. * @@ -87,15 +123,25 @@ class TestObject * @param TestHookObject[] $hooks * @param string $filename * @param string $parentTest + * @param string|null $deprecated */ - public function __construct($name, $parsedSteps, $annotations, $hooks, $filename = null, $parentTest = null) - { + public function __construct( + $name, + $parsedSteps, + $annotations, + $hooks, + $filename = null, + $parentTest = null, + $deprecated = null + ) { $this->name = $name; $this->parsedSteps = $parsedSteps; $this->annotations = $annotations; $this->hooks = $hooks; $this->filename = $filename; $this->parentTest = $parentTest; + $this->deprecated = $deprecated; + $this->hasWebApiAuthAction = false; } /** @@ -128,6 +174,16 @@ public function getParentName() return $this->parentTest; } + /** + * Returns deprecated messages. + * + * @return string|null + */ + public function getDeprecated() + { + return $this->deprecated; + } + /** * Getter for the skip_test boolean * @@ -135,11 +191,8 @@ public function getParentName() */ public function isSkipped() { - // TODO deprecation|deprecate MFTF 3.0.0 remove elseif when group skip is no longer allowed if (array_key_exists('skip', $this->annotations)) { return true; - } elseif (array_key_exists('group', $this->annotations) && (in_array("skip", $this->annotations['group']))) { - return true; } return false; } @@ -151,7 +204,7 @@ public function isSkipped() */ public function getCodeceptionName() { - if (strpos($this->name, 'Cest') && substr($this->name, -4) == 'Cest') { + if (strpos($this->name, 'Cest') && substr($this->name, -4) === 'Cest') { return $this->name; } @@ -199,9 +252,36 @@ public function getEstimatedDuration() $testTime = $this->calculateWeightedActionTimes($this->getOrderedActions()); - return $hookTime + $testTime; + if ($this->hasWebApiAuthAction) { + return $hookTime + $testTime + self::WEBAPI_AUTH_TEST_ACTION_WEIGHT; + } else { + return $hookTime + $testTime; + } } + /** + * Function to return credentials + * @return array + */ + public function getCredentials() + { + $requiredCredentials = []; + foreach ($this->hooks as $hookObject) { + foreach ($hookObject->getActions() as $action) { + if (isset($action->getCustomActionAttributes()['requiredCredentials']) + && !empty($action->getCustomActionAttributes()['requiredCredentials'])) { + $requiredCredentials[] = $action->getCustomActionAttributes()['requiredCredentials']; + } + } + } + foreach ($this->getOrderedActions() as $action) { + if (isset($action->getCustomActionAttributes()['requiredCredentials']) + && !empty($action->getCustomActionAttributes()['requiredCredentials'])) { + $requiredCredentials[] = $action->getCustomActionAttributes()['requiredCredentials']; + } + } + return array_unique($requiredCredentials); + } /** * Function which takes a set of actions and estimates time for completion based on action type. * @@ -214,6 +294,11 @@ private function calculateWeightedActionTimes($actions) // search for any actions of special type foreach ($actions as $action) { /** @var ActionObject $action */ + + if (!$this->hasWebApiAuthAction && in_array($action->getType(), self::WEBAPI_AUTH_TEST_ACTIONS)) { + $this->hasWebApiAuthAction = true; + } + if (array_key_exists($action->getType(), self::TEST_ACTION_WEIGHT)) { $weight = self::TEST_ACTION_WEIGHT[$action->getType()]; if ($weight === self::WAIT_TIME_ATTRIBUTE) { diff --git a/src/Magento/FunctionalTestingFramework/Test/Parsers/TestDataParser.php b/src/Magento/FunctionalTestingFramework/Test/Parsers/TestDataParser.php index 18044c1af..dc3489de1 100644 --- a/src/Magento/FunctionalTestingFramework/Test/Parsers/TestDataParser.php +++ b/src/Magento/FunctionalTestingFramework/Test/Parsers/TestDataParser.php @@ -7,29 +7,42 @@ namespace Magento\FunctionalTestingFramework\Test\Parsers; use Magento\FunctionalTestingFramework\Config\DataInterface; +use Magento\FunctionalTestingFramework\Config\MftfApplicationConfig; +use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; +use Magento\FunctionalTestingFramework\Filter\FilterInterface; +use Magento\FunctionalTestingFramework\Util\Logger\LoggingUtil; /** * Class TestDataParser */ class TestDataParser { + /** + * @var DataInterface + */ + private $testData; + /** * TestDataParser constructor. * * @param DataInterface $testData + * @throws \Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException */ public function __construct(DataInterface $testData) { - $this->testData = $testData; + $this->testData = array_filter($testData->get('tests'), function ($value) { + return is_array($value); + }); } /** * Returns an array of data based on *Test.xml files * * @return array + * @throws TestFrameworkException */ public function readTestData() { - return $this->testData->get(); + return $this->testData; } } diff --git a/src/Magento/FunctionalTestingFramework/Test/Util/ActionGroupAnnotationExtractor.php b/src/Magento/FunctionalTestingFramework/Test/Util/ActionGroupAnnotationExtractor.php index 44b004105..7f35ee302 100644 --- a/src/Magento/FunctionalTestingFramework/Test/Util/ActionGroupAnnotationExtractor.php +++ b/src/Magento/FunctionalTestingFramework/Test/Util/ActionGroupAnnotationExtractor.php @@ -13,21 +13,17 @@ */ class ActionGroupAnnotationExtractor extends AnnotationExtractor { - const ACTION_GROUP_REQUIRED_ANNOTATIONS = [ - "description" - ]; - const GENERATE_DOCS_COMMAND = 'generate:docs'; - /** * This method trims away irrelevant tags and returns annotations used in the array passed. The annotations * can be found in both Tests and their child element tests. * - * @param array $testAnnotations - * @param string $filename + * @param array $testAnnotations + * @param string $filename + * @param boolean $validateAnnotations * @return array * @throws \Exception */ - public function extractAnnotations($testAnnotations, $filename) + public function extractAnnotations($testAnnotations, $filename, $validateAnnotations = true) { $annotationObjects = []; $annotations = $this->stripDescriptorTags($testAnnotations, parent::NODE_NAME); @@ -35,51 +31,7 @@ public function extractAnnotations($testAnnotations, $filename) foreach ($annotations as $annotationKey => $annotationData) { $annotationObjects[$annotationKey] = $annotationData[parent::ANNOTATION_VALUE]; } - // TODO: Remove this when all action groups have annotations - if ($this->isCommandDefined()) { - $this->validateMissingAnnotations($annotationObjects, $filename); - } return $annotationObjects; } - - /** - * Validates given annotations against list of required annotations. - * - * @param array $annotationObjects - * @return void - * @throws \Exception - */ - private function validateMissingAnnotations($annotationObjects, $filename) - { - $missingAnnotations = []; - - foreach (self::ACTION_GROUP_REQUIRED_ANNOTATIONS as $REQUIRED_ANNOTATION) { - if (!array_key_exists($REQUIRED_ANNOTATION, $annotationObjects)) { - $missingAnnotations[] = $REQUIRED_ANNOTATION; - } - } - - if (!empty($missingAnnotations)) { - $message = "Action Group File {$filename} is missing required annotations."; - LoggingUtil::getInstance()->getLogger(ActionObject::class)->deprecation( - $message, - ["actionGroup" => $filename, "missingAnnotations" => implode(", ", $missingAnnotations)] - ); - } - } - - /** - * Checks if command is defined as generate:docs - * - * @return boolean - */ - private function isCommandDefined() - { - if (defined('COMMAND') and COMMAND == self::GENERATE_DOCS_COMMAND) { - return true; - } else { - return false; - } - } } diff --git a/src/Magento/FunctionalTestingFramework/Test/Util/ActionGroupObjectExtractor.php b/src/Magento/FunctionalTestingFramework/Test/Util/ActionGroupObjectExtractor.php index e41329cb3..84b960f1b 100644 --- a/src/Magento/FunctionalTestingFramework/Test/Util/ActionGroupObjectExtractor.php +++ b/src/Magento/FunctionalTestingFramework/Test/Util/ActionGroupObjectExtractor.php @@ -11,6 +11,8 @@ use Magento\FunctionalTestingFramework\Test\Objects\ActionGroupObject; use Magento\FunctionalTestingFramework\Test\Objects\ActionObject; use Magento\FunctionalTestingFramework\Test\Objects\ArgumentObject; +use Magento\FunctionalTestingFramework\Test\Objects\TestObject; +use Magento\FunctionalTestingFramework\Util\Logger\LoggingUtil; /** * Class ActionGroupObjectExtractor @@ -58,7 +60,15 @@ public function __construct() public function extractActionGroup($actionGroupData) { $arguments = []; + $deprecated = null; + if (array_key_exists(self::OBJ_DEPRECATED, $actionGroupData)) { + $deprecated = $actionGroupData[self::OBJ_DEPRECATED]; + LoggingUtil::getInstance()->getLogger(ActionGroupObject::class)->deprecation( + "The action group '{$actionGroupData[self::NAME]}' is deprecated.", + ["fileName" => $actionGroupData[self::FILENAME], "deprecatedMessage" => $deprecated] + ); + } $actionGroupReference = $actionGroupData[self::EXTENDS_ACTION_GROUP] ?? null; $actionData = $this->stripDescriptorTags( $actionGroupData, @@ -69,7 +79,8 @@ public function extractActionGroup($actionGroupData) self::FILENAME, self::ACTION_GROUP_INSERT_BEFORE, self::ACTION_GROUP_INSERT_AFTER, - self::EXTENDS_ACTION_GROUP + self::EXTENDS_ACTION_GROUP, + 'deprecated' ); // TODO filename is now available to the ActionGroupObject, integrate this into debug and error statements @@ -99,7 +110,8 @@ public function extractActionGroup($actionGroupData) $arguments, $actions, $actionGroupReference, - $actionGroupData[self::FILENAME] + $actionGroupData[self::FILENAME], + $deprecated ); } diff --git a/src/Magento/FunctionalTestingFramework/Test/Util/ActionMergeUtil.php b/src/Magento/FunctionalTestingFramework/Test/Util/ActionMergeUtil.php index cd81ade24..a601deb24 100644 --- a/src/Magento/FunctionalTestingFramework/Test/Util/ActionMergeUtil.php +++ b/src/Magento/FunctionalTestingFramework/Test/Util/ActionMergeUtil.php @@ -23,9 +23,6 @@ class ActionMergeUtil const WAIT_ATTR = 'timeout'; const WAIT_ACTION_NAME = 'waitForPageLoad'; const WAIT_ACTION_SUFFIX = 'WaitForPageLoad'; - const SKIP_READINESS_ACTION_NAME = 'skipReadinessCheck'; - const SKIP_READINESS_OFF_SUFFIX = 'SkipReadinessOff'; - const SKIP_READINESS_ON_SUFFIX = 'SkipReadinessOn'; const DEFAULT_SKIP_ON_ORDER = 'before'; const DEFAULT_SKIP_OFF_ORDER = 'after'; const DEFAULT_WAIT_ORDER = 'after'; @@ -86,7 +83,6 @@ public function resolveActionSteps($parsedSteps, $skipActionGroupResolution = fa { $this->mergeActions($parsedSteps); $this->insertWaits(); - $this->insertReadinessSkips(); if ($skipActionGroupResolution) { return $this->orderedSteps; @@ -128,7 +124,8 @@ private function resolveSecretFieldAccess($resolvedActions) $action->getCustomActionAttributes(), $action->getLinkedAction(), $action->getOrderOffset(), - $action->getActionOrigin() + $action->getActionOrigin(), + $action->getDeprecatedUsages() ); $actions[$action->getStepKey()] = $action; @@ -173,10 +170,10 @@ private function resolveActionGroups($mergedSteps) foreach ($mergedSteps as $key => $mergedStep) { /**@var ActionObject $mergedStep**/ - if ($mergedStep->getType() == ActionObjectExtractor::ACTION_GROUP_TAG) { + if ($mergedStep->getType() === ActionObjectExtractor::ACTION_GROUP_TAG) { $actionGroupRef = $mergedStep->getCustomActionAttributes()[ActionObjectExtractor::ACTION_GROUP_REF]; $actionGroup = ActionGroupObjectHandler::getInstance()->getObject($actionGroupRef); - if ($actionGroup == null) { + if ($actionGroup === null) { throw new TestReferenceException("Could not find ActionGroup by ref \"{$actionGroupRef}\""); } $args = $mergedStep->getCustomActionAttributes()[ActionObjectExtractor::ACTION_GROUP_ARGUMENTS] ?? null; @@ -232,39 +229,6 @@ private function insertWaits() } } - /** - * Runs through the prepared orderedSteps and calls insertWait if a step requires a wait after it. - * - * @return void - */ - private function insertReadinessSkips() - { - foreach ($this->orderedSteps as $step) { - if (array_key_exists("skipReadiness", $step->getCustomActionAttributes())) { - if ($step->getCustomActionAttributes()['skipReadiness'] == "true") { - $skipReadinessOn = new ActionObject( - $step->getStepKey() . self::SKIP_READINESS_ON_SUFFIX, - self::SKIP_READINESS_ACTION_NAME, - ['state' => "true"], - $step->getStepKey(), - self::DEFAULT_SKIP_ON_ORDER - ); - - $skipReadinessOff = new ActionObject( - $step->getStepKey() . self::SKIP_READINESS_OFF_SUFFIX, - self::SKIP_READINESS_ACTION_NAME, - ['state' => "false"], - $step->getStepKey(), - self::DEFAULT_SKIP_OFF_ORDER - ); - - $this->insertStep($skipReadinessOn); - $this->insertStep($skipReadinessOff); - } - } - } - } - /** * This method takes the steps from the parser and splits steps which need merge from steps that are ordered. * diff --git a/src/Magento/FunctionalTestingFramework/Test/Util/ActionObjectExtractor.php b/src/Magento/FunctionalTestingFramework/Test/Util/ActionObjectExtractor.php index fc874fa24..a9fb0817e 100644 --- a/src/Magento/FunctionalTestingFramework/Test/Util/ActionObjectExtractor.php +++ b/src/Magento/FunctionalTestingFramework/Test/Util/ActionObjectExtractor.php @@ -22,11 +22,12 @@ class ActionObjectExtractor extends BaseObjectExtractor const TEST_ACTION_AFTER = 'after'; const TEST_STEP_MERGE_KEY = 'stepKey'; const ACTION_GROUP_TAG = 'actionGroup'; + const HELPER_TAG = 'helper'; const ACTION_GROUP_REF = 'ref'; const ACTION_GROUP_ARGUMENTS = 'arguments'; const ACTION_GROUP_ARG_VALUE = 'value'; const BEFORE_AFTER_ERROR_MSG = "Merge Error - Steps cannot have both before and after attributes.\tStepKey='%s'"; - const STEP_KEY_BLACKLIST_ERROR_MSG = "StepKeys cannot contain non alphanumeric characters.\tStepKey='%s'"; + const STEP_KEY_BLOCKLIST_ERROR_MSG = "StepKeys cannot contain non alphanumeric characters.\tStepKey='%s'"; const STEP_KEY_EMPTY_ERROR_MSG = "StepKeys cannot be empty.\tAction='%s'"; const DATA_PERSISTENCE_CUSTOM_FIELD = 'field'; const DATA_PERSISTENCE_CUSTOM_FIELD_KEY = 'key'; @@ -69,7 +70,7 @@ public function extractActions($testActions, $testName = null) } if (preg_match('/[^a-zA-Z0-9_]/', $stepKey)) { - throw new XmlException(sprintf(self::STEP_KEY_BLACKLIST_ERROR_MSG, $actionName)); + throw new XmlException(sprintf(self::STEP_KEY_BLOCKLIST_ERROR_MSG, $actionName)); } $actionAttributes = $this->stripDescriptorTags( @@ -84,11 +85,12 @@ public function extractActions($testActions, $testName = null) } $actionAttributes = $this->processActionGroupArgs($actionType, $actionAttributes); + $actionAttributes = $this->processHelperArgs($actionType, $actionAttributes); $linkedAction = $this->processLinkedActions($actionName, $actionData); $actions = $this->extractFieldActions($actionData, $actions); $actionAttributes = $this->extractFieldReferences($actionData, $actionAttributes); - if ($linkedAction['stepKey'] != null) { + if ($linkedAction['stepKey'] !== null) { $stepKeyRefs[$linkedAction['stepKey']][] = $stepKey; } @@ -155,7 +157,7 @@ private function processActionGroupArgs($actionType, $actionAttributeData) $actionAttributeArgData = []; foreach ($actionAttributeData as $attributeDataKey => $attributeDataValues) { - if ($attributeDataKey == self::ACTION_GROUP_REF) { + if ($attributeDataKey === self::ACTION_GROUP_REF) { $actionAttributeArgData[self::ACTION_GROUP_REF] = $attributeDataValues; continue; } @@ -167,6 +169,42 @@ private function processActionGroupArgs($actionType, $actionAttributeData) return $actionAttributeArgData; } + /** + * Takes the helper arguments as an array that can be passed to PHP class + * defined in the action group xml. + * + * @param string $actionType + * @param array $actionAttributeData + * @return array + * @throws TestFrameworkException + */ + private function processHelperArgs($actionType, $actionAttributeData) + { + $reservedHelperVariableNames = ['class', 'method']; + if ($actionType !== self::HELPER_TAG) { + return $actionAttributeData; + } + + $actionAttributeArgData = []; + foreach ($actionAttributeData as $attributeDataKey => $attributeDataValues) { + if (isset($attributeDataValues['nodeName']) && $attributeDataValues['nodeName'] === 'argument') { + if (isset($attributeDataValues['name']) + && in_array($attributeDataValues['name'], $reservedHelperVariableNames)) { + $message = 'Helper argument names ' . implode(',', $reservedHelperVariableNames); + $message .= ' are reserved and can not be used.'; + throw new TestFrameworkException( + $message + ); + } + $actionAttributeArgData[$attributeDataValues['name']] = $attributeDataValues['value'] ?? null; + continue; + } + $actionAttributeArgData[$attributeDataKey] = $attributeDataValues; + } + + return $actionAttributeArgData; + } + /** * Takes the array representing an action and validates it is a persistence type. If of type persistence, * the function checks for any user specified fields to extract as separate actions to be resolved independently @@ -187,7 +225,7 @@ private function extractFieldActions($actionData, $actions) $fieldActions = []; foreach ($actionData as $type => $data) { // determine if field type is entity passed in - if (!is_array($data) || $data[self::NODE_NAME] != self::DATA_PERSISTENCE_CUSTOM_FIELD) { + if (!is_array($data) || $data[self::NODE_NAME] !== self::DATA_PERSISTENCE_CUSTOM_FIELD) { continue; } @@ -220,7 +258,8 @@ private function extractFieldReferences($actionData, $actionAttributes) $attributes = []; foreach ($actionAttributes as $attributeName => $attributeValue) { - if (!is_array($attributeValue) || $attributeValue[self::NODE_NAME] != self::DATA_PERSISTENCE_CUSTOM_FIELD) { + if (!is_array($attributeValue) || + $attributeValue[self::NODE_NAME] !== self::DATA_PERSISTENCE_CUSTOM_FIELD) { $attributes[$attributeName] = $attributeValue; continue; } @@ -267,7 +306,7 @@ private function auditMergeSteps($stepKeyRefs, $testName) }); foreach ($atRiskStepRef as $stepKey => $stepRefs) { - LoggingUtil::getInstance()->getLogger(ActionObjectExtractor::class)->warn( + LoggingUtil::getInstance()->getLogger(ActionObjectExtractor::class)->warning( 'multiple actions referencing step key', ['test' => $testName, 'stepKey' => $stepKey, 'ref' => $stepRefs] ); diff --git a/src/Magento/FunctionalTestingFramework/Test/Util/AnnotationExtractor.php b/src/Magento/FunctionalTestingFramework/Test/Util/AnnotationExtractor.php index d30ed351e..8710fbb22 100644 --- a/src/Magento/FunctionalTestingFramework/Test/Util/AnnotationExtractor.php +++ b/src/Magento/FunctionalTestingFramework/Test/Util/AnnotationExtractor.php @@ -7,8 +7,10 @@ namespace Magento\FunctionalTestingFramework\Test\Util; use Magento\FunctionalTestingFramework\Config\MftfApplicationConfig; +use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; use Magento\FunctionalTestingFramework\Exceptions\XmlException; use Magento\FunctionalTestingFramework\Test\Objects\ActionObject; +use Magento\FunctionalTestingFramework\Util\GenerationErrorHandler; use Magento\FunctionalTestingFramework\Util\Logger\LoggingUtil; /** @@ -55,12 +57,14 @@ public function __construct() * This method trims away irrelevant tags and returns annotations used in the array passed. The annotations * can be found in both Tests and their child element tests. * - * @param array $testAnnotations - * @param string $filename + * @param array $testAnnotations + * @param string $filename + * @param boolean $validateAnnotations * @return array * @throws XmlException + * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ - public function extractAnnotations($testAnnotations, $filename) + public function extractAnnotations($testAnnotations, $filename, $validateAnnotations = true) { $annotationObjects = []; $annotations = $this->stripDescriptorTags($testAnnotations, self::NODE_NAME); @@ -73,34 +77,32 @@ public function extractAnnotations($testAnnotations, $filename) } $annotationValues = []; // Only transform severity annotation - if ($annotationKey == "severity") { + if ($annotationKey === "severity") { $annotationObjects[$annotationKey] = $this->transformAllureSeverityToMagento( - $annotationData[0]['value'] + trim($annotationData[0][self::ANNOTATION_VALUE]) ); continue; } - if ($annotationKey == "skip") { + if ($annotationKey === "skip") { $annotationData = $annotationData['issueId']; - $this->validateSkippedIssues($annotationData, $filename); + if ($validateAnnotations) { + $this->validateSkippedIssues($annotationData, $filename); + } } foreach ($annotationData as $annotationValue) { - $annotationValues[] = $annotationValue[self::ANNOTATION_VALUE]; - } - // TODO deprecation|deprecate MFTF 3.0.0 - if ($annotationKey == "group" && in_array("skip", $annotationValues)) { - LoggingUtil::getInstance()->getLogger(AnnotationExtractor::class)->warning( - "Use of group skip will be deprecated in MFTF 3.0.0. Please update tests to use skip tags.", - ["test" => $filename] - ); + $annotationValues[] = trim($annotationValue[self::ANNOTATION_VALUE]); } $annotationObjects[$annotationKey] = $annotationValues; } + if ($validateAnnotations) { + $this->validateMissingAnnotations($annotationObjects, $filename); + } + $this->addTestCaseIdToTitle($annotationObjects, $filename); - $this->validateMissingAnnotations($annotationObjects, $filename); $this->addStoryTitleToMap($annotationObjects, $filename); return $annotationObjects; @@ -155,7 +157,9 @@ private function validateMissingAnnotations($annotationObjects, $filename) $missingAnnotations = []; foreach (self::REQUIRED_ANNOTATIONS as $REQUIRED_ANNOTATION) { - if (!array_key_exists($REQUIRED_ANNOTATION, $annotationObjects)) { + if (!array_key_exists($REQUIRED_ANNOTATION, $annotationObjects) + || !isset($annotationObjects[$REQUIRED_ANNOTATION][0]) + || empty($annotationObjects[$REQUIRED_ANNOTATION][0])) { $missingAnnotations[] = $REQUIRED_ANNOTATION; } } @@ -164,45 +168,62 @@ private function validateMissingAnnotations($annotationObjects, $filename) $message = "Test {$filename} is missing required annotations."; LoggingUtil::getInstance()->getLogger(ActionObject::class)->deprecation( $message, - ["testName" => $filename, "missingAnnotations" => implode(", ", $missingAnnotations)] + ["testName" => $filename, "missingAnnotations" => implode(", ", $missingAnnotations)], + true ); } } /** * Validates that all Story/Title combinations are unique, builds list of violators if found. - * @throws XmlException + * * @return void + * @throws TestFrameworkException */ public function validateStoryTitleUniqueness() { - $dupes = []; + if (MftfApplicationConfig::getConfig()->getPhase() === MftfApplicationConfig::EXECUTION_PHASE) { + return; + } + $dupes = []; foreach ($this->storyToTitleMappings as $storyTitle => $files) { if (count($files) > 1) { $dupes[$storyTitle] = "'" . implode("', '", $files) . "'"; } } if (!empty($dupes)) { - $message = "Story and Title annotation pairs must be unique:\n\n"; foreach ($dupes as $storyTitle => $tests) { - $storyTitleArray = explode("/", $storyTitle); - $story = $storyTitleArray[0]; - $title = $storyTitleArray[1]; - $message .= "Story: '{$story}' Title: '{$title}' in Tests {$tests}\n\n"; + $message = "Story and Title annotation pairs is not unique in Tests {$tests}\n"; + LoggingUtil::getInstance()->getLogger(self::class)->error($message); + if (MftfApplicationConfig::getConfig()->verboseEnabled() + && MftfApplicationConfig::getConfig()->getPhase() === MftfApplicationConfig::GENERATION_PHASE) { + print('ERROR: ' . $message); + } + $testArray = explode(',', $tests); + foreach ($testArray as $test) { + GenerationErrorHandler::getInstance()->addError( + 'test', + trim($test, "' \t"), + $message, + true + ); + } } - throw new XmlException($message); } } /** * Validates uniqueness between Test Case ID and Titles globally - * @returns void - * @throws XmlException * @return void + * @throws TestFrameworkException */ public function validateTestCaseIdTitleUniqueness() { + if (MftfApplicationConfig::getConfig()->getPhase() === MftfApplicationConfig::EXECUTION_PHASE) { + return; + } + $dupes = []; foreach ($this->testCaseToTitleMappings as $newTitle => $files) { if (count($files) > 1) { @@ -210,14 +231,23 @@ public function validateTestCaseIdTitleUniqueness() } } if (!empty($dupes)) { - $message = "TestCaseId and Title pairs must be unique:\n\n"; foreach ($dupes as $newTitle => $tests) { - $testCaseTitleArray = explode(": ", $newTitle); - $testCaseId = $testCaseTitleArray[0]; - $title = $testCaseTitleArray[1]; - $message .= "TestCaseId: '{$testCaseId}' Title: '{$title}' in Tests {$tests}\n\n"; + $message = "TestCaseId and Title pairs is not unique in Tests {$tests}\n"; + LoggingUtil::getInstance()->getLogger(self::class)->error($message); + if (MftfApplicationConfig::getConfig()->verboseEnabled() + && MftfApplicationConfig::getConfig()->getPhase() === MftfApplicationConfig::GENERATION_PHASE) { + print('ERROR: ' . $message); + } + $testArray = explode(',', $tests); + foreach ($testArray as $test) { + GenerationErrorHandler::getInstance()->addError( + 'test', + trim($test, "' \t"), + $message, + true + ); + } } - throw new XmlException($message); } } @@ -231,7 +261,7 @@ public function validateTestCaseIdTitleUniqueness() public function validateSkippedIssues($issues, $filename) { foreach ($issues as $issueId) { - if (empty($issueId['value'])) { + if (empty(trim($issueId['value']))) { $message = "issueId for skipped tests cannot be empty. Test: $filename"; throw new XmlException($message); } diff --git a/src/Magento/FunctionalTestingFramework/Test/Util/BaseObjectExtractor.php b/src/Magento/FunctionalTestingFramework/Test/Util/BaseObjectExtractor.php index 26644937d..516f79d14 100644 --- a/src/Magento/FunctionalTestingFramework/Test/Util/BaseObjectExtractor.php +++ b/src/Magento/FunctionalTestingFramework/Test/Util/BaseObjectExtractor.php @@ -13,6 +13,7 @@ class BaseObjectExtractor { const NODE_NAME = 'nodeName'; const NAME = 'name'; + const OBJ_DEPRECATED = 'deprecated'; /** * BaseObjectExtractor constructor. diff --git a/src/Magento/FunctionalTestingFramework/Test/Util/ObjectExtensionUtil.php b/src/Magento/FunctionalTestingFramework/Test/Util/ObjectExtensionUtil.php index 91617b1e2..7f6d050fb 100644 --- a/src/Magento/FunctionalTestingFramework/Test/Util/ObjectExtensionUtil.php +++ b/src/Magento/FunctionalTestingFramework/Test/Util/ObjectExtensionUtil.php @@ -7,6 +7,7 @@ namespace Magento\FunctionalTestingFramework\Test\Util; use Magento\FunctionalTestingFramework\Config\MftfApplicationConfig; +use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; use Magento\FunctionalTestingFramework\Exceptions\TestReferenceException; use Magento\FunctionalTestingFramework\Exceptions\XmlException; use Magento\FunctionalTestingFramework\Test\Handlers\ActionGroupObjectHandler; @@ -32,7 +33,8 @@ public function __construct() * * @param TestObject $testObject * @return TestObject - * @throws TestReferenceException|XmlException + * @throws TestFrameworkException + * @throws XmlException */ public function extendTest($testObject) { @@ -40,13 +42,13 @@ public function extendTest($testObject) try { $parentTest = TestObjectHandler::getInstance()->getObject($testObject->getParentName()); } catch (TestReferenceException $error) { + $skippedTest = $this->skipTest($testObject, 'ParentTestDoesNotExist'); if (MftfApplicationConfig::getConfig()->verboseEnabled()) { LoggingUtil::getInstance()->getLogger(ObjectExtensionUtil::class)->debug( "parent test not defined. test will be skipped", ["parent" => $testObject->getParentName(), "test" => $testObject->getName()] ); } - $skippedTest = $this->skipTest($testObject); return $skippedTest; } @@ -62,6 +64,11 @@ public function extendTest($testObject) ->debug("extending test", ["parent" => $parentTest->getName(), "test" => $testObject->getName()]); } + // Skip test if parent is skipped + if ($parentTest->isSkipped()) { + return $this->skipTest($testObject); + } + // Get steps for both the parent and the child tests $referencedTestSteps = $parentTest->getUnresolvedSteps(); $newSteps = $this->processRemoveActions(array_merge($referencedTestSteps, $testObject->getUnresolvedSteps())); @@ -91,7 +98,7 @@ public function extendActionGroup($actionGroupObject) { // Check to see if the parent action group exists $parentActionGroup = ActionGroupObjectHandler::getInstance()->getObject($actionGroupObject->getParentName()); - if ($parentActionGroup == null) { + if ($parentActionGroup === null) { throw new XmlException( "Parent Action Group " . $actionGroupObject->getParentName() . @@ -193,7 +200,7 @@ private function processRemoveActions($actions) // remove actions merged that are of type 'remove' foreach ($actions as $actionName => $actionData) { - if ($actionData->getType() != "remove") { + if ($actionData->getType() !== 'remove') { $cleanedActions[$actionName] = $actionData; } } @@ -205,17 +212,19 @@ private function processRemoveActions($actions) * This method returns a skipped form of the Test Object * * @param TestObject $testObject + * @param string $skipReason * @return TestObject */ - public function skipTest($testObject) + public function skipTest($testObject, $skipReason = null) { $annotations = $testObject->getAnnotations(); + $skipReason = $skipReason ?? 'ParentTestIsSkipped'; // Add skip to the group array if it doesn't already exist if (array_key_exists('skip', $annotations)) { return $testObject; } elseif (!array_key_exists('skip', $annotations)) { - $annotations['skip'] = ['issueId' => "ParentTestDoesNotExist"]; + $annotations['skip'] = ['issueId' => $skipReason]; } $skippedTest = new TestObject( @@ -227,6 +236,12 @@ public function skipTest($testObject) $testObject->getParentName() ); + if (MftfApplicationConfig::getConfig()->verboseEnabled()) { + LoggingUtil::getInstance()->getLogger(ObjectExtensionUtil::class)->debug( + "{$testObject->getName()} is skipped due to {$skipReason}" + ); + } + return $skippedTest; } } diff --git a/src/Magento/FunctionalTestingFramework/Test/Util/TestHookObjectExtractor.php b/src/Magento/FunctionalTestingFramework/Test/Util/TestHookObjectExtractor.php index c11a6e512..523f1278b 100644 --- a/src/Magento/FunctionalTestingFramework/Test/Util/TestHookObjectExtractor.php +++ b/src/Magento/FunctionalTestingFramework/Test/Util/TestHookObjectExtractor.php @@ -6,6 +6,8 @@ namespace Magento\FunctionalTestingFramework\Test\Util; +use Magento\FunctionalTestingFramework\Exceptions\TestReferenceException; +use Magento\FunctionalTestingFramework\Exceptions\XmlException; use Magento\FunctionalTestingFramework\Test\Objects\ActionObject; use Magento\FunctionalTestingFramework\Test\Objects\TestHookObject; @@ -37,7 +39,8 @@ public function __construct() * @param string $hookType * @param array $testHook * @return TestHookObject - * @throws \Exception + * @throws XmlException + * @throws TestReferenceException */ public function extractHook($parentName, $hookType, $testHook) { @@ -57,19 +60,26 @@ public function extractHook($parentName, $hookType, $testHook) /** * Creates the default failed hook object with a single saveScreenshot action. + * And a pause action when ENABLE_PAUSE is set to true. * * @param string $parentName * @return TestHookObject */ public function createDefaultFailedHook($parentName) { - - $saveScreenshotStep = [new ActionObject("saveScreenshot", "saveScreenshot", [])]; + $defaultSteps['saveScreenshot'] = new ActionObject("saveScreenshot", "saveScreenshot", []); + if (getenv('ENABLE_PAUSE') === 'true') { + $defaultSteps['pauseWhenFailed'] = new ActionObject( + 'pauseWhenFailed', + 'pause', + [ActionObject::PAUSE_ACTION_INTERNAL_ATTRIBUTE => true] + ); + } $hook = new TestHookObject( TestObjectExtractor::TEST_FAILED_HOOK, $parentName, - $saveScreenshotStep + $defaultSteps ); return $hook; diff --git a/src/Magento/FunctionalTestingFramework/Test/Util/TestObjectExtractor.php b/src/Magento/FunctionalTestingFramework/Test/Util/TestObjectExtractor.php index d5420e355..daee2ada2 100644 --- a/src/Magento/FunctionalTestingFramework/Test/Util/TestObjectExtractor.php +++ b/src/Magento/FunctionalTestingFramework/Test/Util/TestObjectExtractor.php @@ -7,14 +7,18 @@ namespace Magento\FunctionalTestingFramework\Test\Util; use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; +use Magento\FunctionalTestingFramework\Exceptions\TestReferenceException; use Magento\FunctionalTestingFramework\Exceptions\XmlException; +use Magento\FunctionalTestingFramework\Page\Objects\ElementObject; use Magento\FunctionalTestingFramework\Test\Objects\ActionObject; use Magento\FunctionalTestingFramework\Test\Objects\TestObject; +use Magento\FunctionalTestingFramework\Util\Logger\LoggingUtil; use Magento\FunctionalTestingFramework\Util\ModulePathExtractor; use Magento\FunctionalTestingFramework\Util\Validation\NameValidationUtil; /** * Class TestObjectExtractor + * @SuppressWarnings(PHPMD) */ class TestObjectExtractor extends BaseObjectExtractor { @@ -80,13 +84,16 @@ public function getAnnotationExtractor() * This method takes and array of test data and strips away irrelevant tags. The data is converted into an array of * TestObjects. * - * @param array $testData + * @param array $testData + * @param boolean $validateAnnotations * @return TestObject - * @throws \Exception + * @throws TestReferenceException + * @throws XmlException + * @throws TestFrameworkException */ - public function extractTestData($testData) + public function extractTestData($testData, $validateAnnotations = true) { - // validate the test name for blacklisted char (will cause allure report issues) MQE-483 + // validate the test name for blocklisted char (will cause allure report issues) MQE-483 NameValidationUtil::validateName($testData[self::NAME], "Test"); $testAnnotations = []; @@ -95,7 +102,8 @@ public function extractTestData($testData) $fileNames = explode(",", $filename); $baseFileName = $fileNames[0]; $module = $this->modulePathExtractor->extractModuleName($baseFileName); - $testReference = $testData['extends'] ?? null; + $testReference = $testData['extends'] ?? null; + $deprecated = isset($testData[self::OBJ_DEPRECATED]) ? $testData[self::OBJ_DEPRECATED] : null; $testActions = $this->stripDescriptorTags( $testData, self::NODE_NAME, @@ -107,12 +115,14 @@ public function extractTestData($testData) self::TEST_INSERT_BEFORE, self::TEST_INSERT_AFTER, self::TEST_FILENAME, + self::OBJ_DEPRECATED, 'extends' ); $testAnnotations = $this->annotationExtractor->extractAnnotations( $testData[self::TEST_ANNOTATIONS] ?? [], - $testData[self::NAME] + $testData[self::NAME], + $validateAnnotations ); //Override features with module name if present, populates it otherwise @@ -122,12 +132,20 @@ public function extractTestData($testData) // when $fileNames is not available if (!isset($testAnnotations["description"])) { $testAnnotations["description"] = []; + } else { + $testAnnotations["description"]['main'] = $testAnnotations["description"][0]; + unset($testAnnotations["description"][0]); + } + $testAnnotations["description"]['test_files'] = $this->appendFileNamesInDescriptionAnnotation($fileNames); + + $testAnnotations["description"][self::OBJ_DEPRECATED] = []; + if ($deprecated !== null) { + $testAnnotations["description"][self::OBJ_DEPRECATED][] = $deprecated; + LoggingUtil::getInstance()->getLogger(TestObject::class)->deprecation( + $deprecated, + ["testName" => $filename, "deprecatedTest" => $deprecated] + ); } - $description = $testAnnotations["description"][0] ?? ''; - $testAnnotations["description"][0] = $this->appendFileNamesInDescriptionAnnotation( - $description, - $fileNames - ); // extract before if (array_key_exists(self::TEST_BEFORE_HOOK, $testData) && is_array($testData[self::TEST_BEFORE_HOOK])) { @@ -152,6 +170,10 @@ public function extractTestData($testData) ); } + if (!empty($testData[self::OBJ_DEPRECATED])) { + $testAnnotations[self::OBJ_DEPRECATED] = $testData[self::OBJ_DEPRECATED]; + } + // TODO extract filename info and store try { return new TestObject( @@ -160,7 +182,8 @@ public function extractTestData($testData) $testAnnotations, $testHooks, $filename, - $testReference + $testReference, + $deprecated ); } catch (XmlException $exception) { throw new XmlException($exception->getMessage() . ' in Test ' . $filename); @@ -170,14 +193,13 @@ public function extractTestData($testData) /** * Append names of test files, including merge files, in description annotation * - * @param string $description - * @param array $fileNames + * @param array $fileNames * * @return string */ - private function appendFileNamesInDescriptionAnnotation($description, $fileNames) + private function appendFileNamesInDescriptionAnnotation(array $fileNames) { - $description .= '<br><br><b><font size=+0.9>Test files</font></b><br><br>'; + $filePaths = '<h3>Test files</h3>'; foreach ($fileNames as $fileName) { if (!empty($fileName) && realpath($fileName) !== false) { @@ -187,11 +209,11 @@ private function appendFileNamesInDescriptionAnnotation($description, $fileNames DIRECTORY_SEPARATOR ); if (!empty($relativeFileName)) { - $description .= $relativeFileName . '<br>'; + $filePaths .= $relativeFileName . '<br>'; } } } - return $description; + return $filePaths; } } diff --git a/src/Magento/FunctionalTestingFramework/Test/etc/Actions/assertActions.xsd b/src/Magento/FunctionalTestingFramework/Test/etc/Actions/assertActions.xsd index 4fdde9990..52794f578 100644 --- a/src/Magento/FunctionalTestingFramework/Test/etc/Actions/assertActions.xsd +++ b/src/Magento/FunctionalTestingFramework/Test/etc/Actions/assertActions.xsd @@ -14,11 +14,15 @@ <xs:element type="assertElementContainsAttributeType" name="assertElementContainsAttribute" minOccurs="0" maxOccurs="unbounded"/> <xs:element type="assertArrayHasKeyType" name="assertArrayHasKey" minOccurs="0" maxOccurs="unbounded"/> <xs:element type="assertArrayNotHasKeyType" name="assertArrayNotHasKey" minOccurs="0" maxOccurs="unbounded"/> - <xs:element type="assertArraySubsetType" name="assertArraySubset" minOccurs="0" maxOccurs="unbounded"/> <xs:element type="assertContainsType" name="assertContains" minOccurs="0" maxOccurs="unbounded"/> + <xs:element type="assertStringContainsStringType" name="assertStringContainsString" minOccurs="0" maxOccurs="unbounded"/> + <xs:element type="assertStringContainsStringIgnoringCaseType" name="assertStringContainsStringIgnoringCase" minOccurs="0" maxOccurs="unbounded"/> <xs:element type="assertCountType" name="assertCount" minOccurs="0" maxOccurs="unbounded"/> <xs:element type="assertEmptyType" name="assertEmpty" minOccurs="0" maxOccurs="unbounded"/> <xs:element type="assertEqualsType" name="assertEquals" minOccurs="0" maxOccurs="unbounded"/> + <xs:element type="assertEqualsWithDeltaType" name="assertEqualsWithDelta" minOccurs="0" maxOccurs="unbounded"/> + <xs:element type="assertEqualsCanonicalizingType" name="assertEqualsCanonicalizing" minOccurs="0" maxOccurs="unbounded"/> + <xs:element type="assertEqualsIgnoringCaseType" name="assertEqualsIgnoringCase" minOccurs="0" maxOccurs="unbounded"/> <xs:element type="assertFalseType" name="assertFalse" minOccurs="0" maxOccurs="unbounded"/> <xs:element type="assertFileExistsType" name="assertFileExists" minOccurs="0" maxOccurs="unbounded"/> <xs:element type="assertFileNotExistsType" name="assertFileNotExists" minOccurs="0" maxOccurs="unbounded"/> @@ -26,14 +30,18 @@ <xs:element type="assertGreaterThanType" name="assertGreaterThan" minOccurs="0" maxOccurs="unbounded"/> <xs:element type="assertGreaterThanOrEqualType" name="assertGreaterThanOrEqual" minOccurs="0" maxOccurs="unbounded"/> <xs:element type="assertInstanceOfType" name="assertInstanceOf" minOccurs="0" maxOccurs="unbounded"/> - <xs:element type="assertInternalTypeType" name="assertInternalType" minOccurs="0" maxOccurs="unbounded"/> <xs:element type="assertIsEmptyType" name="assertIsEmpty" minOccurs="0" maxOccurs="unbounded"/> <xs:element type="assertLessOrEqualsType" name="assertLessOrEquals" minOccurs="0" maxOccurs="unbounded"/> <xs:element type="assertLessThanType" name="assertLessThan" minOccurs="0" maxOccurs="unbounded"/> <xs:element type="assertLessThanOrEqualType" name="assertLessThanOrEqual" minOccurs="0" maxOccurs="unbounded"/> <xs:element type="assertNotContainsType" name="assertNotContains" minOccurs="0" maxOccurs="unbounded"/> + <xs:element type="assertStringNotContainsStringType" name="assertStringNotContainsString" minOccurs="0" maxOccurs="unbounded"/> + <xs:element type="assertStringNotContainsStringIgnoringCaseType" name="assertStringNotContainsStringIgnoringCase" minOccurs="0" maxOccurs="unbounded"/> <xs:element type="assertNotEmptyType" name="assertNotEmpty" minOccurs="0" maxOccurs="unbounded"/> <xs:element type="assertNotEqualsType" name="assertNotEquals" minOccurs="0" maxOccurs="unbounded"/> + <xs:element type="assertNotEqualsWithDeltaType" name="assertNotEqualsWithDelta" minOccurs="0" maxOccurs="unbounded"/> + <xs:element type="assertNotEqualsCanonicalizingType" name="assertNotEqualsCanonicalizing" minOccurs="0" maxOccurs="unbounded"/> + <xs:element type="assertNotEqualsIgnoringCaseType" name="assertNotEqualsIgnoringCase" minOccurs="0" maxOccurs="unbounded"/> <xs:element type="assertNotInstanceOfType" name="assertNotInstanceOf" minOccurs="0" maxOccurs="unbounded"/> <xs:element type="assertNotNullType" name="assertNotNull" minOccurs="0" maxOccurs="unbounded"/> <xs:element type="assertNotRegExpType" name="assertNotRegExp" minOccurs="0" maxOccurs="unbounded"/> @@ -49,22 +57,6 @@ </xs:group> <!-- Data Attributes --> - <xs:attribute type="xs:string" name="expected"> - <xs:annotation> - <xs:documentation> - Assertion's Expected value. Cast by expectedType. - </xs:documentation> - </xs:annotation> - </xs:attribute> - - <xs:attribute type="xs:string" name="actual"> - <xs:annotation> - <xs:documentation> - Assertion's Actual value. Cast by actualType. - </xs:documentation> - </xs:annotation> - </xs:attribute> - <xs:attribute type="xs:string" name="message"> <xs:annotation> <xs:documentation> @@ -92,16 +84,11 @@ <!-- Complex Types --> <!-- ASSERTION TYPES --> - <!-- REMOVE expected/expectedType and actual/actualType in MQE-683--> <xs:complexType name="assertionType"> <xs:choice maxOccurs="unbounded"> <xs:element name="expectedResult" type="expectedResultType" minOccurs="0"/> <xs:element name="actualResult" type="actualResultType" minOccurs="0"/> </xs:choice> - <xs:attribute ref="expected"/> - <xs:attribute type="assertEnum" name="expectedType" default="const"/> - <xs:attribute ref="actual"/> - <xs:attribute type="assertEnum" name="actualType" default="const"/> <xs:attribute ref="message"/> <xs:attribute ref="delta"/> <xs:attribute ref="strict"/> @@ -115,23 +102,8 @@ </xs:documentation> </xs:annotation> <xs:choice maxOccurs="unbounded"> - <xs:element name="expectedResult" type="expectedResultType" minOccurs="0"/> + <xs:element name="expectedResult" type="expectedElementContainsType" minOccurs="0"/> </xs:choice> - <xs:attribute type="xs:string" name="expectedValue"> - <xs:annotation> - <xs:documentation> - Assertion's Expected value. Cast by expectedType. - </xs:documentation> - </xs:annotation> - </xs:attribute> - <xs:attribute name="selector" use="required"/> - <xs:attribute type="xs:string" name="attribute" use="required"> - <xs:annotation> - <xs:documentation> - Attribute in given element to be assert against. - </xs:documentation> - </xs:annotation> - </xs:attribute> <xs:attributeGroup ref="commonActionAttributes"/> </xs:complexType> @@ -145,10 +117,6 @@ <xs:element name="expectedResult" type="expectedResultType" minOccurs="0"/> <xs:element name="actualResult" type="actualResultType" minOccurs="0"/> </xs:choice> - <xs:attribute ref="expected"/> - <xs:attribute type="assertEnum" name="expectedType" default="const"/> - <xs:attribute ref="actual"/> - <xs:attribute type="assertEnum" name="actualType" default="const"/> <xs:attribute ref="message"/> <xs:attributeGroup ref="commonActionAttributes"/> </xs:complexType> @@ -163,10 +131,6 @@ <xs:element name="expectedResult" type="expectedResultType" minOccurs="0"/> <xs:element name="actualResult" type="actualResultType" minOccurs="0"/> </xs:choice> - <xs:attribute ref="expected"/> - <xs:attribute type="assertEnum" name="expectedType" default="const"/> - <xs:attribute ref="actual"/> - <xs:attribute type="assertEnum" name="actualType" default="const"/> <xs:attribute ref="message"/> <xs:attributeGroup ref="commonActionAttributes"/> </xs:complexType> @@ -181,10 +145,6 @@ <xs:element name="expectedResult" type="expectedResultType" minOccurs="0"/> <xs:element name="actualResult" type="actualResultType" minOccurs="0"/> </xs:choice> - <xs:attribute ref="expected"/> - <xs:attribute type="assertEnum" name="expectedType" default="const"/> - <xs:attribute ref="actual"/> - <xs:attribute type="assertEnum" name="actualType" default="const"/> <xs:attribute ref="message"/> <xs:attribute ref="strict"/> <xs:attributeGroup ref="commonActionAttributes"/> @@ -200,10 +160,34 @@ <xs:element name="expectedResult" type="expectedResultType" minOccurs="0"/> <xs:element name="actualResult" type="actualResultType" minOccurs="0"/> </xs:choice> - <xs:attribute ref="expected"/> - <xs:attribute type="assertEnum" name="expectedType" default="const"/> - <xs:attribute ref="actual"/> - <xs:attribute type="assertEnum" name="actualType" default="const"/> + <xs:attribute ref="message"/> + <xs:attributeGroup ref="commonActionAttributes"/> + </xs:complexType> + + <xs:complexType name="assertStringContainsStringType"> + <xs:annotation> + <xs:documentation> + Asserts that given string contains a value. + </xs:documentation> + </xs:annotation> + <xs:choice maxOccurs="unbounded"> + <xs:element name="expectedResult" type="expectedResultType" minOccurs="0"/> + <xs:element name="actualResult" type="actualResultType" minOccurs="0"/> + </xs:choice> + <xs:attribute ref="message"/> + <xs:attributeGroup ref="commonActionAttributes"/> + </xs:complexType> + + <xs:complexType name="assertStringContainsStringIgnoringCaseType"> + <xs:annotation> + <xs:documentation> + Asserts that given string contains a value ignoring case. + </xs:documentation> + </xs:annotation> + <xs:choice maxOccurs="unbounded"> + <xs:element name="expectedResult" type="expectedResultType" minOccurs="0"/> + <xs:element name="actualResult" type="actualResultType" minOccurs="0"/> + </xs:choice> <xs:attribute ref="message"/> <xs:attributeGroup ref="commonActionAttributes"/> </xs:complexType> @@ -218,10 +202,6 @@ <xs:element name="expectedResult" type="expectedResultType" minOccurs="0"/> <xs:element name="actualResult" type="actualResultType" minOccurs="0"/> </xs:choice> - <xs:attribute ref="expected"/> - <xs:attribute type="assertEnum" name="expectedType" default="const"/> - <xs:attribute ref="actual"/> - <xs:attribute type="assertEnum" name="actualType" default="const"/> <xs:attribute ref="message"/> <xs:attributeGroup ref="commonActionAttributes"/> </xs:complexType> @@ -235,10 +215,6 @@ <xs:choice maxOccurs="unbounded"> <xs:element name="actualResult" type="actualResultType" minOccurs="0"/> </xs:choice> - <xs:attribute ref="expected"/> - <xs:attribute type="assertEnum" name="expectedType" default="const"/> - <xs:attribute ref="actual"/> - <xs:attribute type="assertEnum" name="actualType" default="const"/> <xs:attribute ref="message"/> <xs:attributeGroup ref="commonActionAttributes"/> </xs:complexType> @@ -246,22 +222,62 @@ <xs:complexType name="assertEqualsType"> <xs:annotation> <xs:documentation> - Asserts that two given variables are equal. Can be given a "delta" to allow precision tolerance in floating point comparison. + Asserts that two given variables are equal. + </xs:documentation> + </xs:annotation> + <xs:choice maxOccurs="unbounded"> + <xs:element name="expectedResult" type="expectedResultType" minOccurs="0"/> + <xs:element name="actualResult" type="actualResultType" minOccurs="0"/> + </xs:choice> + <xs:attribute ref="message"/> + <xs:attributeGroup ref="commonActionAttributes"/> + </xs:complexType> + + <xs:complexType name="assertEqualsWithDeltaType"> + <xs:annotation> + <xs:documentation> + Asserts that two given variables are equal. Accepts a delta. </xs:documentation> </xs:annotation> <xs:choice maxOccurs="unbounded"> <xs:element name="expectedResult" type="expectedResultType" minOccurs="0"/> <xs:element name="actualResult" type="actualResultType" minOccurs="0"/> </xs:choice> - <xs:attribute ref="expected"/> - <xs:attribute type="assertEnum" name="expectedType" default="const"/> - <xs:attribute ref="actual"/> - <xs:attribute type="assertEnum" name="actualType" default="const"/> <xs:attribute ref="delta"/> <xs:attribute ref="message"/> <xs:attributeGroup ref="commonActionAttributes"/> </xs:complexType> + <xs:complexType name="assertEqualsIgnoringCaseType"> + <xs:annotation> + <xs:documentation> + Asserts that two given variables are equal. + </xs:documentation> + </xs:annotation> + <xs:choice maxOccurs="unbounded"> + <xs:element name="expectedResult" type="expectedResultType" minOccurs="0"/> + <xs:element name="actualResult" type="actualResultType" minOccurs="0"/> + </xs:choice> + <xs:attribute ref="message"/> + <xs:attributeGroup ref="commonActionAttributes"/> + </xs:complexType> + + <xs:complexType name="assertEqualsCanonicalizingType"> + <xs:annotation> + <xs:documentation> + Asserts that two given variables are equal. The contents are canonicalized before they are compared. + For instance, when the two variables $expected and $actual are arrays, then these arrays are + sorted before they are compared. + </xs:documentation> + </xs:annotation> + <xs:choice maxOccurs="unbounded"> + <xs:element name="expectedResult" type="expectedResultType" minOccurs="0"/> + <xs:element name="actualResult" type="actualResultType" minOccurs="0"/> + </xs:choice> + <xs:attribute ref="message"/> + <xs:attributeGroup ref="commonActionAttributes"/> + </xs:complexType> + <xs:complexType name="assertFalseType"> <xs:annotation> <xs:documentation> @@ -271,10 +287,6 @@ <xs:choice maxOccurs="unbounded"> <xs:element name="actualResult" type="actualResultType" minOccurs="0"/> </xs:choice> - <xs:attribute ref="expected"/> - <xs:attribute type="assertEnum" name="expectedType" default="const"/> - <xs:attribute ref="actual"/> - <xs:attribute type="assertEnum" name="actualType" default="const"/> <xs:attribute ref="message"/> <xs:attributeGroup ref="commonActionAttributes"/> </xs:complexType> @@ -288,10 +300,6 @@ <xs:choice maxOccurs="unbounded"> <xs:element name="actualResult" type="actualResultType" minOccurs="0"/> </xs:choice> - <xs:attribute ref="expected"/> - <xs:attribute type="assertEnum" name="expectedType" default="const"/> - <xs:attribute ref="actual"/> - <xs:attribute type="assertEnum" name="actualType" default="const"/> <xs:attribute ref="message"/> <xs:attributeGroup ref="commonActionAttributes"/> </xs:complexType> @@ -305,10 +313,6 @@ <xs:choice maxOccurs="unbounded"> <xs:element name="actualResult" type="actualResultType" minOccurs="0"/> </xs:choice> - <xs:attribute ref="expected"/> - <xs:attribute type="assertEnum" name="expectedType" default="const"/> - <xs:attribute ref="actual"/> - <xs:attribute type="assertEnum" name="actualType" default="const"/> <xs:attribute ref="message"/> <xs:attributeGroup ref="commonActionAttributes"/> </xs:complexType> @@ -323,10 +327,6 @@ <xs:element name="expectedResult" type="expectedResultType" minOccurs="0"/> <xs:element name="actualResult" type="actualResultType" minOccurs="0"/> </xs:choice> - <xs:attribute ref="expected"/> - <xs:attribute type="assertEnum" name="expectedType" default="const"/> - <xs:attribute ref="actual"/> - <xs:attribute type="assertEnum" name="actualType" default="const"/> <xs:attribute ref="message"/> <xs:attributeGroup ref="commonActionAttributes"/> </xs:complexType> @@ -341,10 +341,6 @@ <xs:element name="expectedResult" type="expectedResultType" minOccurs="0"/> <xs:element name="actualResult" type="actualResultType" minOccurs="0"/> </xs:choice> - <xs:attribute ref="expected"/> - <xs:attribute type="assertEnum" name="expectedType" default="const"/> - <xs:attribute ref="actual"/> - <xs:attribute type="assertEnum" name="actualType" default="const"/> <xs:attribute ref="message"/> <xs:attributeGroup ref="commonActionAttributes"/> </xs:complexType> @@ -359,10 +355,6 @@ <xs:element name="expectedResult" type="expectedResultType" minOccurs="0"/> <xs:element name="actualResult" type="actualResultType" minOccurs="0"/> </xs:choice> - <xs:attribute ref="expected"/> - <xs:attribute type="assertEnum" name="expectedType" default="const"/> - <xs:attribute ref="actual"/> - <xs:attribute type="assertEnum" name="actualType" default="const"/> <xs:attribute ref="message"/> <xs:attributeGroup ref="commonActionAttributes"/> </xs:complexType> @@ -377,10 +369,6 @@ <xs:element name="expectedResult" type="expectedResultType" minOccurs="0"/> <xs:element name="actualResult" type="actualResultType" minOccurs="0"/> </xs:choice> - <xs:attribute ref="expected"/> - <xs:attribute type="assertEnum" name="expectedType" default="const"/> - <xs:attribute ref="actual"/> - <xs:attribute type="assertEnum" name="actualType" default="const"/> <xs:attribute ref="message"/> <xs:attributeGroup ref="commonActionAttributes"/> </xs:complexType> @@ -395,10 +383,6 @@ <xs:element name="expectedResult" type="expectedResultType" minOccurs="0"/> <xs:element name="actualResult" type="actualResultType" minOccurs="0"/> </xs:choice> - <xs:attribute ref="expected"/> - <xs:attribute type="assertEnum" name="expectedType" default="const"/> - <xs:attribute ref="actual"/> - <xs:attribute type="assertEnum" name="actualType" default="const"/> <xs:attribute ref="message"/> <xs:attributeGroup ref="commonActionAttributes"/> </xs:complexType> @@ -412,10 +396,6 @@ <xs:choice maxOccurs="unbounded"> <xs:element name="actualResult" type="actualResultType" minOccurs="0"/> </xs:choice> - <xs:attribute ref="expected"/> - <xs:attribute type="assertEnum" name="expectedType" default="const"/> - <xs:attribute ref="actual"/> - <xs:attribute type="assertEnum" name="actualType" default="const"/> <xs:attribute ref="message"/> <xs:attributeGroup ref="commonActionAttributes"/> </xs:complexType> @@ -430,10 +410,6 @@ <xs:element name="expectedResult" type="expectedResultType" minOccurs="0"/> <xs:element name="actualResult" type="actualResultType" minOccurs="0"/> </xs:choice> - <xs:attribute ref="expected"/> - <xs:attribute type="assertEnum" name="expectedType" default="const"/> - <xs:attribute ref="actual"/> - <xs:attribute type="assertEnum" name="actualType" default="const"/> <xs:attribute ref="message"/> <xs:attributeGroup ref="commonActionAttributes"/> </xs:complexType> @@ -448,10 +424,6 @@ <xs:element name="expectedResult" type="expectedResultType" minOccurs="0"/> <xs:element name="actualResult" type="actualResultType" minOccurs="0"/> </xs:choice> - <xs:attribute ref="expected"/> - <xs:attribute type="assertEnum" name="expectedType" default="const"/> - <xs:attribute ref="actual"/> - <xs:attribute type="assertEnum" name="actualType" default="const"/> <xs:attribute ref="message"/> <xs:attributeGroup ref="commonActionAttributes"/> </xs:complexType> @@ -466,10 +438,6 @@ <xs:element name="expectedResult" type="expectedResultType" minOccurs="0"/> <xs:element name="actualResult" type="actualResultType" minOccurs="0"/> </xs:choice> - <xs:attribute ref="expected"/> - <xs:attribute type="assertEnum" name="expectedType" default="const"/> - <xs:attribute ref="actual"/> - <xs:attribute type="assertEnum" name="actualType" default="const"/> <xs:attribute ref="message"/> <xs:attributeGroup ref="commonActionAttributes"/> </xs:complexType> @@ -484,10 +452,34 @@ <xs:element name="expectedResult" type="expectedResultType" minOccurs="0"/> <xs:element name="actualResult" type="actualResultType" minOccurs="0"/> </xs:choice> - <xs:attribute ref="expected"/> - <xs:attribute type="assertEnum" name="expectedType" default="const"/> - <xs:attribute ref="actual"/> - <xs:attribute type="assertEnum" name="actualType" default="const"/> + <xs:attribute ref="message"/> + <xs:attributeGroup ref="commonActionAttributes"/> + </xs:complexType> + + <xs:complexType name="assertStringNotContainsStringType"> + <xs:annotation> + <xs:documentation> + Asserts that given string does not contain a value. + </xs:documentation> + </xs:annotation> + <xs:choice maxOccurs="unbounded"> + <xs:element name="expectedResult" type="expectedResultType" minOccurs="0"/> + <xs:element name="actualResult" type="actualResultType" minOccurs="0"/> + </xs:choice> + <xs:attribute ref="message"/> + <xs:attributeGroup ref="commonActionAttributes"/> + </xs:complexType> + + <xs:complexType name="assertStringNotContainsStringIgnoringCaseType"> + <xs:annotation> + <xs:documentation> + Asserts that given string does not contain a value ignoring case. + </xs:documentation> + </xs:annotation> + <xs:choice maxOccurs="unbounded"> + <xs:element name="expectedResult" type="expectedResultType" minOccurs="0"/> + <xs:element name="actualResult" type="actualResultType" minOccurs="0"/> + </xs:choice> <xs:attribute ref="message"/> <xs:attributeGroup ref="commonActionAttributes"/> </xs:complexType> @@ -501,10 +493,6 @@ <xs:choice maxOccurs="unbounded"> <xs:element name="actualResult" type="actualResultType" minOccurs="0"/> </xs:choice> - <xs:attribute ref="expected"/> - <xs:attribute type="assertEnum" name="expectedType" default="const"/> - <xs:attribute ref="actual"/> - <xs:attribute type="assertEnum" name="actualType" default="const"/> <xs:attribute ref="message"/> <xs:attributeGroup ref="commonActionAttributes"/> </xs:complexType> @@ -512,19 +500,59 @@ <xs:complexType name="assertNotEqualsType"> <xs:annotation> <xs:documentation> - Asserts that actual and expected are not equal. Can be given a "delta" to allow precision tolerance in floating point comparison. + Asserts that actual and expected are not equal. </xs:documentation> </xs:annotation> <xs:choice maxOccurs="unbounded"> <xs:element name="expectedResult" type="expectedResultType" minOccurs="0"/> <xs:element name="actualResult" type="actualResultType" minOccurs="0"/> </xs:choice> - <xs:attribute ref="expected"/> - <xs:attribute type="assertEnum" name="expectedType" default="const"/> - <xs:attribute ref="actual"/> - <xs:attribute type="assertEnum" name="actualType" default="const"/> <xs:attribute ref="message"/> + <xs:attributeGroup ref="commonActionAttributes"/> + </xs:complexType> + + <xs:complexType name="assertNotEqualsWithDeltaType"> + <xs:annotation> + <xs:documentation> + Asserts that two given variables are not equal. Accepts a delta. + </xs:documentation> + </xs:annotation> + <xs:choice maxOccurs="unbounded"> + <xs:element name="expectedResult" type="expectedResultType" minOccurs="0"/> + <xs:element name="actualResult" type="actualResultType" minOccurs="0"/> + </xs:choice> <xs:attribute ref="delta"/> + <xs:attribute ref="message"/> + <xs:attributeGroup ref="commonActionAttributes"/> + </xs:complexType> + + <xs:complexType name="assertNotEqualsIgnoringCaseType"> + <xs:annotation> + <xs:documentation> + Asserts that actual and expected are not equal. + </xs:documentation> + </xs:annotation> + <xs:choice maxOccurs="unbounded"> + <xs:element name="expectedResult" type="expectedResultType" minOccurs="0"/> + <xs:element name="actualResult" type="actualResultType" minOccurs="0"/> + </xs:choice> + <xs:attribute ref="message"/> + <xs:attributeGroup ref="commonActionAttributes"/> + </xs:complexType> + + <xs:complexType name="assertNotEqualsCanonicalizingType"> + <xs:annotation> + <xs:documentation> + Asserts that two given variables are equal. The contents are canonicalized before they are compared. + For instance, when the two variables $expected and $actual are arrays, then these arrays are + sorted before they are compared. + </xs:documentation> + </xs:annotation> + <xs:choice maxOccurs="unbounded"> + <xs:element name="expectedResult" type="expectedResultType" minOccurs="0"/> + <xs:element name="actualResult" type="actualResultType" minOccurs="0"/> + </xs:choice> + <xs:attribute ref="message"/> <xs:attributeGroup ref="commonActionAttributes"/> </xs:complexType> @@ -538,10 +566,6 @@ <xs:element name="expectedResult" type="expectedResultType" minOccurs="0"/> <xs:element name="actualResult" type="actualResultType" minOccurs="0"/> </xs:choice> - <xs:attribute ref="expected"/> - <xs:attribute type="assertEnum" name="expectedType" default="const"/> - <xs:attribute ref="actual"/> - <xs:attribute type="assertEnum" name="actualType" default="const"/> <xs:attribute ref="message"/> <xs:attributeGroup ref="commonActionAttributes"/> </xs:complexType> @@ -555,10 +579,6 @@ <xs:choice maxOccurs="unbounded"> <xs:element name="actualResult" type="actualResultType" minOccurs="0"/> </xs:choice> - <xs:attribute ref="expected"/> - <xs:attribute type="assertEnum" name="expectedType" default="const"/> - <xs:attribute ref="actual"/> - <xs:attribute type="assertEnum" name="actualType" default="const"/> <xs:attribute ref="message"/> <xs:attributeGroup ref="commonActionAttributes"/> </xs:complexType> @@ -573,10 +593,6 @@ <xs:element name="expectedResult" type="expectedResultType" minOccurs="0"/> <xs:element name="actualResult" type="actualResultType" minOccurs="0"/> </xs:choice> - <xs:attribute ref="expected"/> - <xs:attribute type="assertEnum" name="expectedType" default="const"/> - <xs:attribute ref="actual"/> - <xs:attribute type="assertEnum" name="actualType" default="const"/> <xs:attribute ref="message"/> <xs:attributeGroup ref="commonActionAttributes"/> </xs:complexType> @@ -591,10 +607,6 @@ <xs:element name="expectedResult" type="expectedResultType" minOccurs="0"/> <xs:element name="actualResult" type="actualResultType" minOccurs="0"/> </xs:choice> - <xs:attribute ref="expected"/> - <xs:attribute type="assertEnum" name="expectedType" default="const"/> - <xs:attribute ref="actual"/> - <xs:attribute type="assertEnum" name="actualType" default="const"/> <xs:attribute ref="message"/> <xs:attributeGroup ref="commonActionAttributes"/> </xs:complexType> @@ -608,10 +620,6 @@ <xs:choice maxOccurs="unbounded"> <xs:element name="actualResult" type="actualResultType" minOccurs="0"/> </xs:choice> - <xs:attribute ref="expected"/> - <xs:attribute type="assertEnum" name="expectedType" default="const"/> - <xs:attribute ref="actual"/> - <xs:attribute type="assertEnum" name="actualType" default="const"/> <xs:attribute ref="message"/> <xs:attributeGroup ref="commonActionAttributes"/> </xs:complexType> @@ -626,10 +634,6 @@ <xs:element name="expectedResult" type="expectedResultType" minOccurs="0"/> <xs:element name="actualResult" type="actualResultType" minOccurs="0"/> </xs:choice> - <xs:attribute ref="expected"/> - <xs:attribute type="assertEnum" name="expectedType" default="const"/> - <xs:attribute ref="actual"/> - <xs:attribute type="assertEnum" name="actualType" default="const"/> <xs:attribute ref="message"/> <xs:attributeGroup ref="commonActionAttributes"/> </xs:complexType> @@ -644,10 +648,6 @@ <xs:element name="expectedResult" type="expectedResultType" minOccurs="0"/> <xs:element name="actualResult" type="actualResultType" minOccurs="0"/> </xs:choice> - <xs:attribute ref="expected"/> - <xs:attribute type="assertEnum" name="expectedType" default="const"/> - <xs:attribute ref="actual"/> - <xs:attribute type="assertEnum" name="actualType" default="const"/> <xs:attribute ref="message"/> <xs:attributeGroup ref="commonActionAttributes"/> </xs:complexType> @@ -662,10 +662,6 @@ <xs:element name="expectedResult" type="expectedResultType" minOccurs="0"/> <xs:element name="actualResult" type="actualResultType" minOccurs="0"/> </xs:choice> - <xs:attribute ref="expected"/> - <xs:attribute type="assertEnum" name="expectedType" default="const"/> - <xs:attribute ref="actual"/> - <xs:attribute type="assertEnum" name="actualType" default="const"/> <xs:attribute ref="message"/> <xs:attributeGroup ref="commonActionAttributes"/> </xs:complexType> @@ -680,10 +676,6 @@ <xs:element name="expectedResult" type="expectedResultType" minOccurs="0"/> <xs:element name="actualResult" type="actualResultType" minOccurs="0"/> </xs:choice> - <xs:attribute ref="expected"/> - <xs:attribute type="assertEnum" name="expectedType" default="const"/> - <xs:attribute ref="actual"/> - <xs:attribute type="assertEnum" name="actualType" default="const"/> <xs:attribute ref="message"/> <xs:attributeGroup ref="commonActionAttributes"/> </xs:complexType> @@ -697,10 +689,6 @@ <xs:choice maxOccurs="unbounded"> <xs:element name="actualResult" type="actualResultType" minOccurs="0"/> </xs:choice> - <xs:attribute ref="expected"/> - <xs:attribute type="assertEnum" name="expectedType" default="const"/> - <xs:attribute ref="actual"/> - <xs:attribute type="assertEnum" name="actualType" default="const"/> <xs:attribute ref="message"/> <xs:attributeGroup ref="commonActionAttributes"/> </xs:complexType> @@ -715,10 +703,6 @@ <xs:element name="expectedResult" type="expectedResultType" minOccurs="0"/> <xs:element name="actualResult" type="actualResultType" minOccurs="0"/> </xs:choice> - <xs:attribute ref="expected"/> - <xs:attribute type="assertEnum" name="expectedType" default="const"/> - <xs:attribute ref="actual"/> - <xs:attribute type="assertEnum" name="actualType" default="const"/> <xs:attributeGroup ref="commonActionAttributes"/> </xs:complexType> @@ -735,6 +719,20 @@ </xs:extension> </xs:simpleContent> </xs:complexType> + <xs:complexType name="expectedElementContainsType"> + <xs:annotation> + <xs:documentation> + Element containing the Expected value and selector/attributeName. + </xs:documentation> + </xs:annotation> + <xs:simpleContent> + <xs:extension base="xs:string"> + <xs:attribute type="xs:string" name="selector" use="required"/> + <xs:attribute type="xs:string" name="attribute" use="required"/> + <xs:attribute type="assertEnum" name="type" use="required"/> + </xs:extension> + </xs:simpleContent> + </xs:complexType> <xs:complexType name="actualResultType"> <xs:annotation> <xs:documentation> @@ -763,4 +761,4 @@ <xs:enumeration value="const"/> </xs:restriction> </xs:simpleType> -</xs:schema> \ No newline at end of file +</xs:schema> diff --git a/src/Magento/FunctionalTestingFramework/Test/etc/Actions/customActions.xsd b/src/Magento/FunctionalTestingFramework/Test/etc/Actions/customActions.xsd index 3a002e126..2ea291cd8 100644 --- a/src/Magento/FunctionalTestingFramework/Test/etc/Actions/customActions.xsd +++ b/src/Magento/FunctionalTestingFramework/Test/etc/Actions/customActions.xsd @@ -11,11 +11,13 @@ <xs:group name="customTags"> <xs:choice> + <xs:element name="helper" type="helperType" minOccurs="0" maxOccurs="unbounded" /> <xs:element type="magentoCLIType" name="magentoCLI" minOccurs="0" maxOccurs="unbounded"/> + <xs:element type="magentoCronType" name="magentoCron" minOccurs="0" maxOccurs="unbounded"/> <xs:element type="closeAdminNotificationType" name="closeAdminNotification" minOccurs="0" maxOccurs="unbounded"/> <xs:element type="searchAndMultiSelectOptionType" name="searchAndMultiSelectOption" minOccurs="0" maxOccurs="unbounded"/> <xs:element type="selectMultipleOptionsType" name="selectMultipleOptions" minOccurs="0" maxOccurs="unbounded"/> - <xs:element type="formatMoneyType" name="formatMoney" minOccurs="0" maxOccurs="unbounded"/> + <xs:element type="formatCurrencyType" name="formatCurrency" minOccurs="0" maxOccurs="unbounded"/> <xs:element type="parseFloatType" name="parseFloat" minOccurs="0" maxOccurs="unbounded"/> <xs:element type="mSetLocaleType" name="mSetLocale" minOccurs="0" maxOccurs="unbounded"/> <xs:element type="mResetLocaleType" name="mResetLocale" minOccurs="0" maxOccurs="unbounded"/> @@ -23,11 +25,37 @@ <xs:element type="clearFieldType" name="clearField" minOccurs="0" maxOccurs="unbounded"/> <xs:element type="assertArrayIsSortedType" name="assertArrayIsSorted" minOccurs="0" maxOccurs="unbounded"/> <xs:element type="generateDateType" name="generateDate" minOccurs="0" maxOccurs="unbounded"/> + <xs:element type="getOTPType" name="getOTP" minOccurs="0" maxOccurs="unbounded"/> + </xs:choice> + </xs:group> + + <xs:group name="returnTags"> + <xs:choice> + <xs:element type="returnType" name="return" minOccurs="0" maxOccurs="1"/> </xs:choice> </xs:group> <!-- Complex Types --> + <xs:complexType name="helperType"> + <xs:sequence> + <xs:element name="argument" type="argumentType" minOccurs="0" maxOccurs="unbounded" /> + </xs:sequence> + <xs:attribute type="xs:string" name="class" use="required" /> + <xs:attribute type="xs:string" name="method" use="required" /> + <xs:attributeGroup ref="commonActionAttributes"/> + </xs:complexType> + + <xs:complexType name="argumentType" mixed="true"> + <xs:attribute name="name" use="required"> + <xs:simpleType> + <xs:restriction base="xs:string"> + <xs:pattern value="[^cm].*|c(.{0,3}|[^l].*|l[^a].*|la[^s].*|las[^s].*|lass.+)|m(.{0,4}|[^e].*|e[^t].*|et[^h].*|eth[^o].*|etho[^d].*|ethod.+)" /> + </xs:restriction> + </xs:simpleType> + </xs:attribute> + </xs:complexType> + <xs:complexType name="magentoCLIType"> <xs:annotation> <xs:documentation> @@ -50,6 +78,47 @@ </xs:documentation> </xs:annotation> </xs:attribute> + <xs:attribute type="xs:string" name="timeout"> + <xs:annotation> + <xs:documentation> + Idle timeout in seconds, defaulted to 60s when not specified. + </xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attributeGroup ref="commonActionAttributes"/> + </xs:extension> + </xs:simpleContent> + </xs:complexType> + + <xs:complexType name="magentoCronType"> + <xs:annotation> + <xs:documentation> + Executes Magento Cron Jobs (selected groups) + </xs:documentation> + </xs:annotation> + <xs:simpleContent> + <xs:extension base="xs:string"> + <xs:attribute type="xs:string" name="groups"> + <xs:annotation> + <xs:documentation> + Cron groups to be executed (separated by space) + </xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attribute type="xs:string" name="arguments"> + <xs:annotation> + <xs:documentation> + Arguments for Magento CLI command, will not be escaped. + </xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attribute type="xs:string" name="timeout"> + <xs:annotation> + <xs:documentation> + Idle timeout in seconds, defaulted to 60s when not specified. + </xs:documentation> + </xs:annotation> + </xs:attribute> <xs:attributeGroup ref="commonActionAttributes"/> </xs:extension> </xs:simpleContent> @@ -116,25 +185,29 @@ <xs:attributeGroup ref="commonActionAttributes"/> </xs:complexType> - <xs:complexType name="formatMoneyType"> + <xs:complexType name="formatCurrencyType"> <xs:annotation> <xs:documentation> - Formats given input to given locale. Returns formatted string for test use. + Format input to specified currency according to the locale specified. Returns formatted string for test use. + Use NumberFormatter::formatCurrency(), see https://www.php.net/manual/en/numberformatter.formatcurrency.php </xs:documentation> </xs:annotation> - <xs:simpleContent> - <xs:extension base="xs:string"> - <xs:attribute ref="userInput"/> - <xs:attribute type="xs:string" name="locale"> - <xs:annotation> - <xs:documentation> - Locale to format given input. Defaults to 'en_US.UTF-8' if nothing is given. - </xs:documentation> - </xs:annotation> - </xs:attribute> - <xs:attributeGroup ref="commonActionAttributes"/> - </xs:extension> - </xs:simpleContent> + <xs:attribute type="xs:string" name="userInput" use="required"/> + <xs:attribute type="xs:string" name="locale" use="required"> + <xs:annotation> + <xs:documentation> + Locale in which the input would be formatted (e.g. en_US). + </xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attribute type="xs:string" name="currency" use="required"> + <xs:annotation> + <xs:documentation> + The 3-letter ISO 4217 currency code indicating the currency to use. + </xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attributeGroup ref="commonActionAttributes"/> </xs:complexType> <xs:complexType name="parseFloatType"> @@ -254,6 +327,19 @@ </xs:simpleContent> </xs:complexType> + <xs:complexType name="getOTPType"> + <xs:annotation> + <xs:documentation> + Generates TOTP by a shared secret. Use https://github.com/Spomky-Labs/otphp + </xs:documentation> + </xs:annotation> + <xs:simpleContent> + <xs:extension base="xs:string"> + <xs:attribute ref="userInput"/> + <xs:attributeGroup ref="commonActionAttributes"/> + </xs:extension> + </xs:simpleContent> + </xs:complexType> <xs:complexType name="arrayType"> <xs:simpleContent> @@ -267,6 +353,26 @@ </xs:simpleContent> </xs:complexType> + <xs:complexType name="returnType"> + <xs:annotation> + <xs:documentation> + Used in an action group to return a value. Must be used only once in action group. Do not use in tests or suites. + </xs:documentation> + </xs:annotation> + <xs:simpleContent> + <xs:extension base="xs:string"> + <xs:attribute name="value" use="required" type="xs:string"> + <xs:annotation> + <xs:documentation> + Value or variable to be returned. + </xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attributeGroup ref="commonActionAttributes"/> + </xs:extension> + </xs:simpleContent> + </xs:complexType> + <xs:simpleType name="sortEnum" final="restriction"> <xs:annotation> <xs:documentation> @@ -278,4 +384,4 @@ <xs:enumeration value="desc"/> </xs:restriction> </xs:simpleType> -</xs:schema> \ No newline at end of file +</xs:schema> diff --git a/src/Magento/FunctionalTestingFramework/Test/etc/Actions/dataActions.xsd b/src/Magento/FunctionalTestingFramework/Test/etc/Actions/dataActions.xsd index 2cd614266..003760bfc 100644 --- a/src/Magento/FunctionalTestingFramework/Test/etc/Actions/dataActions.xsd +++ b/src/Magento/FunctionalTestingFramework/Test/etc/Actions/dataActions.xsd @@ -125,14 +125,32 @@ </xs:simpleContent> </xs:complexType> + <xs:complexType name="additionalFieldType"> - <xs:annotation> - <xs:documentation>field used to override defined fields from metadata or existing data definitions, during operation.</xs:documentation> - </xs:annotation> <xs:simpleContent> <xs:extension base="xs:string"> - <xs:attribute name="key" use="required"/> + <xs:attribute type="xs:string" name="key" use="required"> + <xs:annotation> + <xs:documentation>xp + Key attribute of data/value pair. + </xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attribute type="uniquenessEnumType" name="unique" use="optional"> + <xs:annotation> + <xs:documentation> + Add suite or test wide unique sequence as "prefix" or "suffix" to the data value if specified. + </xs:documentation> + </xs:annotation> + </xs:attribute> </xs:extension> </xs:simpleContent> </xs:complexType> -</xs:schema> \ No newline at end of file + + <xs:simpleType name="uniquenessEnumType"> + <xs:restriction base="xs:string"> + <xs:enumeration value="prefix" /> + <xs:enumeration value="suffix" /> + </xs:restriction> + </xs:simpleType> +</xs:schema> diff --git a/src/Magento/FunctionalTestingFramework/Test/etc/Actions/grabActions.xsd b/src/Magento/FunctionalTestingFramework/Test/etc/Actions/grabActions.xsd index 9109489aa..993657a86 100644 --- a/src/Magento/FunctionalTestingFramework/Test/etc/Actions/grabActions.xsd +++ b/src/Magento/FunctionalTestingFramework/Test/etc/Actions/grabActions.xsd @@ -13,6 +13,7 @@ <xs:choice> <xs:element type="grabAttributeFromType" name="grabAttributeFrom" minOccurs="0" maxOccurs="unbounded"/> <xs:element type="grabCookieType" name="grabCookie" minOccurs="0" maxOccurs="unbounded"/> + <xs:element type="grabCookieAttributesType" name="grabCookieAttributes" minOccurs="0" maxOccurs="unbounded"/> <xs:element type="grabFromCurrentUrlType" name="grabFromCurrentUrl" minOccurs="0" maxOccurs="unbounded"/> <xs:element type="grabMultipleType" name="grabMultiple" minOccurs="0" maxOccurs="unbounded"/> <xs:element type="grabPageSourceType" name="grabPageSource" minOccurs="0" maxOccurs="unbounded"/> @@ -53,6 +54,21 @@ </xs:simpleContent> </xs:complexType> + <xs:complexType name="grabCookieAttributesType"> + <xs:annotation> + <xs:documentation> + Grabs a cookie attributes value. + </xs:documentation> + </xs:annotation> + <xs:simpleContent> + <xs:extension base="xs:string"> + <xs:attribute ref="userInput"/> + <xs:attribute ref="parameterArray"/> + <xs:attributeGroup ref="commonActionAttributes"/> + </xs:extension> + </xs:simpleContent> + </xs:complexType> + <xs:complexType name="grabFromCurrentUrlType"> <xs:annotation> <xs:documentation> @@ -129,4 +145,4 @@ </xs:extension> </xs:simpleContent> </xs:complexType> -</xs:schema> \ No newline at end of file +</xs:schema> diff --git a/src/Magento/FunctionalTestingFramework/Test/etc/Actions/waitActions.xsd b/src/Magento/FunctionalTestingFramework/Test/etc/Actions/waitActions.xsd index 70b8201a1..18214ba9a 100644 --- a/src/Magento/FunctionalTestingFramework/Test/etc/Actions/waitActions.xsd +++ b/src/Magento/FunctionalTestingFramework/Test/etc/Actions/waitActions.xsd @@ -23,6 +23,8 @@ <xs:element type="waitForPwaElementNotVisibleType" name="waitForPwaElementNotVisible" minOccurs="0" maxOccurs="unbounded"/> <xs:element type="waitForPwaElementVisibleType" name="waitForPwaElementVisible" minOccurs="0" maxOccurs="unbounded"/> <xs:element type="waitForTextType" name="waitForText" minOccurs="0" maxOccurs="unbounded"/> + <xs:element type="waitForElementClickableType" name="waitForElementClickable" minOccurs="0" maxOccurs="unbounded"/> + </xs:choice> </xs:group> @@ -224,4 +226,19 @@ </xs:extension> </xs:simpleContent> </xs:complexType> + <xs:complexType name="waitForElementClickableType"> + <xs:annotation> + <xs:documentation> + Waits up to $timeout seconds for the given element to be clickable. + If element doesn’t become clickable, a timeout exception is thrown. + </xs:documentation> + </xs:annotation> + <xs:simpleContent> + <xs:extension base="xs:string"> + <xs:attribute ref="selector" use="required"/> + <xs:attribute ref="time"/> + <xs:attributeGroup ref="commonActionAttributes"/> + </xs:extension> + </xs:simpleContent> + </xs:complexType> </xs:schema> \ No newline at end of file diff --git a/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd b/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd index 6e9e798f1..2638722d8 100644 --- a/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd +++ b/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd @@ -7,47 +7,11 @@ --> <xs:schema elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema"> - <xs:include schemaLocation="../../Test/etc/actionTypeTags.xsd"/> - <xs:element name="actionGroups" type="actionGroupType"/> - <xs:complexType name="actionGroupType"> - <xs:choice minOccurs="0" maxOccurs="unbounded"> - <xs:element type="actionsRefType" name="actionGroup" maxOccurs="unbounded"/> - </xs:choice> - </xs:complexType> - <xs:complexType name="actionsRefType"> - <xs:choice minOccurs="0" maxOccurs="unbounded"> - <xs:group ref="actionTypeTags"/> - <xs:element name="arguments"> - <xs:complexType> - <xs:sequence> - <xs:element name="argument" maxOccurs="unbounded" minOccurs="0"> - <xs:complexType> - <xs:attribute type="xs:string" name="name" use="required"/> - <xs:attribute type="xs:string" name="defaultValue"/> - <xs:attribute type="dataTypeEnum" name="type" default="entity"/> - </xs:complexType> - </xs:element> - </xs:sequence> - </xs:complexType> - </xs:element> - <xs:element name="annotations"> - <xs:complexType> - <xs:sequence> - <xs:element name="description"/> - </xs:sequence> - </xs:complexType> - </xs:element> - </xs:choice> - <xs:attribute type="xs:string" name="name" use="required"/> - <xs:attribute type="xs:string" name="filename"/> - <xs:attribute type="xs:string" name="insertBefore"/> - <xs:attribute type="xs:string" name="insertAfter"/> - <xs:attribute type="xs:string" name="extends"/> - </xs:complexType> - <xs:simpleType name="dataTypeEnum" final="restriction"> - <xs:restriction base="xs:string"> - <xs:enumeration value="string"/> - <xs:enumeration value="entity"/> - </xs:restriction> - </xs:simpleType> + <xs:redefine schemaLocation="mergedActionGroupSchema.xsd"> + <xs:complexType name="actionGroupType"> + <xs:choice minOccurs="0" maxOccurs="1"> + <xs:element type="actionsRefType" name="actionGroup" maxOccurs="1"/> + </xs:choice> + </xs:complexType> + </xs:redefine> </xs:schema> diff --git a/src/Magento/FunctionalTestingFramework/Test/etc/actionTypeTags.xsd b/src/Magento/FunctionalTestingFramework/Test/etc/actionTypeTags.xsd index 9aa772858..1ce58001d 100644 --- a/src/Magento/FunctionalTestingFramework/Test/etc/actionTypeTags.xsd +++ b/src/Magento/FunctionalTestingFramework/Test/etc/actionTypeTags.xsd @@ -34,7 +34,7 @@ <xs:element type="closeTabType" name="closeTab" minOccurs="0" maxOccurs="unbounded"/> <xs:element type="commentType" name="comment" minOccurs="0" maxOccurs="unbounded"/> <xs:element type="dragAndDropType" name="dragAndDrop" minOccurs="0" maxOccurs="unbounded"/> - <xs:element type="executeInSeleniumType" name="executeInSelenium" minOccurs="0" maxOccurs="unbounded"/> + <xs:element type="rapidClickType" name="rapidClick" minOccurs="0" maxOccurs="unbounded"/> <xs:element type="executeJSType" name="executeJS" minOccurs="0" maxOccurs="unbounded"/> <xs:element type="fillFieldType" name="fillField" minOccurs="0" maxOccurs="unbounded"/> <xs:element type="loadSessionSnapshotType" name="loadSessionSnapshot" minOccurs="0" maxOccurs="unbounded"/> @@ -44,8 +44,7 @@ <xs:element type="moveForwardType" name="moveForward" minOccurs="0" maxOccurs="unbounded"/> <xs:element type="moveMouseOverType" name="moveMouseOver" minOccurs="0" maxOccurs="unbounded"/> <xs:element type="openNewTabType" name="openNewTab" minOccurs="0" maxOccurs="unbounded"/> - <xs:element type="pauseExecutionType" name="pauseExecution" minOccurs="0" maxOccurs="unbounded"/> - <xs:element type="performOnType" name="performOn" minOccurs="0" maxOccurs="unbounded"/> + <xs:element type="pauseType" name="pause" minOccurs="0" maxOccurs="unbounded"/> <xs:element type="pressKeyType" name="pressKey" minOccurs="0" maxOccurs="unbounded"/> <xs:element type="reloadPageType" name="reloadPage" minOccurs="0" maxOccurs="unbounded"/> <xs:element type="resetCookieType" name="resetCookie" minOccurs="0" maxOccurs="unbounded"/> @@ -70,6 +69,12 @@ </xs:choice> </xs:group> + <xs:group name="returnTypeTags"> + <xs:choice> + <xs:group ref="returnTags"/> + </xs:choice> + </xs:group> + <!-- Complex Types --> <xs:complexType name="failType"> @@ -249,6 +254,33 @@ </xs:simpleContent> </xs:complexType> + <xs:complexType name="rapidClickType"> + <xs:annotation> + <xs:documentation> + Performs simple mouse rapid click as per given count. + </xs:documentation> + </xs:annotation> + <xs:simpleContent> + <xs:extension base="xs:string"> + <xs:attribute type="xs:string" name="selector" use="required"> + <xs:annotation> + <xs:documentation> + Selector for rapid click. + </xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attribute type="xs:string" name="count" use="required"> + <xs:annotation> + <xs:documentation> + Click count. + </xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attributeGroup ref="commonActionAttributes"/> + </xs:extension> + </xs:simpleContent> + </xs:complexType> + <xs:complexType name="dragAndDropType"> <xs:annotation> <xs:documentation> @@ -290,20 +322,6 @@ </xs:simpleContent> </xs:complexType> - <xs:complexType name="executeInSeleniumType"> - <xs:annotation> - <xs:documentation> - Allows you to use Selenium WebDriver methods directly; last resort method, do not use regularly. - </xs:documentation> - </xs:annotation> - <xs:simpleContent> - <xs:extension base="xs:string"> - <xs:attribute ref="function" use="required"/> - <xs:attributeGroup ref="commonActionAttributes"/> - </xs:extension> - </xs:simpleContent> - </xs:complexType> - <xs:complexType name="executeJSType"> <xs:annotation> <xs:documentation> @@ -431,7 +449,7 @@ </xs:simpleContent> </xs:complexType> - <xs:complexType name="pauseExecutionType"> + <xs:complexType name="pauseType"> <xs:annotation> <xs:documentation> Pauses test execution in debug mode. To proceed test, press "ENTER" in console. Useful for test writing. @@ -444,21 +462,6 @@ </xs:simpleContent> </xs:complexType> - <xs:complexType name="performOnType"> - <xs:annotation> - <xs:documentation> - Performs given function on element. - </xs:documentation> - </xs:annotation> - <xs:simpleContent> - <xs:extension base="xs:string"> - <xs:attribute ref="selector" use="required"/> - <xs:attribute ref="function" use="required"/> - <xs:attributeGroup ref="commonActionAttributes"/> - </xs:extension> - </xs:simpleContent> - </xs:complexType> - <xs:complexType name="pressKeyType"> <xs:annotation> <xs:documentation> diff --git a/src/Magento/FunctionalTestingFramework/Test/etc/mergedActionGroupSchema.xsd b/src/Magento/FunctionalTestingFramework/Test/etc/mergedActionGroupSchema.xsd new file mode 100644 index 000000000..f45c33acd --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Test/etc/mergedActionGroupSchema.xsd @@ -0,0 +1,76 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<xs:schema elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema"> + <xs:include schemaLocation="../../Test/etc/actionTypeTags.xsd"/> + <xs:element name="actionGroups" type="actionGroupType"/> + <xs:complexType name="actionGroupType"> + <xs:choice minOccurs="0" maxOccurs="unbounded"> + <xs:element type="actionsRefType" name="actionGroup" maxOccurs="unbounded"/> + </xs:choice> + </xs:complexType> + <xs:complexType name="actionsRefType"> + <xs:sequence> + <xs:choice minOccurs="0" maxOccurs="unbounded"> + <xs:group ref="actionTypeTags"/> + <xs:element ref="arguments"/> + <xs:element ref="annotations"/> + </xs:choice> + <xs:sequence minOccurs="0"> + <xs:group ref="returnTags"/> + <xs:choice minOccurs="0" maxOccurs="unbounded"> + <xs:group ref="actionTypeTags"/> + <xs:element ref="arguments"/> + <xs:element ref="annotations"/> + </xs:choice> + </xs:sequence> + </xs:sequence> + <xs:attribute type="xs:string" name="name" use="required"/> + <xs:attribute type="xs:string" name="filename"/> + <xs:attribute type="xs:string" name="insertBefore"/> + <xs:attribute type="xs:string" name="insertAfter"/> + <xs:attribute type="xs:string" name="extends"/> + <xs:attribute type="xs:string" name="deprecated"> + <xs:annotation> + <xs:documentation> + Message and flag which shows that entity is deprecated. + </xs:documentation> + </xs:annotation> + </xs:attribute> + </xs:complexType> + <xs:simpleType name="dataTypeEnum" final="restriction"> + <xs:restriction base="xs:string"> + <xs:enumeration value="string"/> + <xs:enumeration value="entity"/> + </xs:restriction> + </xs:simpleType> + + <!-- elements --> + + <xs:element name="arguments"> + <xs:complexType> + <xs:sequence> + <xs:element name="argument" maxOccurs="unbounded" minOccurs="0"> + <xs:complexType> + <xs:attribute type="xs:string" name="name" use="required"/> + <xs:attribute type="xs:string" name="defaultValue"/> + <xs:attribute type="dataTypeEnum" name="type" default="entity"/> + </xs:complexType> + </xs:element> + </xs:sequence> + </xs:complexType> + </xs:element> + + <xs:element name="annotations"> + <xs:complexType> + <xs:sequence> + <xs:element name="description"/> + </xs:sequence> + </xs:complexType> + </xs:element> +</xs:schema> diff --git a/src/Magento/FunctionalTestingFramework/Test/etc/mergedTestSchema.xsd b/src/Magento/FunctionalTestingFramework/Test/etc/mergedTestSchema.xsd index fe45945ee..6690ef8dd 100644 --- a/src/Magento/FunctionalTestingFramework/Test/etc/mergedTestSchema.xsd +++ b/src/Magento/FunctionalTestingFramework/Test/etc/mergedTestSchema.xsd @@ -86,6 +86,13 @@ <xs:attribute type="xs:string" name="insertBefore"/> <xs:attribute type="xs:string" name="insertAfter"/> <xs:attribute type="xs:string" name="extends"/> + <xs:attribute type="xs:string" name="deprecated"> + <xs:annotation> + <xs:documentation> + Message and flag which shows that entity is deprecated. + </xs:documentation> + </xs:annotation> + </xs:attribute> </xs:complexType> <xs:group name="testTypeTags"> <xs:choice> @@ -129,13 +136,6 @@ </xs:documentation> </xs:annotation> </xs:attribute> - <xs:attribute type="xs:boolean" name="skipReadiness" use="prohibited"> - <xs:annotation> - <xs:documentation> - Flag for skipping readiness check. - </xs:documentation> - </xs:annotation> - </xs:attribute> <xs:attributeGroup ref="commonActionAttributes"/> </xs:complexType> </xs:schema> diff --git a/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd b/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd index f9b09a86f..8fce6e63e 100644 --- a/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd +++ b/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd @@ -12,7 +12,7 @@ <xs:complexContent> <xs:restriction base="configType"> <xs:choice> - <xs:element type="testType" name="test" maxOccurs="unbounded"> + <xs:element type="testType" name="test" maxOccurs="1"> <xs:unique name="uniqueStepKeyInTestBlock"> <xs:annotation> <xs:documentation> diff --git a/src/Magento/FunctionalTestingFramework/Upgrade/RemoveModuleFileInSuiteFiles.php b/src/Magento/FunctionalTestingFramework/Upgrade/RemoveModuleFileInSuiteFiles.php new file mode 100644 index 000000000..a42fbbf31 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Upgrade/RemoveModuleFileInSuiteFiles.php @@ -0,0 +1,152 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\FunctionalTestingFramework\Upgrade; + +use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; +use Magento\FunctionalTestingFramework\Util\Script\ScriptUtil; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Component\Finder\Finder; + +/** + * Class RemoveModuleFileInSuiteFiles + * @package Magento\FunctionalTestingFramework\Upgrade + */ +class RemoveModuleFileInSuiteFiles implements UpgradeInterface +{ + /** + * OutputInterface + * + * @var OutputInterface + */ + private $output; + + /** + * Console output style + * + * @var SymfonyStyle + */ + private $ioStyle = null; + + /** + * Indicate if notice is print + * + * @var boolean + */ + private $printNotice = false; + + /** + * Number of test being updated + * + * @var integer + */ + private $testsUpdated = 0; + + /** + * Indicate if a match and replace has happened + * + * @var boolean + */ + private $replaced = false; + + /** + * Scan all suite xml files, remove <module file="".../> node, and print update message + * + * @param InputInterface $input + * @param OutputInterface $output + * @return string + * @throws TestFrameworkException + */ + public function execute(InputInterface $input, OutputInterface $output) + { + $scriptUtil = new ScriptUtil(); + $this->setOutputStyle($input, $output); + $this->output = $output; + $testPaths[] = $input->getArgument('path'); + if (empty($testPaths[0])) { + $testPaths = $scriptUtil->getAllModulePaths(); + } + + // Get module suite xml files + $xmlFiles = $scriptUtil->getModuleXmlFilesByScope($testPaths, 'Suite'); + $this->processXmlFiles($xmlFiles); + + return ("Removed module file reference in {$this->testsUpdated} suite file(s)."); + } + + /** + * Process on list of xml files + * + * @param Finder $xmlFiles + * @return void + */ + private function processXmlFiles($xmlFiles) + { + foreach ($xmlFiles as $file) { + $contents = $file->getContents(); + $filePath = $file->getRealPath(); + $this->replaced = false; + $contents = $this->removeModuleFileAttributeInSuite($contents, $filePath); + if ($this->replaced) { + file_put_contents($filePath, $contents); + $this->testsUpdated++; + } + } + } + + /** + * Remove module file attribute in Suite xml file + * + * @param string $contents + * @param string $file + * @return string|string[]|null + */ + private function removeModuleFileAttributeInSuite($contents, $file) + { + $pattern = '/<module[^\<\>]+file[\s]*=[\s]*"(?<file>[^"\<\>]*)"[^\>\<]*>/'; + $contents = preg_replace_callback( + $pattern, + function ($matches) use ($file) { + if (!$this->printNotice) { + $this->ioStyle->note( + '`file` is not a valid attribute for <module> in Suite XML schema.' . PHP_EOL + . 'The `file`references in the following xml files are commented out. ' + . 'Consider using <test> instead.' + ); + $this->printNotice = true; + } + $this->output->writeln( + PHP_EOL + . '"' . trim($matches[0]) . '"' . PHP_EOL + . 'is commented out from file: ' . $file . PHP_EOL + ); + $result = str_replace('<module', '<!--module', $matches[0]); + $result = str_replace('>', '--> <!-- Please replace with <test name="" -->', $result); + $this->replaced = true; + return $result; + }, + $contents + ); + return $contents; + } + + /** + * Set Symfony Style for output + * + * @param InputInterface $input + * @param OutputInterface $output + * @return void + */ + private function setOutputStyle(InputInterface $input, OutputInterface $output) + { + // For output style + if (null === $this->ioStyle) { + $this->ioStyle = new SymfonyStyle($input, $output); + } + } +} diff --git a/src/Magento/FunctionalTestingFramework/Upgrade/RemoveUnusedArguments.php b/src/Magento/FunctionalTestingFramework/Upgrade/RemoveUnusedArguments.php new file mode 100644 index 000000000..ced1042ad --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Upgrade/RemoveUnusedArguments.php @@ -0,0 +1,65 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\FunctionalTestingFramework\Upgrade; + +use Magento\FunctionalTestingFramework\StaticCheck\ActionGroupArgumentsCheck; +use Magento\FunctionalTestingFramework\Util\Script\ScriptUtil; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Filesystem\Filesystem; +use DOMElement; + +/** + * Class RenameMetadataFiles + * @package Magento\FunctionalTestingFramework\Upgrade + */ +class RemoveUnusedArguments implements UpgradeInterface +{ + const ARGUMENTS_BLOCK_REGEX_PATTERN = "/\s*<arguments.*\/arguments>/s"; + + /** + * Updates all actionGroup xml files + * + * @param InputInterface $input + * @param OutputInterface $output + * @return string + */ + public function execute(InputInterface $input, OutputInterface $output) + { + $scriptUtil = new ScriptUtil(); + $testPaths[] = $input->getArgument('path'); + if (empty($testPaths[0])) { + $testPaths = $scriptUtil->getAllModulePaths(); + } + $xmlFiles = $scriptUtil->getModuleXmlFilesByScope($testPaths, 'ActionGroup'); + $actionGroupsUpdated = 0; + $fileSystem = new Filesystem(); + foreach ($xmlFiles as $file) { + $contents = $file->getContents(); + $argumentsCheck = new ActionGroupArgumentsCheck(); + /** @var DOMElement $actionGroup */ + $actionGroup = $argumentsCheck->getActionGroupDomElement($contents); + $allArguments = $argumentsCheck->extractActionGroupArguments($actionGroup); + $unusedArguments = $argumentsCheck->findUnusedArguments($allArguments, $contents); + if (empty($unusedArguments)) { + continue; + } + //Remove <arguments> block if all arguments are unused + if (empty(array_diff($allArguments, $unusedArguments))) { + $contents = preg_replace(self::ARGUMENTS_BLOCK_REGEX_PATTERN, '', $contents); + } else { + foreach ($unusedArguments as $argument) { + $argumentRegexPattern = "/\s*<argument.*name\s*=\s*\"".$argument."\".*\/>/"; + $contents = preg_replace($argumentRegexPattern, '', $contents); + } + } + $fileSystem->dumpFile($file->getRealPath(), $contents); + $actionGroupsUpdated++; + } + return "Removed unused action group arguments from {$actionGroupsUpdated} file(s)."; + } +} diff --git a/src/Magento/FunctionalTestingFramework/Upgrade/RenameMetadataFiles.php b/src/Magento/FunctionalTestingFramework/Upgrade/RenameMetadataFiles.php new file mode 100644 index 000000000..5be881a62 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Upgrade/RenameMetadataFiles.php @@ -0,0 +1,73 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\FunctionalTestingFramework\Upgrade; + +use Magento\FunctionalTestingFramework\Util\Script\ScriptUtil; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Finder\Finder; + +/** + * Class RenameMetadataFiles + * @package Magento\FunctionalTestingFramework\Upgrade + */ +class RenameMetadataFiles implements UpgradeInterface +{ + /** + * Upgrades all test xml files + * + * @param InputInterface $input + * @param OutputInterface $output + * @return string + */ + public function execute(InputInterface $input, OutputInterface $output) + { + $scriptUtil = new ScriptUtil(); + $testPaths[] = $input->getArgument('path'); + if (empty($testPaths[0])) { + $testPaths = $scriptUtil->getAllModulePaths(); + } + + foreach ($testPaths as $testsPath) { + $finder = new Finder(); + $finder->files()->in($testsPath)->name("*-meta.xml"); + + foreach ($finder->files() as $file) { + $oldFileName = $file->getFileName(); + $newFileName = $this->convertFileName($oldFileName); + $oldPath = $file->getPathname(); + $newPath = $file->getPath() . "/" . $newFileName; + print("Renaming " . $oldPath . " => " . $newPath . "\n"); + rename($oldPath, $newPath); + } + } + + return "Finished renaming -meta.xml files."; + } + + /** + * Convert filenames like: + * user_role-meta.xml => UserRoleMeta.xml + * store-meta.xml => StoreMeta.xml + * + * @param string $oldFileName + * @return string + */ + private function convertFileName(string $oldFileName) + { + $stripEnding = preg_replace("/-meta.xml/", "", $oldFileName); + $hyphenToUnderscore = str_replace("-", "_", $stripEnding); + $parts = explode("_", $hyphenToUnderscore); + $ucParts = []; + foreach ($parts as $part) { + $ucParts[] = ucfirst($part); + } + $recombine = join("", $ucParts); + $addEnding = $recombine . "Meta.xml"; + return $addEnding; + } +} diff --git a/src/Magento/FunctionalTestingFramework/Upgrade/SplitMultipleEntitiesFiles.php b/src/Magento/FunctionalTestingFramework/Upgrade/SplitMultipleEntitiesFiles.php new file mode 100644 index 000000000..b0190d959 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Upgrade/SplitMultipleEntitiesFiles.php @@ -0,0 +1,237 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\FunctionalTestingFramework\Upgrade; + +use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; +use Magento\FunctionalTestingFramework\Util\Script\ScriptUtil; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Finder\Finder; + +/** + * Class SplitMultipleEntitiesFiles + * @package Magento\FunctionalTestingFramework\Upgrade + */ +class SplitMultipleEntitiesFiles implements UpgradeInterface +{ + const XML_VERSION = '<?xml version="1.0" encoding="UTF-8"?>' . PHP_EOL; + const XML_COPYRIGHT = '<!--' . PHP_EOL + . ' /**' . PHP_EOL + . ' * Copyright © Magento, Inc. All rights reserved.' . PHP_EOL + . ' * See COPYING.txt for license details.' . PHP_EOL + . ' */' . PHP_EOL + . '-->' . PHP_EOL; + const XML_NAMESPACE = 'xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"' . PHP_EOL; + const XML_SCHEMA_LOCATION = "\t" . 'xsi:noNamespaceSchemaLocation="urn:magento:mftf:'; + + const FILENAME_BASE = 'base'; + const FILENAME_SUFFIX = 'type'; + + /** + * OutputInterface + * + * @var OutputInterface + */ + private $output; + + /** + * Total test updated + * + * @var integer + */ + private $testsUpdated = 0; + + /** + * Entity categories for the upgrade script + * + * @var array + */ + private $entityCategories = [ + 'Suite' => 'Suite/etc/suiteSchema.xsd', + 'Test' => 'Test/etc/testSchema.xsd', + 'ActionGroup' => 'Test/etc/actionGroupSchema.xsd', + 'Page' => 'Page/etc/PageObject.xsd', + 'Section' => 'Page/etc/SectionObject.xsd', + ]; + + /** + * Scan all xml files and split xml files that contains more than one entities + * for Test, Action Group, Page, Section, Suite types. + * + * @param InputInterface $input + * @param OutputInterface $output + * @return string + * @throws TestFrameworkException + */ + public function execute(InputInterface $input, OutputInterface $output) + { + $scriptUtil = new ScriptUtil(); + $this->output = $output; + $this->testsUpdated = 0; + $testPaths[] = $input->getArgument('path'); + if (empty($testPaths[0])) { + $testPaths = $scriptUtil->getAllModulePaths(); + } + + // Process module xml files + foreach ($this->entityCategories as $type => $urn) { + $xmlFiles = $scriptUtil->getModuleXmlFilesByScope($testPaths, $type); + $this->processXmlFiles($xmlFiles, $type, $urn); + } + + return ("Split multiple entities in {$this->testsUpdated} file(s)."); + } + + /** + * Split on list of xml files + * + * @param Finder $xmlFiles + * @param string $type + * @param string $urn + * @return void + */ + private function processXmlFiles($xmlFiles, $type, $urn) + { + foreach ($xmlFiles as $file) { + $contents = $file->getContents(); + $domDocument = new \DOMDocument(); + $domDocument->loadXML($contents); + $entities = $domDocument->getElementsByTagName(lcfirst($type)); + + if ($entities->length > 1) { + $filename = $file->getRealPath(); + if ($this->output->isVerbose()) { + $this->output->writeln('Processing file:' . $filename); + } + foreach ($entities as $entity) { + /** @var \DOMElement $entity */ + $entityName = $entity->getAttribute('name'); + $entityContent = $entity->ownerDocument->saveXML($entity); + + $dir = dirname($file); + $dir .= DIRECTORY_SEPARATOR . ucfirst(basename($file, '.xml')); + $splitFileName = $this->formatName($entityName, $type); + $this->filePutContents( + $dir . DIRECTORY_SEPARATOR . $splitFileName . '.xml', + $type, + $urn, + $entityContent + ); + if ($this->output->isVerbose()) { + $this->output->writeln( + 'Created file:' . $dir . DIRECTORY_SEPARATOR . $splitFileName . '.xml' + ); + } + $this->testsUpdated++; + } + unlink($file); + if ($this->output->isVerbose()) { + $this->output->writeln('Unlinked file:' . $filename . PHP_EOL); + } + } + } + } + + /** + * Create file with contents and create dir if needed + * + * @param string $fullPath + * @param string $type + * @param string $urn + * @param string $contents + * @return void + */ + private function filePutContents($fullPath, $type, $urn, $contents) + { + $dir = dirname($fullPath); + + if (!is_dir($dir)) { + mkdir($dir, 0777, true); + } + + // Make sure not overwriting an existing file + $fullPath = $this->getNonExistingFileFullPath($fullPath, $type); + + $fullContents = self::XML_VERSION + . self::XML_COPYRIGHT + . '<' . lcfirst($type) . 's ' + . self::XML_NAMESPACE + . self::XML_SCHEMA_LOCATION . $urn . '">' . PHP_EOL + . ' ' . $contents . PHP_EOL + . '</' . lcfirst($type) . 's>' . PHP_EOL; + + file_put_contents($fullPath, $fullContents); + } + + /** + * Format name to include type if it's Page, Section or Action Group + * + * @param string $name + * @param string $type + * @return string + */ + private function formatName($name, $type) + { + $name = ucfirst($name); + $type = ucfirst($type); + + if ($type !== 'Section' && $type !== 'Page' && $type !== 'ActionGroup') { + return $name; + } + + $parts = $this->getFileNameParts($name, $type); + if (empty($parts[self::FILENAME_SUFFIX])) { + $name .= $type; + } + return $name; + } + + /** + * Vary the input to return a non-existing file name + * + * @param string $fullPath + * @param string $type + * @return string + */ + private function getNonExistingFileFullPath($fullPath, $type) + { + $type = ucfirst($type); + $dir = dirname($fullPath); + $filename = basename($fullPath, '.xml'); + $i = 1; + $parts = []; + while (file_exists($fullPath)) { + if (empty($parts)) { + $parts = $this->getFileNameParts($filename, $type); + } + $basename = $parts[self::FILENAME_BASE] . strval(++$i); + $fullPath = $dir . DIRECTORY_SEPARATOR . $basename . $parts[self::FILENAME_SUFFIX] . '.xml'; + } + return $fullPath; + } + + /** + * Split filename into two parts and return it in an associate array with keys FILENAME_BASE and FILENAME_SUFFIX + * + * @param string $filename + * @param string $type + * @return array + */ + private function getFileNameParts($filename, $type) + { + $type = ucfirst($type); + $fileNameParts = []; + if (substr($filename, -strlen($type)) === $type) { + $fileNameParts[self::FILENAME_BASE] = substr($filename, 0, strlen($filename) - strlen($type)); + $fileNameParts[self::FILENAME_SUFFIX] = $type; + } else { + $fileNameParts[self::FILENAME_BASE] = $filename; + $fileNameParts[self::FILENAME_SUFFIX] = ''; + } + return $fileNameParts; + } +} diff --git a/src/Magento/FunctionalTestingFramework/Upgrade/UpdateAssertionSchema.php b/src/Magento/FunctionalTestingFramework/Upgrade/UpdateAssertionSchema.php new file mode 100644 index 000000000..3c6825092 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Upgrade/UpdateAssertionSchema.php @@ -0,0 +1,167 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\FunctionalTestingFramework\Upgrade; + +use Magento\FunctionalTestingFramework\Util\Script\ScriptUtil; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Filesystem\Filesystem; +use Symfony\Component\Finder\Finder; + +/** + * Class UpdateAssertionSchema + * @package Magento\FunctionalTestingFramework\Upgrade + */ +class UpdateAssertionSchema implements UpgradeInterface +{ + /** + * Upgrades all test xml files, changing as many <assert> actions to be nested as possible + * WILL NOT CATCH cases where style is a mix of old and new + * + * @param InputInterface $input + * @param OutputInterface $output + * @return string + */ + public function execute(InputInterface $input, OutputInterface $output) + { + $scriptUtil = new ScriptUtil(); + $testPaths[] = $input->getArgument('path'); + if (empty($testPaths[0])) { + $testPaths = $scriptUtil->getAllModulePaths(); + } + + $testsUpdated = 0; + foreach ($testPaths as $testsPath) { + $finder = new Finder(); + $finder->files()->in($testsPath)->name("*.xml"); + + $fileSystem = new Filesystem(); + foreach ($finder->files() as $file) { + $contents = $file->getContents(); + // Isolate <assert ... /> but never <assert> ... </assert>, stops after finding first /> + preg_match_all('/<assert.*\/>/', $contents, $potentialAssertions); + $newAssertions = []; + $index = 0; + if (empty($potentialAssertions[0])) { + continue; + } + foreach ($potentialAssertions[0] as $potentialAssertion) { + $newAssertions[$index] = $this->convertOldAssertionToNew($potentialAssertion); + $index++; + } + foreach ($newAssertions as $currentIndex => $replacements) { + $contents = str_replace($potentialAssertions[0][$currentIndex], $replacements, $contents); + } + $fileSystem->dumpFile($file->getRealPath(), $contents); + $testsUpdated++; + } + } + + return ("Assertion Syntax updated in {$testsUpdated} file(s)."); + } + + /** + * Takes given string and attempts to convert it from single line to multi-line + * + * @param string $assertion + * @return string + * @SuppressWarnings(PHPMD.CyclomaticComplexity) + * @SuppressWarnings(PHPMD.NPathComplexity) + */ + private function convertOldAssertionToNew($assertion) + { + // <assertSomething => assertSomething + $assertType = ltrim(explode(' ', $assertion)[0], '<'); + + // regex to all attribute=>value pairs + $allAttributes = "stepKey|actual|actualType|expected|expectedType|expectedValue|"; + $allAttributes .= "delta|message|selector|attribute|before|after|remove"; + $grabValueRegex = '/('. $allAttributes .')=(\'[^\']*\'|"[^"]*")/'; + + // Makes 3 arrays in $grabbedParts: + // 0 contains stepKey="value" + // 1 contains stepKey + // 2 contains value + $sortedParts = []; + preg_match_all($grabValueRegex, $assertion, $grabbedParts); + for ($i = 0; $i < count($grabbedParts[0]); $i++) { + $sortedParts[$grabbedParts[1][$i]] = $grabbedParts[2][$i]; + } + + // Begin trimming values and adding back into new string + $trimmedParts = []; + $newString = "<$assertType"; + $subElements = ["actual" => [], "expected" => []]; + foreach ($sortedParts as $type => $value) { + // If attribute="'value'", elseif attribute='"value"', new nested format will break if we leave these in + if (strpos($value, '"') === 0) { + $value = rtrim(ltrim($value, '"'), '"'); + } elseif (strpos($value, "'") === 0) { + $value = rtrim(ltrim($value, "'"), "'"); + } + // If value is empty string (" " or ' '), trim again to become empty + if (str_replace(" ", "", $value) === "''") { + $value = ""; + } elseif (str_replace(" ", "", $value) === '""') { + $value = ""; + } + + // Value is ready for storage/reapply + $trimmedParts[$type] = $value; + if (in_array($type, ["stepKey", "delta", "message", "before", "after", "remove"])) { + // Add back as attribute safely + $newString .= " $type=\"$value\""; + continue; + } + + // Store in subtype for child element creation + if ($type === "actual") { + $subElements["actual"]["value"] = $value; + } elseif ($type === "actualType") { + $subElements["actual"]["type"] = $value; + } elseif ($type === "expected" or $type === "expectedValue") { + $subElements["expected"]["value"] = $value; + } elseif ($type === "expectedType") { + $subElements["expected"]["type"] = $value; + } + } + $newString .= ">\n"; + + // Assert type is very edge-cased, completely different schema + if ($assertType === 'assertElementContainsAttribute') { + // assertElementContainsAttribute type defaulted to string if not present + if (!isset($subElements["expected"]['type'])) { + $subElements["expected"]['type'] = "string"; + } + $value = $subElements['expected']['value'] ?? ""; + $type = $subElements["expected"]['type']; + $selector = $trimmedParts['selector']; + $attribute = $trimmedParts['attribute']; + // @codingStandardsIgnoreStart + $newString .= "\t\t\t<expectedResult selector=\"$selector\" attribute=\"$attribute\" type=\"$type\">$value</expectedResult>\n"; + // @codingStandardsIgnoreEnd + } else { + // Set type to const if it's absent, old default + if (isset($subElements["actual"]['value']) && !isset($subElements["actual"]['type'])) { + $subElements["actual"]['type'] = "const"; + } + if (isset($subElements["expected"]['value']) && !isset($subElements["expected"]['type'])) { + $subElements["expected"]['type'] = "const"; + } + foreach ($subElements as $type => $subElement) { + if (empty($subElement)) { + continue; + } + $value = $subElement['value']; + $typeValue = $subElement['type']; + $newString .= "\t\t\t<{$type}Result type=\"$typeValue\">$value</{$type}Result>\n"; + } + } + $newString .= " </$assertType>"; + return $newString; + } +} diff --git a/src/Magento/FunctionalTestingFramework/Upgrade/UpdateTestSchemaPaths.php b/src/Magento/FunctionalTestingFramework/Upgrade/UpdateTestSchemaPaths.php index 8b79b6019..e6d3358d6 100644 --- a/src/Magento/FunctionalTestingFramework/Upgrade/UpdateTestSchemaPaths.php +++ b/src/Magento/FunctionalTestingFramework/Upgrade/UpdateTestSchemaPaths.php @@ -6,9 +6,11 @@ namespace Magento\FunctionalTestingFramework\Upgrade; +use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; +use Magento\FunctionalTestingFramework\Util\Script\ScriptUtil; use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Filesystem\Filesystem; use Symfony\Component\Finder\Finder; +use Symfony\Component\Console\Output\OutputInterface; /** * Class UpdateTestSchemaPaths @@ -16,58 +18,82 @@ */ class UpdateTestSchemaPaths implements UpgradeInterface { + /** + * OutputInterface + * + * @var OutputInterface + */ + private $output; + + /** + * Total test updated + * + * @var integer + */ + private $testsUpdated = 0; + + /** + * Entity type to urn map + * + * @var array + */ + private $typeToUrns = [ + 'ActionGroup' => 'urn:magento:mftf:Test/etc/actionGroupSchema.xsd', + 'Data' => 'urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd', + 'Metadata' => 'urn:magento:mftf:DataGenerator/etc/dataOperation.xsd', + 'Page' => 'urn:magento:mftf:Page/etc/PageObject.xsd', + 'Section' => 'urn:magento:mftf:Page/etc/SectionObject.xsd', + 'Suite' => 'urn:magento:mftf:Suite/etc/suiteSchema.xsd', + 'Test' => 'urn:magento:mftf:Test/etc/testSchema.xsd', + ]; + /** * Upgrades all test xml files, replacing relative schema paths to URN. * - * @param InputInterface $input + * @param InputInterface $input + * @param OutputInterface $output * @return string + * @throws TestFrameworkException */ - public function execute(InputInterface $input) + public function execute(InputInterface $input, OutputInterface $output) { - // @codingStandardsIgnoreStart - $relativeToUrn = [ - "dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataOperation.xsd" - => "urn:magento:mftf:DataGenerator/etc/dataOperation.xsd", - "dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd" - => "urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd", - "dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/PageObject.xsd" - => "urn:magento:mftf:Page/etc/PageObject.xsd", - "dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd" - => "urn:magento:mftf:Page/etc/SectionObject.xsd", - "dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd" - => "urn:magento:mftf:Test/etc/actionGroupSchema.xsd", - "dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd" - => "urn:magento:mftf:Test/etc/testSchema.xsd", - "dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Suite/etc/suiteSchema.xsd" - => "urn:magento:mftf:Suite/etc/suiteSchema.xsd" - ]; - // @codingStandardsIgnoreEnd + $scriptUtil = new ScriptUtil(); + $this->output = $output; + $this->testsUpdated = 0; + $testPaths[] = $input->getArgument('path'); + if (empty($testPaths[0])) { + $testPaths = $scriptUtil->getAllModulePaths(); + } - $relativePatterns = []; - $urns = []; - // Prepare array of patterns to URNs for preg_replace (replace / to escapes - foreach ($relativeToUrn as $relative => $urn) { - $relativeReplaced = str_replace('/', '\/', $relative); - $relativePatterns[] = '/[.\/]+' . $relativeReplaced . '/'; - $urns[] = $urn; + // Process module xml files + foreach ($this->typeToUrns as $type => $urn) { + $xmlFiles = $scriptUtil->getModuleXmlFilesByScope($testPaths, $type); + $this->processXmlFiles($xmlFiles, $urn); } - $testsPath = $input->getArgument('path'); - $finder = new Finder(); - $finder->files()->in($testsPath)->name("*.xml"); + return ("Schema Path updated to use MFTF URNs in {$this->testsUpdated} file(s)."); + } - $fileSystem = new Filesystem(); - $testsUpdated = 0; - foreach ($finder->files() as $file) { - $count = 0; + /** + * Convert xml schema location from non urn based to urn based + * + * @param Finder $xmlFiles + * @param string $urn + * @return void + */ + private function processXmlFiles($xmlFiles, $urn) + { + $pattern = '/xsi:noNamespaceSchemaLocation[\s]*=[\s]*"(?<urn>[^\<\>"\']*)"/'; + foreach ($xmlFiles as $file) { + $filePath = $file->getRealPath(); $contents = $file->getContents(); - $contents = preg_replace($relativePatterns, $urns, $contents, -1, $count); - $fileSystem->dumpFile($file->getRealPath(), $contents); - if ($count > 0) { - $testsUpdated++; + preg_match($pattern, $contents, $matches); + if (isset($matches['urn'])) { + if (trim($matches['urn']) !== $urn) { + file_put_contents($filePath, str_replace($matches['urn'], $urn, $contents)); + $this->testsUpdated++; + } } } - - return ("Schema Path updated to use MFTF URNs in {$testsUpdated} file(s)."); } } diff --git a/src/Magento/FunctionalTestingFramework/Upgrade/UpgradeInterface.php b/src/Magento/FunctionalTestingFramework/Upgrade/UpgradeInterface.php index b6905845c..150737bc2 100644 --- a/src/Magento/FunctionalTestingFramework/Upgrade/UpgradeInterface.php +++ b/src/Magento/FunctionalTestingFramework/Upgrade/UpgradeInterface.php @@ -7,6 +7,7 @@ namespace Magento\FunctionalTestingFramework\Upgrade; use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; /** * Upgrade script interface @@ -15,8 +16,9 @@ interface UpgradeInterface { /** * Executes upgrade script, returns output. - * @param InputInterface $input + * @param InputInterface $input + * @param OutputInterface $output * @return string */ - public function execute(InputInterface $input); + public function execute(InputInterface $input, OutputInterface $output); } diff --git a/src/Magento/FunctionalTestingFramework/Upgrade/UpgradeScriptList.php b/src/Magento/FunctionalTestingFramework/Upgrade/UpgradeScriptList.php index 245f95d82..cf2aabe2d 100644 --- a/src/Magento/FunctionalTestingFramework/Upgrade/UpgradeScriptList.php +++ b/src/Magento/FunctionalTestingFramework/Upgrade/UpgradeScriptList.php @@ -28,7 +28,12 @@ class UpgradeScriptList implements UpgradeScriptListInterface public function __construct(array $scripts = []) { $this->scripts = [ + 'removeUnusedArguments' => new RemoveUnusedArguments(), 'upgradeTestSchema' => new UpdateTestSchemaPaths(), + 'upgradeAssertionSchema' => new UpdateAssertionSchema(), + 'renameMetadataFiles' => new RenameMetadataFiles(), + 'removeModuleFileInSuiteFiles' => new RemoveModuleFileInSuiteFiles(), + 'splitMultipleEntitiesFiles' => new SplitMultipleEntitiesFiles(), ] + $scripts; } diff --git a/src/Magento/FunctionalTestingFramework/Util/ComposerModuleResolver.php b/src/Magento/FunctionalTestingFramework/Util/ComposerModuleResolver.php index 210c10fdd..92d5dc121 100644 --- a/src/Magento/FunctionalTestingFramework/Util/ComposerModuleResolver.php +++ b/src/Magento/FunctionalTestingFramework/Util/ComposerModuleResolver.php @@ -48,7 +48,7 @@ public function getComposerInstalledTestModules($rootComposerFile) return $this->installedTestModules; } - if (!file_exists($rootComposerFile) || basename($rootComposerFile, '.json') != 'composer') { + if (!file_exists($rootComposerFile) || basename($rootComposerFile, '.json') !== 'composer') { throw new TestFrameworkException("Invalid root composer json file: {$rootComposerFile}"); } @@ -168,7 +168,7 @@ private function findComposerJsonFilesAtDepth($directory, $depth) self::findComposerJsonFilesAtDepth($dir, $depth-1) ); } - } elseif ($depth == 0) { + } elseif ($depth === 0) { $jsonFileList = glob($directory . $jsonPattern); if ($jsonFileList === false) { $jsonFileList = []; diff --git a/src/Magento/FunctionalTestingFramework/Util/ConfigSanitizerUtil.php b/src/Magento/FunctionalTestingFramework/Util/ConfigSanitizerUtil.php index ce5740493..ee4c85018 100644 --- a/src/Magento/FunctionalTestingFramework/Util/ConfigSanitizerUtil.php +++ b/src/Magento/FunctionalTestingFramework/Util/ConfigSanitizerUtil.php @@ -7,6 +7,8 @@ namespace Magento\FunctionalTestingFramework\Util; use Magento\FunctionalTestingFramework\Config\MftfApplicationConfig; +use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; +use Magento\FunctionalTestingFramework\Util\Path\UrlFormatter; /** * Class ConfigSanitizerUtil @@ -24,7 +26,7 @@ public static function sanitizeWebDriverConfig($config, $params = ['url', 'selen self::validateConfigBasedVars($config); if (in_array('url', $params)) { - $config['url'] = self::sanitizeUrl($config['url']); + $config['url'] = UrlFormatter::format($config['url']); } if (in_array('selenium', $params)) { @@ -41,16 +43,16 @@ public static function sanitizeWebDriverConfig($config, $params = ['url', 'selen */ private static function sanitizeSeleniumEnvs($config) { - if ($config['protocol'] == '%SELENIUM_PROTOCOL%') { + if ($config['protocol'] === '%SELENIUM_PROTOCOL%') { $config['protocol'] = "http"; } - if ($config['host'] == '%SELENIUM_HOST%') { + if ($config['host'] === '%SELENIUM_HOST%') { $config['host'] = "127.0.0.1"; } - if ($config['port'] == '%SELENIUM_PORT%') { + if ($config['port'] === '%SELENIUM_PORT%') { $config['port'] = "4444"; } - if ($config['path'] == '%SELENIUM_PATH%') { + if ($config['path'] === '%SELENIUM_PATH%') { $config['path'] = "/wd/hub"; } return $config; @@ -80,71 +82,4 @@ private static function validateConfigBasedVars($config) } } } - - /** - * Sanitizes and returns given URL. - * @param string $url - * @return string - */ - public static function sanitizeUrl($url) - { - if (strlen($url) == 0 && !MftfApplicationConfig::getConfig()->forceGenerateEnabled()) { - trigger_error("MAGENTO_BASE_URL must be defined in .env", E_USER_ERROR); - } - - if (filter_var($url, FILTER_VALIDATE_URL) === true) { - return rtrim($url, "/") . "/"; - } - - $urlParts = parse_url($url); - - if (!isset($urlParts['scheme'])) { - $urlParts['scheme'] = "http"; - } - if (!isset($urlParts['host'])) { - $urlParts['host'] = rtrim($urlParts['path'], "/"); - $urlParts['host'] = str_replace("//", "/", $urlParts['host']); - unset($urlParts['path']); - } - - if (!isset($urlParts['path'])) { - $urlParts['path'] = "/"; - } else { - $urlParts['path'] = rtrim($urlParts['path'], "/") . "/"; - } - - return str_replace("///", "//", self::buildUrl($urlParts)); - } - - /** - * Returns url from $parts given, used with parse_url output for convenience. - * This only exists because of deprecation of http_build_url, which does the exact same thing as the code below. - * @param array $parts - * @return string - */ - private static function buildUrl(array $parts) - { - $get = function ($key) use ($parts) { - return isset($parts[$key]) ? $parts[$key] : null; - }; - - $pass = $get('pass'); - $user = $get('user'); - $userinfo = $pass !== null ? "$user:$pass" : $user; - $port = $get('port'); - $scheme = $get('scheme'); - $query = $get('query'); - $fragment = $get('fragment'); - $authority = - ($userinfo !== null ? "$userinfo@" : '') . - $get('host') . - ($port ? ":$port" : ''); - - return - (strlen($scheme) ? "$scheme:" : '') . - (strlen($authority) ? "//$authority" : '') . - $get('path') . - (strlen($query) ? "?$query" : '') . - (strlen($fragment) ? "#$fragment" : ''); - } } diff --git a/src/Magento/FunctionalTestingFramework/Util/DocGenerator.php b/src/Magento/FunctionalTestingFramework/Util/DocGenerator.php deleted file mode 100644 index 7a28f8969..000000000 --- a/src/Magento/FunctionalTestingFramework/Util/DocGenerator.php +++ /dev/null @@ -1,137 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ - -namespace Magento\FunctionalTestingFramework\Util; - -use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; -use Magento\FunctionalTestingFramework\Test\Handlers\ActionGroupObjectHandler; -use Magento\FunctionalTestingFramework\Test\Objects\ActionGroupObject; -use Magento\FunctionalTestingFramework\Test\Objects\TestObject; -use Magento\FunctionalTestingFramework\Test\Util\ActionGroupAnnotationExtractor; -use Magento\FunctionalTestingFramework\Test\Util\ActionGroupObjectExtractor; - -/** - * Class TestGenerator - */ -class DocGenerator -{ - const DEFAULT_OUTPUT_DIR = - PROJECT_ROOT . - DIRECTORY_SEPARATOR . - "dev" . - DIRECTORY_SEPARATOR . - "tests" . - DIRECTORY_SEPARATOR . - "docs"; - const DOC_NAME = "documentation.md"; - # This is the only place FILENAMES is defined as this string - const FILENAMES = "filenames"; - - /** - * DocGenerator constructor. - * - */ - public function __construct() - { - } - - /** - * This creates html documentation for objects passed in - * - * @param ActionGroupObject[]|TestObject[] $annotatedObjects - * @param string $outputDir - * @return void - * @throws TestFrameworkException - */ - public function createDocumentation($annotatedObjects, $outputDir, $clean) - { - if (empty($outputDir)) { - $fullPath = self::DEFAULT_OUTPUT_DIR . DIRECTORY_SEPARATOR; - } else { - $fullPath = $outputDir . DIRECTORY_SEPARATOR; - } - $filePath = $fullPath . self::DOC_NAME; - - if (!file_exists($fullPath)) { - mkdir($fullPath, 0755, true); - } - if (file_exists($filePath) and !$clean) { - throw new TestFrameworkException( - "$filePath already exists, please add --clean if you want to overwrite it." - ); - } - $pageGroups = []; - - foreach ($annotatedObjects as $name => $object) { - $annotations = $object->getAnnotations(); - $filenames = explode(',', $object->getFilename()); - $arguments = $object->getArguments(); - - $info = [ - actionGroupObject::ACTION_GROUP_DESCRIPTION => $annotations[actionGroupObject::ACTION_GROUP_DESCRIPTION] - ?? 'NO_DESCRIPTION_SPECIFIED', - self::FILENAMES => $filenames, - ActionGroupObjectExtractor::ACTION_GROUP_ARGUMENTS => $arguments - ]; - - $pageGroups = array_merge_recursive( - $pageGroups, - [$annotations[ActionGroupObject::ACTION_GROUP_PAGE] ?? 'NO_PAGE_SPECIFIED' => [$name => $info]] - ); - } - - ksort($pageGroups); - foreach ($pageGroups as $page => $groups) { - ksort($groups); - $pageGroups[$page] = $groups; - } - - $markdown = $this->transformToMarkdown($pageGroups); - - file_put_contents($filePath, $markdown); - } - - /** - * This creates html documentation for objects passed in - * - * @param array $annotationList - * @return string - */ - private function transformToMarkdown($annotationList) - { - $markdown = "#Action Group Information" . PHP_EOL; - $markdown .= "This documentation contains a list of all Action Groups." . - PHP_EOL . - PHP_EOL; - - $markdown .= "---" . PHP_EOL; - foreach ($annotationList as $group => $objects) { - foreach ($objects as $name => $annotations) { - $markdown .= "###$name" . PHP_EOL; - $markdown .= "**Description**:" . PHP_EOL; - $markdown .= "- " . $annotations[actionGroupObject::ACTION_GROUP_DESCRIPTION] . PHP_EOL . PHP_EOL; - if (!empty($annotations[ActionGroupObjectExtractor::ACTION_GROUP_ARGUMENTS])) { - $markdown .= "**Action Group Arguments**:" . PHP_EOL . PHP_EOL; - $markdown .= "| Name | Type |" . PHP_EOL; - $markdown .= "| ---- | ---- |" . PHP_EOL; - foreach ($annotations[ActionGroupObjectExtractor::ACTION_GROUP_ARGUMENTS] as $argument) { - $argumentName = $argument->getName(); - $argumentType = $argument->getDataType(); - $markdown .= "| $argumentName | $argumentType |" . PHP_EOL; - } - $markdown .= PHP_EOL; - } - $markdown .= "**Located In**:" . PHP_EOL; - foreach ($annotations[self::FILENAMES] as $filename) { - $relativeFilename = str_replace(MAGENTO_BP . DIRECTORY_SEPARATOR, "", $filename); - $markdown .= PHP_EOL . "- $relativeFilename"; - } - $markdown .= PHP_EOL . "***" . PHP_EOL; - } - } - return $markdown; - } -} diff --git a/src/Magento/FunctionalTestingFramework/Util/Env/EnvProcessor.php b/src/Magento/FunctionalTestingFramework/Util/Env/EnvProcessor.php index f09ab63fa..86f7fa89d 100644 --- a/src/Magento/FunctionalTestingFramework/Util/Env/EnvProcessor.php +++ b/src/Magento/FunctionalTestingFramework/Util/Env/EnvProcessor.php @@ -7,6 +7,9 @@ namespace Magento\FunctionalTestingFramework\Util\Env; +use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; +use Magento\FunctionalTestingFramework\Util\Path\FilePathFormatter; + /** * Helper class EnvProcessor for reading and writing .env files. * @@ -45,13 +48,14 @@ class EnvProcessor /** * EnvProcessor constructor. * @param string $envFile + * @throws TestFrameworkException */ public function __construct( string $envFile = '' ) { $this->envFile = $envFile; $this->envExists = file_exists($envFile); - $this->envExampleFile = realpath(FW_BP . "/etc/config/.env.example"); + $this->envExampleFile = realpath(FilePathFormatter::format(FW_BP) . "etc/config/.env.example"); } /** diff --git a/src/Magento/FunctionalTestingFramework/Util/Filesystem/CestFileCreatorUtil.php b/src/Magento/FunctionalTestingFramework/Util/Filesystem/CestFileCreatorUtil.php new file mode 100644 index 000000000..9fe205151 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Util/Filesystem/CestFileCreatorUtil.php @@ -0,0 +1,68 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\FunctionalTestingFramework\Util\Filesystem; + +use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; + +class CestFileCreatorUtil +{ + /** + * Singleton CestFileCreatorUtil Instance. + * + * @var CestFileCreatorUtil + */ + private static $INSTANCE; + + /** + * CestFileCreatorUtil constructor. + */ + private function __construct() + { + } + + /** + * Get CestFileCreatorUtil instance. + * + * @return CestFileCreatorUtil + */ + public static function getInstance(): CestFileCreatorUtil + { + if (!self::$INSTANCE) { + self::$INSTANCE = new CestFileCreatorUtil(); + } + + return self::$INSTANCE; + } + + /** + * Create a single PHP file containing the $cestPhp using the $filename. + * If the _generated directory doesn't exist it will be created. + * + * @param string $filename + * @param string $exportDirectory + * @param string $testPhp + * + * @return void + * @throws TestFrameworkException + */ + public function create(string $filename, string $exportDirectory, string $testPhp): void + { + DirSetupUtil::createGroupDir($exportDirectory); + $exportFilePath = $exportDirectory . DIRECTORY_SEPARATOR . $filename . '.php'; + $file = fopen($exportFilePath, 'w'); + + if (!$file) { + throw new TestFrameworkException( + sprintf('Could not open test file: "%s"', $exportFilePath) + ); + } + + fwrite($file, $testPhp); + fclose($file); + } +} diff --git a/src/Magento/FunctionalTestingFramework/Util/Filesystem/DirSetupUtil.php b/src/Magento/FunctionalTestingFramework/Util/Filesystem/DirSetupUtil.php index 9f0036629..120b74ac3 100644 --- a/src/Magento/FunctionalTestingFramework/Util/Filesystem/DirSetupUtil.php +++ b/src/Magento/FunctionalTestingFramework/Util/Filesystem/DirSetupUtil.php @@ -49,6 +49,10 @@ public static function createGroupDir($fullPath) */ public static function rmdirRecursive($directory) { + if (!is_dir($directory)) { + return; + } + $it = new RecursiveDirectoryIterator($directory, FilesystemIterator::SKIP_DOTS); while ($it->valid()) { diff --git a/src/Magento/FunctionalTestingFramework/Util/GenerationErrorHandler.php b/src/Magento/FunctionalTestingFramework/Util/GenerationErrorHandler.php new file mode 100644 index 000000000..a86cd459d --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Util/GenerationErrorHandler.php @@ -0,0 +1,176 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\FunctionalTestingFramework\Util; + +use Magento\FunctionalTestingFramework\Config\MftfApplicationConfig; +use PHP_CodeSniffer\Exceptions\RuntimeException; + +class GenerationErrorHandler +{ + /** + * Generation Error Handler Instance + * + * @var GenerationErrorHandler + */ + private static $instance; + + /** + * Collected errors + * + * @var array + */ + private $errors = []; + + /** + * Singleton method to return GenerationErrorHandler + * + * @return GenerationErrorHandler + */ + public static function getInstance() + { + if (!self::$instance) { + self::$instance = new GenerationErrorHandler(); + } + + return self::$instance; + } + + /** + * GenerationErrorHandler constructor + */ + private function __construct() + { + } + + /** + * Add a generation error into error handler + * + * @param string $type + * @param string $entityName + * @param string $message + * @param boolean $generated + * @return void + * @throws \Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException + */ + public function addError($type, $entityName, $message, $generated = false) + { + $error[$entityName] = [ + 'message' => $message, + 'generated' => $generated, + ]; + if (isset($this->errors[$type])) { + $this->errors[$type] = array_merge_recursive($this->errors[$type], $error); + } else { + $this->errors[$type] = $error; + } + } + + /** + * Return all errors + * + * @return array + */ + public function getAllErrors() + { + return $this->errors; + } + + /** + * Return all error message in a string + * + * @return string + */ + public function getAllErrorMessages() + { + $errMessages = ''; + + foreach ($this->errors as $type => $errors) { + foreach ($errors as $error) { + if (is_array($error['message'])) { + $errMessages .= (!empty($errMessages) ? PHP_EOL : '') . implode(PHP_EOL, $error['message']); + } else { + $errMessages .= (!empty($errMessages) ? PHP_EOL : '') . $error['message']; + } + } + } + + return $errMessages; + } + + /** + * Return errors for given type + * + * @param string $type + * @return array + */ + public function getErrorsByType($type) + { + return $this->errors[$type] ?? []; + } + + /** + * Reset error to empty array + * + * @return void + */ + public function reset() + { + $this->errors = []; + } + + /** + * Print error summary in console + * + * @return void + */ + public function printErrorSummary() + { + foreach (array_keys($this->errors) as $type) { + $totalErrors = count($this->getErrorsByType($type)); + $totalAnnotationErrors = 0; + foreach ($this->getErrorsByType($type) as $entity => $error) { + if ((is_array($error['generated']) && $error['generated'][0] === true) + || ($error['generated'] === true)) { + $totalAnnotationErrors++; + } + } + $totalNotGenErrors = $totalErrors - $totalAnnotationErrors; + if ($totalNotGenErrors > 0) { + print( + 'ERROR: ' + . strval($totalNotGenErrors) + . ' ' + . ucfirst($type) + . "(s) failed to generate. See mftf.log for details." + . PHP_EOL + ); + } + if ($totalAnnotationErrors > 0) { + if ($type !== 'suite') { + print( + 'ERROR: ' + . strval($totalAnnotationErrors) + . ' ' + . ucfirst($type) + . "(s) generated with annotation errors. See mftf.log for details." + . PHP_EOL + ); + } else { + print( + 'ERROR: ' + . strval($totalAnnotationErrors) + . ' ' + . ucfirst($type) + . '(s) has(have) tests with annotation errors or some included tests missing.' + . ' See mftf.log for details.' + . PHP_EOL + ); + } + } + } + print(PHP_EOL); + } +} diff --git a/src/Magento/FunctionalTestingFramework/Util/Iterator/AbstractIterator.php b/src/Magento/FunctionalTestingFramework/Util/Iterator/AbstractIterator.php index 69e2527aa..85b5dd411 100644 --- a/src/Magento/FunctionalTestingFramework/Util/Iterator/AbstractIterator.php +++ b/src/Magento/FunctionalTestingFramework/Util/Iterator/AbstractIterator.php @@ -33,6 +33,7 @@ abstract class AbstractIterator implements \Iterator, \Countable * * @return mixed */ + #[\ReturnTypeWillChange] abstract public function current(); // @codingStandardsIgnoreEnd @@ -48,14 +49,14 @@ abstract public function current(); * * @return boolean */ - abstract protected function isValid(); + abstract protected function isValid() : bool; /** * Initialize Data Array * * @return void */ - public function rewind() + public function rewind() : void { reset($this->data); if (!$this->isValid()) { @@ -68,7 +69,7 @@ public function rewind() * * @return void */ - public function next() + public function next() : void { $this->current = next($this->data); @@ -86,7 +87,7 @@ public function next() * * @return boolean */ - public function valid() + public function valid() : bool { $current = current($this->data); if ($current === false || $current === null) { @@ -96,22 +97,25 @@ public function valid() } } + // @codingStandardsIgnoreStart /** * Get data key of the current data element * * @return integer|string */ + #[\ReturnTypeWillChange] public function key() { return key($this->data); } + // @codingStandardsIgnoreEnd /** * To make iterator countable * * @return integer */ - public function count() + public function count() : int { return count($this->data); } diff --git a/src/Magento/FunctionalTestingFramework/Util/Iterator/File.php b/src/Magento/FunctionalTestingFramework/Util/Iterator/File.php index 0186acd11..aafc4e288 100644 --- a/src/Magento/FunctionalTestingFramework/Util/Iterator/File.php +++ b/src/Magento/FunctionalTestingFramework/Util/Iterator/File.php @@ -58,7 +58,7 @@ public function current() * * @return boolean */ - protected function isValid() + protected function isValid() : bool { return true; } diff --git a/src/Magento/FunctionalTestingFramework/Util/Logger/LoggingUtil.php b/src/Magento/FunctionalTestingFramework/Util/Logger/LoggingUtil.php index 7ac128f28..c32348bcc 100644 --- a/src/Magento/FunctionalTestingFramework/Util/Logger/LoggingUtil.php +++ b/src/Magento/FunctionalTestingFramework/Util/Logger/LoggingUtil.php @@ -6,8 +6,9 @@ namespace Magento\FunctionalTestingFramework\Util\Logger; +use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; +use Magento\FunctionalTestingFramework\Util\Path\FilePathFormatter; use Monolog\Handler\StreamHandler; -use Monolog\Logger; class LoggingUtil { @@ -61,12 +62,12 @@ private function __clone() * * @param string $className * @return MftfLogger - * @throws \Exception + * @throws TestFrameworkException */ public function getLogger($className): MftfLogger { - if ($className == null) { - throw new \Exception("You must pass a class name to receive a logger"); + if ($className === null) { + throw new TestFrameworkException("You must pass a class name to receive a logger"); } if (!array_key_exists($className, $this->loggers)) { @@ -82,9 +83,10 @@ public function getLogger($className): MftfLogger * Function which returns a static path to the the log file. * * @return string + * @throws TestFrameworkException */ public function getLoggingPath(): string { - return TESTS_BP . DIRECTORY_SEPARATOR . "mftf.log"; + return FilePathFormatter::format(TESTS_BP) . "mftf.log"; } } diff --git a/src/Magento/FunctionalTestingFramework/Util/Logger/MftfLogger.php b/src/Magento/FunctionalTestingFramework/Util/Logger/MftfLogger.php index 0a52f6b6b..880dd9736 100644 --- a/src/Magento/FunctionalTestingFramework/Util/Logger/MftfLogger.php +++ b/src/Magento/FunctionalTestingFramework/Util/Logger/MftfLogger.php @@ -7,42 +7,94 @@ namespace Magento\FunctionalTestingFramework\Util\Logger; use Magento\FunctionalTestingFramework\Config\MftfApplicationConfig; -use Monolog\Handler\StreamHandler; +use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; +use Monolog\Handler\HandlerInterface; use Monolog\Logger; class MftfLogger extends Logger { + /** + * MFTF execution phase + * + * @var string + */ + private $phase; + + /** + * MftfLogger constructor. + * + * @param string $name + * @param HandlerInterface[] $handlers + * @param callable[] $processors + * @throws TestFrameworkException + */ + public function __construct($name, array $handlers = [], array $processors = []) + { + parent::__construct($name, $handlers, $processors); + $this->phase = MftfApplicationConfig::getConfig()->getPhase(); + } + /** * Prints a deprecation warning, as well as adds a log at the WARNING level. + * Suppresses logging during execution phase. * - * @param string $message The log message. - * @param array $context The log context. + * @param string $message The log message. + * @param array $context The log context. + * @param boolean $verbose * @return void */ - public function deprecation($message, array $context = []) + public function deprecation($message, array $context = [], $verbose = false) { $message = "DEPRECATION: " . $message; - // Suppress print during unit testing - if (MftfApplicationConfig::getConfig()->getPhase() !== MftfApplicationConfig::UNIT_TEST_PHASE) { + // print during test generation including metadata + if ((array_key_exists('operationType', $context) || + $this->phase === MftfApplicationConfig::GENERATION_PHASE) && $verbose) { print ($message . json_encode($context) . "\n"); } - parent::warning($message, $context); + // suppress logging during test execution except metadata + if (array_key_exists('operationType', $context) || + $this->phase !== MftfApplicationConfig::EXECUTION_PHASE) { + parent::warning($message, $context); + } } /** * Prints a critical failure, as well as adds a log at the CRITICAL level. * - * @param string $message The log message. - * @param array $context The log context. + * @param string $message The log message. + * @param array $context The log context. + * @param boolean $verbose * @return void */ - public function criticalFailure($message, array $context = []) + public function criticalFailure($message, array $context = [], $verbose = false) { $message = "FAILURE: " . $message; // Suppress print during unit testing - if (MftfApplicationConfig::getConfig()->getPhase() !== MftfApplicationConfig::UNIT_TEST_PHASE) { + if ($this->phase !== MftfApplicationConfig::UNIT_TEST_PHASE && $verbose) { print ($message . implode("\n", $context) . "\n"); } parent::critical($message, $context); } + + /** + * Adds a log record at the NOTICE level. + * Suppresses logging during execution phase. + * + * @param string $message + * @param array $context + * @param boolean $verbose + * @return void + */ + public function notification($message, array $context = [], $verbose = false) + { + $message = "NOTICE: " . $message; + // print during test generation + if ($this->phase === MftfApplicationConfig::GENERATION_PHASE && $verbose) { + print ($message . json_encode($context) . "\n"); + } + // suppress logging during test execution + if ($this->phase !== MftfApplicationConfig::EXECUTION_PHASE) { + parent::notice($message, $context); + } + } } diff --git a/src/Magento/FunctionalTestingFramework/Util/Manifest/ParallelTestManifest.php b/src/Magento/FunctionalTestingFramework/Util/Manifest/BaseParallelTestManifest.php similarity index 70% rename from src/Magento/FunctionalTestingFramework/Util/Manifest/ParallelTestManifest.php rename to src/Magento/FunctionalTestingFramework/Util/Manifest/BaseParallelTestManifest.php index 9b12cfd00..1e9e1d109 100644 --- a/src/Magento/FunctionalTestingFramework/Util/Manifest/ParallelTestManifest.php +++ b/src/Magento/FunctionalTestingFramework/Util/Manifest/BaseParallelTestManifest.php @@ -6,60 +6,59 @@ namespace Magento\FunctionalTestingFramework\Util\Manifest; -use Codeception\Suite; use Magento\Framework\Exception\RuntimeException; -use Magento\FunctionalTestingFramework\Suite\Handlers\SuiteObjectHandler; -use Magento\FunctionalTestingFramework\Suite\Objects\SuiteObject; -use Magento\FunctionalTestingFramework\Test\Handlers\TestObjectHandler; use Magento\FunctionalTestingFramework\Test\Objects\TestObject; use Magento\FunctionalTestingFramework\Util\Filesystem\DirSetupUtil; use Magento\FunctionalTestingFramework\Util\Sorter\ParallelGroupSorter; -use RecursiveArrayIterator; -use RecursiveIteratorIterator; -class ParallelTestManifest extends BaseTestManifest +abstract class BaseParallelTestManifest extends BaseTestManifest { - const PARALLEL_CONFIG = 'parallel'; - /** * An associate array of test name to size of test. * * @var string[] */ - private $testNameToSize = []; + protected $testNameToSize = []; /** * Class variable to store resulting group config. * * @var array */ - private $testGroups; + protected $testGroups; /** * An instance of the group sorter which will take suites and tests organizing them to be run together. * * @var ParallelGroupSorter */ - private $parallelGroupSorter; + protected $parallelGroupSorter; /** * Path to the directory that will contain all test group files * * @var string */ - private $dirPath; + protected $dirPath; + + /** + * An array of test name count in a single group + * @var array + */ + protected $testCountsToGroup = []; /** - * TestManifest constructor. + * BaseParallelTestManifest constructor. * * @param array $suiteConfiguration + * @param string $runConfig * @param string $testPath */ - public function __construct($suiteConfiguration, $testPath) + public function __construct($suiteConfiguration, $runConfig, $testPath) { $this->dirPath = dirname($testPath) . DIRECTORY_SEPARATOR . 'groups'; $this->parallelGroupSorter = new ParallelGroupSorter(); - parent::__construct($testPath, self::PARALLEL_CONFIG, $suiteConfiguration); + parent::__construct($testPath, $runConfig, $suiteConfiguration); } /** @@ -74,22 +73,12 @@ public function addTest($testObject) } /** - * Function which generates test groups based on arg passed. The function builds groups using the args as an upper - * limit. + * Function which generates test groups based on arg passed. * - * @param integer $time + * @param integer $number * @return void */ - public function createTestGroups($time) - { - $this->testGroups = $this->parallelGroupSorter->getTestsGroupedBySize( - $this->getSuiteConfig(), - $this->testNameToSize, - $time - ); - - $this->suiteConfiguration = $this->parallelGroupSorter->getResultingSuiteConfig(); - } + abstract public function createTestGroups($number); /** * Function which generates the actual manifest once the relevant tests have been added to the array. @@ -104,6 +93,8 @@ public function generate() foreach ($this->testGroups as $groupNumber => $groupContents) { $this->generateGroupFile($groupContents, $groupNumber, $suites); } + + $this->generateGroupSummaryFile($this->testCountsToGroup); } /** @@ -126,22 +117,40 @@ public function getSorter() * @param array $suites * @return void */ - private function generateGroupFile($testGroup, $nodeNumber, $suites) + protected function generateGroupFile($testGroup, $nodeNumber, $suites) { foreach ($testGroup as $entryName => $testValue) { $fileResource = fopen($this->dirPath . DIRECTORY_SEPARATOR . "group{$nodeNumber}.txt", 'a'); - $line = null; - if (array_key_exists($entryName, $suites)) { + $this->testCountsToGroup["group{$nodeNumber}"] = $this->testCountsToGroup["group{$nodeNumber}"] ?? 0; + + if (!empty($suites[$entryName])) { $line = "-g {$entryName}"; + $this->testCountsToGroup["group{$nodeNumber}"] += count($suites[$entryName]); } else { $line = $this->relativeDirPath . DIRECTORY_SEPARATOR . $entryName . '.php'; + $this->testCountsToGroup["group{$nodeNumber}"]++; } fwrite($fileResource, $line . PHP_EOL); fclose($fileResource); } } + /** + * @param array $groups + * @return void + */ + protected function generateGroupSummaryFile(array $groups) + { + $fileResource = fopen($this->dirPath . DIRECTORY_SEPARATOR . "mftf_group_summary.txt", 'w'); + $contents = "Total Number of Groups: " . count($groups) . PHP_EOL; + foreach ($groups as $key => $value) { + $contents .= $key . " - ". $value . " tests" .PHP_EOL; + } + fwrite($fileResource, $contents); + fclose($fileResource); + } + /** * Function which recusrively parses a given potentially multidimensional array of suites containing their split * groups. The result is a flattened array of suite names to relevant tests for generation of the manifest. @@ -149,7 +158,7 @@ private function generateGroupFile($testGroup, $nodeNumber, $suites) * @param array $multiDimensionalSuites * @return array */ - private function getFlattenedSuiteConfiguration($multiDimensionalSuites) + protected function getFlattenedSuiteConfiguration($multiDimensionalSuites) { $suites = []; foreach ($multiDimensionalSuites as $suiteName => $suiteContent) { diff --git a/src/Magento/FunctionalTestingFramework/Util/Manifest/DefaultTestManifest.php b/src/Magento/FunctionalTestingFramework/Util/Manifest/DefaultTestManifest.php index eb4f79db2..4cbef3168 100644 --- a/src/Magento/FunctionalTestingFramework/Util/Manifest/DefaultTestManifest.php +++ b/src/Magento/FunctionalTestingFramework/Util/Manifest/DefaultTestManifest.php @@ -86,6 +86,9 @@ public function generate() protected function generateSuiteEntries($fileResource) { foreach ($this->getSuiteConfig() as $suiteName => $tests) { + if (count($tests) === 0) { + continue; + } $line = "-g {$suiteName}"; fwrite($fileResource, $line . PHP_EOL); } diff --git a/src/Magento/FunctionalTestingFramework/Util/Manifest/ParallelByGroupTestManifest.php b/src/Magento/FunctionalTestingFramework/Util/Manifest/ParallelByGroupTestManifest.php new file mode 100644 index 000000000..1baced519 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Util/Manifest/ParallelByGroupTestManifest.php @@ -0,0 +1,47 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\FunctionalTestingFramework\Util\Manifest; + +use Magento\Framework\Exception\RuntimeException; +use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; +use Magento\FunctionalTestingFramework\Test\Objects\TestObject; +use Magento\FunctionalTestingFramework\Util\Filesystem\DirSetupUtil; +use Magento\FunctionalTestingFramework\Util\Sorter\ParallelGroupSorter; + +class ParallelByGroupTestManifest extends BaseParallelTestManifest +{ + const PARALLEL_CONFIG = 'parallelByGroup'; + + /** + * ParallelByGroupTestManifest constructor. + * + * @param array $suiteConfiguration + * @param string $testPath + */ + public function __construct($suiteConfiguration, $testPath) + { + parent::__construct($suiteConfiguration, self::PARALLEL_CONFIG, $testPath); + } + + /** + * Function which generates test groups based on arg passed. + * + * @param integer $totalGroups + * @return void + * @throws TestFrameworkException + */ + public function createTestGroups($totalGroups) + { + $this->testGroups = $this->parallelGroupSorter->getTestsGroupedByFixedGroupCount( + $this->getSuiteConfig(), + $this->testNameToSize, + $totalGroups + ); + + $this->suiteConfiguration = $this->parallelGroupSorter->getResultingSuiteConfig(); + } +} diff --git a/src/Magento/FunctionalTestingFramework/Util/Manifest/ParallelByTimeTestManifest.php b/src/Magento/FunctionalTestingFramework/Util/Manifest/ParallelByTimeTestManifest.php new file mode 100644 index 000000000..2a0984280 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Util/Manifest/ParallelByTimeTestManifest.php @@ -0,0 +1,52 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\FunctionalTestingFramework\Util\Manifest; + +use Codeception\Suite; +use Magento\Framework\Exception\RuntimeException; +use Magento\FunctionalTestingFramework\Suite\Handlers\SuiteObjectHandler; +use Magento\FunctionalTestingFramework\Suite\Objects\SuiteObject; +use Magento\FunctionalTestingFramework\Test\Handlers\TestObjectHandler; +use Magento\FunctionalTestingFramework\Test\Objects\TestObject; +use Magento\FunctionalTestingFramework\Util\Filesystem\DirSetupUtil; +use Magento\FunctionalTestingFramework\Util\Sorter\ParallelGroupSorter; +use RecursiveArrayIterator; +use RecursiveIteratorIterator; + +class ParallelByTimeTestManifest extends BaseParallelTestManifest +{ + const PARALLEL_CONFIG = 'parallelByTime'; + + /** + * GroupBasedParallelTestManifest constructor. + * + * @param array $suiteConfiguration + * @param string $testPath + */ + public function __construct($suiteConfiguration, $testPath) + { + parent::__construct($suiteConfiguration, self::PARALLEL_CONFIG, $testPath); + } + + /** + * Function which generates test groups based on arg passed. The function builds groups using the args as an upper + * limit. + * + * @param integer $time + * @return void + */ + public function createTestGroups($time) + { + $this->testGroups = $this->parallelGroupSorter->getTestsGroupedBySize( + $this->getSuiteConfig(), + $this->testNameToSize, + $time + ); + + $this->suiteConfiguration = $this->parallelGroupSorter->getResultingSuiteConfig(); + } +} diff --git a/src/Magento/FunctionalTestingFramework/Util/Manifest/TestManifestFactory.php b/src/Magento/FunctionalTestingFramework/Util/Manifest/TestManifestFactory.php index 1cfa6559e..e07007264 100644 --- a/src/Magento/FunctionalTestingFramework/Util/Manifest/TestManifestFactory.php +++ b/src/Magento/FunctionalTestingFramework/Util/Manifest/TestManifestFactory.php @@ -6,7 +6,9 @@ namespace Magento\FunctionalTestingFramework\Util\Manifest; +use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; use Magento\FunctionalTestingFramework\Test\Handlers\TestObjectHandler; +use Magento\FunctionalTestingFramework\Util\Path\FilePathFormatter; use Magento\FunctionalTestingFramework\Util\TestGenerator; class TestManifestFactory @@ -26,11 +28,11 @@ private function __construct() * @param array $suiteConfiguration * @param string $testPath * @return BaseTestManifest + * @throws TestFrameworkException */ public static function makeManifest($runConfig, $suiteConfiguration, $testPath = TestGenerator::DEFAULT_DIR) { - $testDirFullPath = TESTS_MODULE_PATH - . DIRECTORY_SEPARATOR + $testDirFullPath = FilePathFormatter::format(TESTS_MODULE_PATH) . TestGenerator::GENERATED_DIR . DIRECTORY_SEPARATOR . $testPath; @@ -39,8 +41,11 @@ public static function makeManifest($runConfig, $suiteConfiguration, $testPath = case 'singleRun': return new SingleRunTestManifest($suiteConfiguration, $testDirFullPath); - case 'parallel': - return new ParallelTestManifest($suiteConfiguration, $testDirFullPath); + case 'parallelByTime': + return new ParallelByTimeTestManifest($suiteConfiguration, $testDirFullPath); + + case 'parallelByGroup': + return new ParallelByGroupTestManifest($suiteConfiguration, $testDirFullPath); default: return new DefaultTestManifest($suiteConfiguration, $testDirFullPath); diff --git a/src/Magento/FunctionalTestingFramework/Util/MetadataGenerator/FormData/MetadataGenUtil.php b/src/Magento/FunctionalTestingFramework/Util/MetadataGenerator/FormData/MetadataGenUtil.php deleted file mode 100644 index 2cd3f7011..000000000 --- a/src/Magento/FunctionalTestingFramework/Util/MetadataGenerator/FormData/MetadataGenUtil.php +++ /dev/null @@ -1,170 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ - -namespace Magento\FunctionalTestingFramework\Util\MetadataGenerator\FormData; - -use Mustache_Engine; -use Mustache_Loader_FilesystemLoader; - -class MetadataGenUtil -{ - const OUTPUT_DIR = '_output'; - const INPUT_TXT_FILE = 'input.yml'; - - /** - * Mustache Engine instance for the templating. - * - * @var Mustache_Engine - */ - private $mustacheEngine; - - /** - * Name of the operation (e.g. createCategory) - * - * @var string - */ - private $operationName; - - /** - * Data type of the operation (e.g. category) - * - * @var string - */ - private $operationDataType; - - /** - * Url path for the operation (e.g. /admin/system_config/save/section/payment) - * - * @var string - */ - private $operationUrl; - - /** - * The raw parameter data to be converted into metadata - * (e.g. entity[param1]=value1&entity[param2]=value2&entity[param3]=value3&entityField=field1) - * - * @var string - */ - private $inputString; - - /** - * The relative filepath for the *meta.xml file to be generated. - * - * @var string - */ - private $filepath; - - /** - * MetadataGenUtil constructor. - * - * @param string $operationName - * @param string $operationDataType - * @param string $operationUrl - * @param string $inputString - */ - public function __construct($operationName, $operationDataType, $operationUrl, $inputString) - { - $this->operationName = $operationName; - $this->operationDataType = $operationDataType; - $this->operationUrl = $operationUrl; - $this->inputString = $inputString; - - $this->filepath = self::OUTPUT_DIR . DIRECTORY_SEPARATOR . $this->operationDataType . "-meta.xml"; - } - - /** - * Function which takes params from constructor, transforms into data array and outputs a representative metadata - * file for MFTF to consume and send requests. - * - * @return void - */ - public function generateMetadataFile() - { - // Load Mustache templates - $this->mustacheEngine = new Mustache_Engine( - ['loader' => new Mustache_Loader_FilesystemLoader("views"), - 'partials_loader' => new Mustache_Loader_FilesystemLoader( - "views" . DIRECTORY_SEPARATOR . "partials" - )] - ); - - // parse the string params into an array - parse_str($this->inputString, $results); - $data = $this->convertResultToEntry($results, $this->operationDataType); - $data = $this->appendParentParams($data); - $output = $this->mustacheEngine->render('operation', $data); - $this->cleanAndCreateOutputDir(); - file_put_contents( - $this->filepath, - $output - ); - } - - /** - * Function which takes the top level params from the user and returns an array appended with the needed config. - * - * @param array $data - * @return array - */ - private function appendParentParams($data) - { - $result = $data; - $result['operationName'] = $this->operationName; - $result['operationDataType'] = $this->operationDataType; - $result['operationUrl'] = $this->operationUrl; - - return $result; - } - - /** - * Function which is called recursively to generate the mustache array for the template enging. Makes decisions - * about type and format based on parameter array. - * - * @param array $results - * @param string $defaultDataType - * @return array - */ - private function convertResultToEntry($results, $defaultDataType) - { - $data = []; - - foreach ($results as $key => $result) { - $entry = []; - if (is_array($result)) { - $entry = array_merge($entry, ['objectName' => $key]); - $res = $this->convertResultToEntry($result, $defaultDataType); - if (!array_key_exists('objects', $res)) { - $entry = array_merge($entry, ['objects' => null]); - $entry = array_merge($entry, ['dataType' => $key]); - } else { - $entry = array_merge($entry, ['hasChildObj' => true]); - $entry = array_merge($entry, ['dataType' => $defaultDataType]); - } - $data['objects'][] = array_merge($entry, $res); - } else { - $data['fields'][] = ['fieldName' => $key]; - } - } - - return $data; - } - - /** - * Function which cleans any previously created fileand creates the _output dir. - * - * @return void - */ - private function cleanAndCreateOutputDir() - { - if (!file_exists(self::OUTPUT_DIR)) { - mkdir(self::OUTPUT_DIR); - } - - if (file_exists($this->filepath)) { - unlink($this->filepath); - } - } -} diff --git a/src/Magento/FunctionalTestingFramework/Util/MetadataGenerator/FormData/_generateMetadataFile.php b/src/Magento/FunctionalTestingFramework/Util/MetadataGenerator/FormData/_generateMetadataFile.php deleted file mode 100644 index fce54be41..000000000 --- a/src/Magento/FunctionalTestingFramework/Util/MetadataGenerator/FormData/_generateMetadataFile.php +++ /dev/null @@ -1,23 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ - -require_once '../../../../../../vendor/autoload.php'; - -const INPUT_TXT_FILE = 'input.yml'; - -// parse the input.yml file for context -$inputCfg = \Symfony\Component\Yaml\Yaml::parse(file_get_contents(INPUT_TXT_FILE)); - -// create new MetadataGenUtil Object -$metadataGenUtil = new Magento\FunctionalTestingFramework\Util\MetadataGenerator\FormData\MetadataGenUtil( - $inputCfg['operationName'], - $inputCfg['operationDataType'], - $inputCfg['operationUrl'], - $inputCfg['inputString'] -); - -//generate the metadata file in the _output dir -$metadataGenUtil->generateMetadataFile(); diff --git a/src/Magento/FunctionalTestingFramework/Util/MetadataGenerator/FormData/input.yml.sample b/src/Magento/FunctionalTestingFramework/Util/MetadataGenerator/FormData/input.yml.sample deleted file mode 100644 index 503aef067..000000000 --- a/src/Magento/FunctionalTestingFramework/Util/MetadataGenerator/FormData/input.yml.sample +++ /dev/null @@ -1,4 +0,0 @@ -operationName: createMyEntity -operationDataType: myEntityType -operationUrl: /admin/system_config/save/someEntity -inputString: entity[param1]=value1&entity[param2]=value2&entity[param3]=value3&entityField=field1 diff --git a/src/Magento/FunctionalTestingFramework/Util/MetadataGenerator/FormData/views/operation.mustache b/src/Magento/FunctionalTestingFramework/Util/MetadataGenerator/FormData/views/operation.mustache deleted file mode 100644 index 28321504d..000000000 --- a/src/Magento/FunctionalTestingFramework/Util/MetadataGenerator/FormData/views/operation.mustache +++ /dev/null @@ -1,13 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ ---> - -<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../DataGenerator/etc/dataOperation.xsd"> - <operation name="{{operationName}}" dataType="{{operationDataType}}" type="create" auth="adminFormKey" url="{{operationUrl}}" method="POST"> - {{>object}} - </operation> -</config> diff --git a/src/Magento/FunctionalTestingFramework/Util/MetadataGenerator/FormData/views/partials/field.mustache b/src/Magento/FunctionalTestingFramework/Util/MetadataGenerator/FormData/views/partials/field.mustache deleted file mode 100644 index 284835ef2..000000000 --- a/src/Magento/FunctionalTestingFramework/Util/MetadataGenerator/FormData/views/partials/field.mustache +++ /dev/null @@ -1 +0,0 @@ -<field key="{{fieldName}}">string</field> diff --git a/src/Magento/FunctionalTestingFramework/Util/MetadataGenerator/FormData/views/partials/object.mustache b/src/Magento/FunctionalTestingFramework/Util/MetadataGenerator/FormData/views/partials/object.mustache deleted file mode 100644 index 7d3aaf5ca..000000000 --- a/src/Magento/FunctionalTestingFramework/Util/MetadataGenerator/FormData/views/partials/object.mustache +++ /dev/null @@ -1,10 +0,0 @@ -{{#objects}} -<object key="{{objectName}}" dataType="{{dataType}}"> -{{#fields}} - {{> field}} -{{/fields}} -{{#hasChildObj}} - {{> object}} -{{/hasChildObj}} -</object> -{{/objects}} diff --git a/src/Magento/FunctionalTestingFramework/Util/MetadataGenerator/Swagger/MetadataGenerator.php b/src/Magento/FunctionalTestingFramework/Util/MetadataGenerator/Swagger/MetadataGenerator.php deleted file mode 100644 index 10d75bf8e..000000000 --- a/src/Magento/FunctionalTestingFramework/Util/MetadataGenerator/Swagger/MetadataGenerator.php +++ /dev/null @@ -1,533 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -namespace Magento\FunctionalTestingFramework\Util\MetadataGenerator\Swagger; - -use Doctrine\Common\Collections\ArrayCollection; -use Epfremme\Swagger\Entity\Schemas\SchemaInterface; -use Epfremme\Swagger\Entity\Schemas\ObjectSchema; -use Epfremme\Swagger\Entity\Schemas\RefSchema; -use Epfremme\Swagger\Entity\Schemas\ArraySchema; -use Epfremme\Swagger\Factory\SwaggerFactory; -use Epfremme\Swagger\Entity\Swagger; -use Epfremme\Swagger\Entity\Operation; -use Epfremme\Swagger\Entity\Parameters\BodyParameter; -use Epfremme\Swagger\Entity\Parameters\AbstractTypedParameter; -use Mustache_Engine; -use Mustache_Loader_FilesystemLoader; - -class MetadataGenerator -{ - const OUTPUT_DIR = '_operations'; - const OUTPUT_DIR2 = '_definitions'; - const INPUT_TXT_FILE = 'magento.json'; - const AUTH = 'adminOauth'; - const TEMPLATE_VAR_DEF_TYPE = 'create'; - - const TEMPLATE_VAR_OP_NAME = 'operationName'; - const TEMPLATE_VAR_OP_DATATYPE = 'operationDataType'; - const TEMPLATE_VAR_OP_TYPE = 'operationType'; - const TEMPLATE_VAR_OP_AUTH = 'auth'; - const TEMPLATE_VAR_OP_URL = 'operationUrl'; - const TEMPLATE_VAR_OP_METHOD = 'method'; - - const TEMPLATE_VAR_OP_FIELD = 'fields'; - const TEMPLATE_VAR_FIELD_NAME = 'fieldName'; - const TEMPLATE_VAR_FIELD_TYPE = 'fieldType'; - const TEMPLATE_VAR_FIELD_IS_REQUIRED = 'isRequired'; - - const TEMPLATE_VAR_OP_PARAM = 'params'; - const TEMPLATE_VAR_PARAM_NAME = 'paramName'; - const TEMPLATE_VAR_PARAM_TYPE = 'paramType'; - - const TEMPLATE_VAR_OP_ARRAY = 'arrays'; - const TEMPLATE_VAR_ARRAY_KEY = 'arrayKey'; - const TEMPLATE_VAR_ARRAY_IS_REQUIRED = 'isRequiredArray'; - const TEMPLATE_VAR_VALUES = 'values'; - const TEMPLATE_VAR_VALUE = 'value'; - - const REF_REGEX = "~#/definitions/([\S]+)~"; - - /** - * Mustache Engine instance for the templating. - * - * @var Mustache_Engine - */ - private $mustache_engine; - - /** - * Swagger built from json. - * - * @var Swagger - */ - private static $swagger; - - /** - * Path params. - * - * @var string - */ - private $pathParams; - - /** - * Array to hold operation query params. - * - * @var array - */ - private $params; - - /** - * Array to hold operation fields. - * - * @var array - */ - private $fields; - - /** - * The relative filepath for the *meta.xml file to be generated. - * - * @var string - */ - private $filepath; - - /** - * Operation method mapping. - * - * @var array - */ - private static $methodMapping = [ - 'POST' => 'create', - 'DELETE' => 'delete', - 'PUT' => 'update', - 'GET' => 'get', - ]; - - /** - * Build and initialize generator. - */ - public function __construct() - { - self::buildSwaggerSpec(); - $this->initMustacheTemplates(); - } - - /** - * Parse swagger spec from input json file. - * TODO: read swagger spec from magento server. - * - * @return void - */ - public function generateMetadataFromSwagger() - { - $paths = self::$swagger->getPaths(); - - foreach ($paths->getIterator() as $pathKey => $path) { - $operations = $path->getOperations(); - foreach ($operations->getIterator() as $operationKey => $operation) { - $this->renderOperation($operation, $pathKey, $operationKey); - } - } - - $definitions = self::$swagger->getDefinitions(); - foreach ($definitions->getIterator() as $defKey => $definition) { - $this->renderDefinition($defKey, $definition); - } - } - - /** - * Render swagger operations. - * - * @param Operation $operation - * @param string $path - * @param string $method - * @return void - */ - private function renderOperation($operation, $path, $method) - { - $operationArray = []; - $this->pathParams = ''; - $this->params = []; - $this->fields = []; - $operationMethod = strtoupper($method); - $operationDataType = ucfirst($operation->getOperationId()); - - $operationArray[self::TEMPLATE_VAR_OP_NAME] = self::$methodMapping[$operationMethod] . $operationDataType; - $operationArray[self::TEMPLATE_VAR_OP_DATATYPE] = $operationDataType; - $operationArray[self::TEMPLATE_VAR_OP_METHOD] = $operationMethod; - $operationArray[self::TEMPLATE_VAR_OP_AUTH] = self::AUTH; - $operationArray[self::TEMPLATE_VAR_OP_TYPE] = self::$methodMapping[$operationMethod]; - $operationArray[self::TEMPLATE_VAR_OP_URL] = $path; - - $params = $operation->getParameters(); - if (!empty($params)) { - $this->parseParams($params, $path); - $operationArray[self::TEMPLATE_VAR_OP_FIELD] = $this->fields; - $operationArray[self::TEMPLATE_VAR_OP_PARAM] = $this->params; - } - - if (!empty($this->pathParams)) { - $operationArray[self::TEMPLATE_VAR_OP_URL] .= $this->pathParams; - } - - $this->generateMetaDataFile( - self::OUTPUT_DIR, - $operationDataType, - 'operation', - $operationArray - ); - } - - /** - * Render swagger definitions. - * - * @param string $defKey - * @param ObjectSchema|ArraySchema $definition - * @return void - */ - private function renderDefinition($defKey, $definition) - { - $operationArray = []; - $this->fields = []; - - $operationArray[self::TEMPLATE_VAR_OP_NAME] = $defKey; - $operationArray[self::TEMPLATE_VAR_OP_DATATYPE] = $defKey; - $operationArray[self::TEMPLATE_VAR_OP_TYPE] = self::TEMPLATE_VAR_DEF_TYPE; - - if ($definition instanceof ObjectSchema) { - $properties = $definition->getProperties(); - if (!empty($properties)) { - $dataField = []; - $dataArray = []; - foreach ($properties->getIterator() as $propertyKey => $property) { - if ($property instanceof ArraySchema) { - $dataArray[] = $this->parseSchema($property, $propertyKey, 1, 1); - } else { - $dataField[] = $this->parseSchema($property, $propertyKey, 0, 1); - } - } - if (!empty($dataField)) { - $operationArray[self::TEMPLATE_VAR_OP_FIELD] = $dataField; - } - if (!empty($dataArray)) { - foreach ($dataArray as $array) { - $operationArray[self::TEMPLATE_VAR_OP_ARRAY.'1'][] = $array[self::TEMPLATE_VAR_OP_ARRAY.'1']; - } - } - } - } elseif ($definition instanceof ArraySchema) { - $operationArray = array_merge($operationArray, $this->parseSchema($definition, $defKey, 1, 1)); - } - - $this->generateMetaDataFile( - self::OUTPUT_DIR2, - $defKey, - 'definition', - $operationArray - ); - } - - /** - * Parse schema and return an array that will be consumed by mustache template engine. - * - * @param SchemaInterface $schema - * @param string $name - * @param boolean $forArray - * @param integer $depth - * @return array - */ - private function parseSchema($schema, $name, $forArray, $depth) - { - $data = []; - - if ($schema instanceof RefSchema) { - $ref = $schema->getRef(); - preg_match(self::REF_REGEX, $ref, $matches); - if (count($matches) == 2) { - if (!$forArray) { - $data[self::TEMPLATE_VAR_FIELD_NAME] = $name; - $data[self::TEMPLATE_VAR_FIELD_TYPE] = $matches[1]; - } else { - $data[self::TEMPLATE_VAR_VALUES][] = [self::TEMPLATE_VAR_VALUE => $matches[1]]; - } - } - } elseif ($schema instanceof ArraySchema) { - $values = []; - $items = $schema->getItems(); - $data[self::TEMPLATE_VAR_OP_ARRAY.$depth][self::TEMPLATE_VAR_ARRAY_KEY] = $name; - if ($items instanceof ArrayCollection) { - foreach ($items->getIterator() as $itemKey => $item) { - $values[] = $this->parseSchema($item, $itemKey, 1, $depth+1); - } - $data[self::TEMPLATE_VAR_VALUES] = $values; - $data[self::TEMPLATE_VAR_OP_ARRAY.$depth] = $data; - } else { - $data[self::TEMPLATE_VAR_OP_ARRAY.$depth] = array_merge( - $data[self::TEMPLATE_VAR_OP_ARRAY.$depth], - $this->parseSchema($items, $name, 1, $depth+1) - ); - } - } else { - if (method_exists($schema, 'getType')) { - if (!$forArray) { - $data[self::TEMPLATE_VAR_FIELD_NAME] = $name; - $data[self::TEMPLATE_VAR_FIELD_TYPE] = $schema->getType(); - } else { - $data[self::TEMPLATE_VAR_VALUES][] = [self::TEMPLATE_VAR_VALUE => $schema->getType()]; - } - } - } - return $data; - } - - /** - * Parse params for an operation. - * - * @param ArrayCollection $params - * @param string $path - * @return void - */ - private function parseParams($params, $path) - { - foreach ($params->getIterator() as $paramKey => $param) { - if (empty($param)) { - continue; - } - - $paramIn = $param->getIn(); - if ($paramIn == 'body') { - $this->setBodyParams($param); - } elseif ($paramIn == 'path') { - $this->setPathParams($param, $path); - } elseif ($paramIn == 'query') { - $this->setQueryParams($param); - } - } - } - - /** - * Set body params for an operation. - * - * @param BodyParameter $param - * @return void - */ - private function setBodyParams($param) - { - $this->fields = []; - $required = []; - - $paramSchema = $param->getSchema(); - $paramSchemaRequired = $paramSchema->getRequired(); - if (!empty($paramSchemaRequired)) { - foreach ($paramSchemaRequired as $i => $key) { - $required[] = $key; - } - } - $paramSchemaProperties = $paramSchema->getProperties(); - foreach ($paramSchemaProperties->getIterator() as $paramPropertyKey => $paramSchemaProperty) { - $field = []; - $field[self::TEMPLATE_VAR_FIELD_NAME] = $paramPropertyKey; - $field[self::TEMPLATE_VAR_FIELD_TYPE] = $paramSchemaProperty->getType(); - if ($field[self::TEMPLATE_VAR_FIELD_TYPE] == 'ref') { - preg_match(self::REF_REGEX, $paramSchemaProperty->getRef(), $matches); - if (count($matches) == 2) { - $field[self::TEMPLATE_VAR_FIELD_TYPE] = $matches[1]; - } - } - if (in_array($paramPropertyKey, $required)) { - $field[self::TEMPLATE_VAR_FIELD_IS_REQUIRED] = 'true'; - } else { - $field[self::TEMPLATE_VAR_FIELD_IS_REQUIRED] = 'false'; - } - $this->fields[] = $field; - } - } - - /** - * Set path params for an operation. - * - * @param AbstractTypedParameter $param - * @param string $path - * @return void - */ - private function setPathParams($param, $path) - { - $pathParamStr = '{' . $param->getName() . '}'; - if (strpos($path, $pathParamStr) === false) { - $this->pathParams .= '/' . $pathParamStr; - } - } - - /** - * Set query params for an operation. - * - * @param AbstractTypedParameter $param - * @return void - */ - private function setQueryParams($param) - { - $query = []; - $query[self::TEMPLATE_VAR_PARAM_NAME] = $param->getName(); - $query[self::TEMPLATE_VAR_PARAM_TYPE] = $param->getType(); - - $this->params[] = $query; - } - - /** - * Build swagger spec from factory. - * - * @return void - */ - private static function buildSwaggerSpec() - { - $factory = new SwaggerFactory(); - self::$swagger = $factory->build(self::INPUT_TXT_FILE); - } - - /** - * Function which initializes mustache templates for file generation. - * - * @return void - */ - private function initMustacheTemplates() - { - $this->mustache_engine = new Mustache_Engine( - ['loader' => new Mustache_Loader_FilesystemLoader("views"), - 'partials_loader' => new Mustache_Loader_FilesystemLoader( - "views" . DIRECTORY_SEPARATOR . "partials" - )] - ); - } - - /** - * Render template and generate a metadata file. - * - * @param string $relativeDir - * @param string $fileName - * @param string $template - * @param array $data - * @return void - */ - private function generateMetaDataFile($relativeDir, $fileName, $template, $data) - { - $this->filepath = $relativeDir . DIRECTORY_SEPARATOR . $fileName . "-meta.xml"; - $result = $this->mustache_engine->render($template, $data); - $this->cleanAndCreateOutputDir(); - file_put_contents( - $this->filepath, - $result - ); - } - - /** - * Function which cleans any previously created fileand creates the _output dir. - * - * @return void - */ - private function cleanAndCreateOutputDir() - { - if (!file_exists(self::OUTPUT_DIR)) { - mkdir(self::OUTPUT_DIR); - } - - if (!file_exists(self::OUTPUT_DIR2)) { - mkdir(self::OUTPUT_DIR2); - } - - if (file_exists($this->filepath)) { - unlink($this->filepath); - } - } - /* - private static function debugData() { - $paramsExample = ['params' => - [ - 'paramName' => 'name', - 'paramType' => 'type' - ], - [ - 'paramName' => 'name', - 'paramType' => 'type' - ], - [ - 'paramName' => 'name', - 'paramType' => 'type' - ], - ]; - $fieldsExample = ['fields' => - [ - 'fieldName' => 'name', - 'fieldType' => 'type', - 'isRequired' => true, - ], - [ - 'fieldName' => 'name', - 'fieldType' => 'type', - 'isRequired' => true, - ], - [ - 'fieldName' => 'name', - 'fieldType' => 'type', - 'isRequired' => true, - ], - ]; - $arraysExample = ['arrays1' => - [ - 'arrayKey' => 'someKey', - 'values' => [ - 'type1', - 'type2', - ], - 'arrays2' => [ - 'arrayKey' => 'otherKey', - 'values' => [ - 'type3', - 'type4', - ], - 'arrays3' => [ - 'arrayKey' => 'anotherKey', - 'values' => [ - 'type5', - 'type6', - ], - ], - ], - ], - [ - 'arrayKey' => 'someKey', - 'values' => [ - [ - 'value' => 'type1', - ], - [ - 'value' => 'type2', - ], - ], - 'arrays2' => [ - 'arrayKey' => 'otherKey', - 'values' => [ - [ - 'value' => 'type3', - ], - [ - 'value' => 'type4', - ], - ], - 'arrays3' => [ - 'arrayKey' => 'anotherKey', - 'values' => [ - [ - 'value' => 'type5', - ], - [ - 'value' => 'type6', - ], - ], - ], - ], - ], - ]; - } - */ -} diff --git a/src/Magento/FunctionalTestingFramework/Util/MetadataGenerator/Swagger/_generateMetadataFile.php b/src/Magento/FunctionalTestingFramework/Util/MetadataGenerator/Swagger/_generateMetadataFile.php deleted file mode 100644 index c759b045e..000000000 --- a/src/Magento/FunctionalTestingFramework/Util/MetadataGenerator/Swagger/_generateMetadataFile.php +++ /dev/null @@ -1,10 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -require_once 'autoload.php'; - -// create a MetadataGenerator Object -$generator = new Magento\FunctionalTestingFramework\Util\MetadataGenerator\Swagger\MetadataGenerator(); -$generator->generateMetadataFromSwagger(); diff --git a/src/Magento/FunctionalTestingFramework/Util/MetadataGenerator/Swagger/autoload.php b/src/Magento/FunctionalTestingFramework/Util/MetadataGenerator/Swagger/autoload.php deleted file mode 100644 index e7108d0f4..000000000 --- a/src/Magento/FunctionalTestingFramework/Util/MetadataGenerator/Swagger/autoload.php +++ /dev/null @@ -1,17 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -use Doctrine\Common\Annotations\AnnotationRegistry; - -$loader = require '../../../../../../vendor/autoload.php'; - -AnnotationRegistry::registerAutoloadNamespace( - 'JMS\Serializer\Annotation', - "../../../../../vendor/jms/serializer/src" -); - -AnnotationRegistry::registerLoader([$loader, 'loadClass']); - -return $loader; diff --git a/src/Magento/FunctionalTestingFramework/Util/MetadataGenerator/Swagger/magento.json b/src/Magento/FunctionalTestingFramework/Util/MetadataGenerator/Swagger/magento.json deleted file mode 100644 index 07f40e979..000000000 --- a/src/Magento/FunctionalTestingFramework/Util/MetadataGenerator/Swagger/magento.json +++ /dev/null @@ -1 +0,0 @@ -{"swagger":"2.0","info":{"version":"2.2","title":"Magento Enterprise"},"host":"magento3.loc","basePath":"/index.php/rest/all","schemes":["http"],"tags":[{"name":"storeStoreRepositoryV1","description":"Store repository interface"},{"name":"storeGroupRepositoryV1","description":"Group repository interface"},{"name":"storeWebsiteRepositoryV1","description":"Website repository interface"},{"name":"storeStoreConfigManagerV1","description":"Store config manager interface"},{"name":"directoryCurrencyInformationAcquirerV1","description":"Currency information acquirer interface"},{"name":"directoryCountryInformationAcquirerV1","description":"Country information acquirer interface"},{"name":"eavAttributeSetRepositoryV1","description":"Interface AttributeSetRepositoryInterface"},{"name":"eavAttributeSetManagementV1","description":"Interface AttributeSetManagementInterface"},{"name":"customerGroupRepositoryV1","description":"Customer group CRUD interface"},{"name":"customerGroupManagementV1","description":"Interface for managing customer groups."},{"name":"customerCustomerGroupConfigV1","description":"Interface for system configuration operations for customer groups."},{"name":"customerCustomerMetadataV1","description":"Interface for retrieval information about customer attributes metadata."},{"name":"customerAddressMetadataV1","description":"Interface for retrieval information about customer address attributes metadata."},{"name":"customerCustomerRepositoryV1","description":"Customer CRUD interface."},{"name":"customerAccountManagementV1","description":"Interface for managing customers accounts."},{"name":"customerAddressRepositoryV1","description":"Customer address CRUD interface."},{"name":"backendModuleServiceV1","description":"Interface for module service."},{"name":"cmsPageRepositoryV1","description":"CMS page CRUD interface."},{"name":"cmsBlockRepositoryV1","description":"CMS block CRUD interface."},{"name":"catalogProductRepositoryV1","description":""},{"name":"catalogProductAttributeTypesListV1","description":""},{"name":"catalogProductAttributeRepositoryV1","description":"Interface RepositoryInterface must be implemented in new model"},{"name":"catalogCategoryAttributeRepositoryV1","description":"Interface RepositoryInterface must be implemented in new model"},{"name":"catalogCategoryAttributeOptionManagementV1","description":"Interface RepositoryInterface must be implemented in new model"},{"name":"catalogProductTypeListV1","description":""},{"name":"catalogAttributeSetRepositoryV1","description":""},{"name":"catalogAttributeSetManagementV1","description":""},{"name":"catalogProductAttributeManagementV1","description":""},{"name":"catalogProductAttributeGroupRepositoryV1","description":""},{"name":"catalogProductAttributeOptionManagementV1","description":""},{"name":"catalogProductMediaAttributeManagementV1","description":""},{"name":"catalogProductAttributeMediaGalleryManagementV1","description":""},{"name":"catalogProductTierPriceManagementV1","description":""},{"name":"catalogTierPriceStorageV1","description":"Tier prices storage."},{"name":"catalogBasePriceStorageV1","description":"Base prices storage."},{"name":"catalogCostStorageV1","description":"Product cost storage."},{"name":"catalogSpecialPriceStorageV1","description":"Special price storage presents efficient price API and is used to retrieve, update or delete special prices."},{"name":"catalogCategoryRepositoryV1","description":""},{"name":"catalogCategoryManagementV1","description":""},{"name":"catalogCategoryListV1","description":""},{"name":"catalogProductCustomOptionTypeListV1","description":""},{"name":"catalogProductCustomOptionRepositoryV1","description":""},{"name":"catalogProductLinkTypeListV1","description":""},{"name":"catalogProductLinkManagementV1","description":""},{"name":"catalogProductLinkRepositoryV1","description":"Interface Product links handling interface"},{"name":"catalogCategoryLinkManagementV1","description":""},{"name":"catalogCategoryLinkRepositoryV1","description":""},{"name":"catalogProductWebsiteLinkRepositoryV1","description":"Interface ProductWebsiteLinkRepositoryInterface"},{"name":"catalogProductRenderListV1","description":"Interface which provides product renders information for products"},{"name":"catalogInventoryStockRegistryV1","description":"Interface StockRegistryInterface"},{"name":"bundleProductLinkManagementV1","description":"Interface for Management of ProductLink"},{"name":"bundleProductOptionRepositoryV1","description":"Interface ProductOptionRepositoryInterface"},{"name":"bundleProductOptionTypeListV1","description":"Interface ProductOptionTypeListInterface"},{"name":"bundleProductOptionManagementV1","description":"Option manager for bundle products"},{"name":"quoteCartRepositoryV1","description":"Interface CartRepositoryInterface"},{"name":"quoteCartManagementV1","description":"Interface CartManagementInterface"},{"name":"quoteGuestCartRepositoryV1","description":"Cart Repository interface for guest carts."},{"name":"quoteGuestCartManagementV1","description":"Cart Management interface for guest carts."},{"name":"quoteShippingMethodManagementV1","description":"Interface ShippingMethodManagementInterface"},{"name":"quoteShipmentEstimationV1","description":"Interface ShipmentManagementInterface"},{"name":"quoteGuestShippingMethodManagementV1","description":"Shipping method management interface for guest carts."},{"name":"quoteGuestShipmentEstimationV1","description":"Interface GuestShipmentEstimationInterface"},{"name":"quoteCartItemRepositoryV1","description":"Interface CartItemRepositoryInterface"},{"name":"quoteGuestCartItemRepositoryV1","description":"Cart Item repository interface for guest carts."},{"name":"quotePaymentMethodManagementV1","description":"Interface PaymentMethodManagementInterface"},{"name":"quoteGuestPaymentMethodManagementV1","description":"Payment method management interface for guest carts."},{"name":"quoteBillingAddressManagementV1","description":"Interface BillingAddressManagementInterface"},{"name":"quoteGuestBillingAddressManagementV1","description":"Billing address management interface for guest carts."},{"name":"quoteCouponManagementV1","description":"Coupon management service interface."},{"name":"quoteGuestCouponManagementV1","description":"Coupon management interface for guest carts."},{"name":"quoteCartTotalRepositoryV1","description":"Interface CartTotalRepositoryInterface"},{"name":"quoteGuestCartTotalManagementV1","description":"Bundled API to collect totals for cart based on shipping/payment methods and additional data."},{"name":"quoteGuestCartTotalRepositoryV1","description":"Cart totals repository interface for guest carts."},{"name":"quoteCartTotalManagementV1","description":"Bundled API to collect totals for cart based on shipping/payment methods and additional data."},{"name":"searchV1","description":"Search API for all requests"},{"name":"salesOrderRepositoryV1","description":"Order repository interface. An order is a document that a web store issues to a customer. Magento generates a sales order that lists the product items, billing and shipping addresses, and shipping and payment methods. A corresponding external document, known as a purchase order, is emailed to the customer."},{"name":"salesOrderManagementV1","description":"Order management interface. An order is a document that a web store issues to a customer. Magento generates a sales order that lists the product items, billing and shipping addresses, and shipping and payment methods. A corresponding external document, known as a purchase order, is emailed to the customer."},{"name":"salesOrderAddressRepositoryV1","description":"Order address repository interface. An order is a document that a web store issues to a customer. Magento generates a sales order that lists the product items, billing and shipping addresses, and shipping and payment methods. A corresponding external document, known as a purchase order, is emailed to the customer."},{"name":"salesOrderItemRepositoryV1","description":"Order item repository interface. An order is a document that a web store issues to a customer. Magento generates a sales order that lists the product items, billing and shipping addresses, and shipping and payment methods. A corresponding external document, known as a purchase order, is emailed to the customer."},{"name":"salesInvoiceRepositoryV1","description":"Invoice repository interface. An invoice is a record of the receipt of payment for an order."},{"name":"salesInvoiceManagementV1","description":"Invoice management interface. An invoice is a record of the receipt of payment for an order."},{"name":"salesInvoiceCommentRepositoryV1","description":"Invoice comment repository interface. An invoice is a record of the receipt of payment for an order. An invoice can include comments that detail the invoice history."},{"name":"salesRefundInvoiceV1","description":"Interface RefundInvoiceInterface"},{"name":"salesCreditmemoManagementV1","description":"Credit memo add comment interface. After a customer places and pays for an order and an invoice has been issued, the merchant can create a credit memo to refund all or part of the amount paid for any returned or undelivered items. The memo restores funds to the customer account so that the customer can make future purchases."},{"name":"salesCreditmemoRepositoryV1","description":"Credit memo repository interface. After a customer places and pays for an order and an invoice has been issued, the merchant can create a credit memo to refund all or part of the amount paid for any returned or undelivered items. The memo restores funds to the customer account so that the customer can make future purchases."},{"name":"salesCreditmemoCommentRepositoryV1","description":"Credit memo comment repository interface. After a customer places and pays for an order and an invoice has been issued, the merchant can create a credit memo to refund all or part of the amount paid for any returned or undelivered items. The memo restores funds to the customer account so that the customer can make future purchases. A credit memo usually includes comments that detail why the credit memo amount was credited to the customer."},{"name":"salesRefundOrderV1","description":"Interface RefundOrderInterface"},{"name":"salesShipmentRepositoryV1","description":"Shipment repository interface. A shipment is a delivery package that contains products. A shipment document accompanies the shipment. This document lists the products and their quantities in the delivery package."},{"name":"salesShipmentManagementV1","description":"Shipment management interface. A shipment is a delivery package that contains products. A shipment document accompanies the shipment. This document lists the products and their quantities in the delivery package."},{"name":"salesShipmentCommentRepositoryV1","description":"Shipment comment repository interface. A shipment is a delivery package that contains products. A shipment document accompanies the shipment. This document lists the products and their quantities in the delivery package. A shipment document can contain comments."},{"name":"salesShipmentTrackRepositoryV1","description":"Shipment track repository interface. A shipment is a delivery package that contains products. A shipment document accompanies the shipment. This document lists the products and their quantities in the delivery package."},{"name":"salesShipOrderV1","description":"Class ShipOrderInterface"},{"name":"salesTransactionRepositoryV1","description":"Transaction repository interface. A transaction is an interaction between a merchant and a customer such as a purchase, a credit, a refund, and so on."},{"name":"salesInvoiceOrderV1","description":"Class InvoiceOrderInterface"},{"name":"checkoutGuestShippingInformationManagementV1","description":"Interface for managing guest shipping address information"},{"name":"checkoutShippingInformationManagementV1","description":"Interface for managing customer shipping address information"},{"name":"checkoutTotalsInformationManagementV1","description":"Interface for quote totals calculation"},{"name":"checkoutGuestTotalsInformationManagementV1","description":"Interface for guest quote totals calculation"},{"name":"checkoutGuestPaymentInformationManagementV1","description":"Interface for managing guest payment information"},{"name":"checkoutPaymentInformationManagementV1","description":"Interface for managing quote payment information"},{"name":"downloadableLinkRepositoryV1","description":"Interface LinkRepositoryInterface"},{"name":"downloadableSampleRepositoryV1","description":"Interface SampleRepositoryInterface"},{"name":"checkoutAgreementsCheckoutAgreementsRepositoryV1","description":"Interface CheckoutAgreementsRepositoryInterface"},{"name":"configurableProductLinkManagementV1","description":"Manage children products of configurable product"},{"name":"configurableProductConfigurableProductManagementV1","description":"Interface ConfigurableProductManagementInterface"},{"name":"configurableProductOptionRepositoryV1","description":"Manage options of configurable product"},{"name":"customerBalanceBalanceManagementV1","description":"Customer balance(store credit) operations"},{"name":"giftCardAccountGiftCardAccountManagementV1","description":"Interface GiftCardAccountManagementInterface"},{"name":"giftCardAccountGuestGiftCardAccountManagementV1","description":"Interface GuestGiftCardAccountManagementInterface"},{"name":"taxTaxRateRepositoryV1","description":"Tax rate CRUD interface."},{"name":"taxTaxRuleRepositoryV1","description":"Tax rule CRUD interface."},{"name":"taxTaxClassRepositoryV1","description":"Tax class CRUD interface."},{"name":"giftMessageCartRepositoryV1","description":"Interface CartRepositoryInterface"},{"name":"giftMessageItemRepositoryV1","description":"Interface ItemRepositoryInterface"},{"name":"giftMessageGuestCartRepositoryV1","description":"Interface GuestCartRepositoryInterface"},{"name":"giftMessageGuestItemRepositoryV1","description":"Interface GuestItemRepositoryInterface"},{"name":"giftWrappingWrappingRepositoryV1","description":"Interface WrappingRepositoryInterface"},{"name":"salesRuleRuleRepositoryV1","description":"Sales rule CRUD interface"},{"name":"salesRuleCouponRepositoryV1","description":"Coupon CRUD interface"},{"name":"salesRuleCouponManagementV1","description":"Coupon management interface"},{"name":"giftRegistryShippingMethodManagementV1","description":"Interface ShippingMethodManagementInterface"},{"name":"giftRegistryGuestCartShippingMethodManagementV1","description":"Interface ShippingMethodManagementInterface"},{"name":"rewardRewardManagementV1","description":"Interface RewardManagementInterface"},{"name":"rmaTrackManagementV1","description":"Interface TrackManagementInterface"},{"name":"rmaRmaRepositoryV1","description":"Interface RmaRepositoryInterface"},{"name":"rmaCommentManagementV1","description":"Interface CommentRepositoryInterface"},{"name":"rmaRmaManagementV1","description":"Interface RmaManagementInterface"},{"name":"rmaRmaAttributesManagementV1","description":"Interface RmaAttributesManagementInterface"},{"name":"integrationAdminTokenServiceV1","description":"Interface providing token generation for Admins"},{"name":"integrationCustomerTokenServiceV1","description":"Interface providing token generation for Customers"},{"name":"testModule1AllSoapAndRestV1","description":""},{"name":"testModule1AllSoapAndRestV2","description":""},{"name":"testModule2SubsetRestV1","description":""},{"name":"testModule3ErrorV1","description":""},{"name":"testModule4DataObjectServiceV1","description":""},{"name":"testModule5AllSoapAndRestV1","description":"Both SOAP and REST Version ONE"},{"name":"testModule5OverrideServiceV1","description":""},{"name":"testModule5AllSoapAndRestV2","description":"Both SOAP and REST Version TWO"},{"name":"testModuleDefaultHydratorCustomerPersistenceV1","description":"Customer CRUD interface"},{"name":"testModuleJoinDirectivesTestRepositoryV1","description":"Interface TestRepositoryInterface"},{"name":"testModuleMSCAllSoapAndRestV1","description":""},{"name":"worldpayGuestPaymentInformationManagementProxyV1","description":"Interface GuestPaymentInformationManagementProxyInterface"}],"paths":{"/V1/store/storeViews":{"get":{"tags":["storeStoreRepositoryV1"],"description":"Retrieve list of all stores","operationId":"storeStoreRepositoryV1GetListGet","responses":{"200":{"description":"200 Success.","schema":{"type":"array","items":{"$ref":"#/definitions/store-data-store-interface"}}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/store/storeGroups":{"get":{"tags":["storeGroupRepositoryV1"],"description":"Retrieve list of all groups","operationId":"storeGroupRepositoryV1GetListGet","responses":{"200":{"description":"200 Success.","schema":{"type":"array","items":{"$ref":"#/definitions/store-data-group-interface"}}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/store/websites":{"get":{"tags":["storeWebsiteRepositoryV1"],"description":"Retrieve list of all websites","operationId":"storeWebsiteRepositoryV1GetListGet","responses":{"200":{"description":"200 Success.","schema":{"type":"array","items":{"$ref":"#/definitions/store-data-website-interface"}}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/store/storeConfigs":{"get":{"tags":["storeStoreConfigManagerV1"],"description":"","operationId":"storeStoreConfigManagerV1GetStoreConfigsGet","parameters":[{"name":"storeCodes","in":"query","type":"array","items":{"type":"string"},"required":false}],"responses":{"200":{"description":"200 Success.","schema":{"type":"array","items":{"$ref":"#/definitions/store-data-store-config-interface"}}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/directory/currency":{"get":{"tags":["directoryCurrencyInformationAcquirerV1"],"description":"Get currency information for the store.","operationId":"directoryCurrencyInformationAcquirerV1GetCurrencyInfoGet","responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/directory-data-currency-information-interface"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/directory/countries":{"get":{"tags":["directoryCountryInformationAcquirerV1"],"description":"Get all countries and regions information for the store.","operationId":"directoryCountryInformationAcquirerV1GetCountriesInfoGet","responses":{"200":{"description":"200 Success.","schema":{"type":"array","items":{"$ref":"#/definitions/directory-data-country-information-interface"}}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/directory/countries/{countryId}":{"get":{"tags":["directoryCountryInformationAcquirerV1"],"description":"Get country and region information for the store.","operationId":"directoryCountryInformationAcquirerV1GetCountryInfoGet","parameters":[{"name":"countryId","in":"path","type":"string","required":true}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/directory-data-country-information-interface"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/eav/attribute-sets/list":{"get":{"tags":["eavAttributeSetRepositoryV1"],"description":"Retrieve list of Attribute Sets This call returns an array of objects, but detailed information about each object\u2019s attributes might not be included. See http://devdocs.magento.com/codelinks/attributes.html#AttributeSetRepositoryInterface to determine which call to use to get detailed information about all attributes for an object.","operationId":"eavAttributeSetRepositoryV1GetListGet","parameters":[{"name":"searchCriteria[filterGroups][][filters][][field]","in":"query","type":"string","description":"Field"},{"name":"searchCriteria[filterGroups][][filters][][value]","in":"query","type":"string","description":"Value"},{"name":"searchCriteria[filterGroups][][filters][][conditionType]","in":"query","type":"string","description":"Condition type"},{"name":"searchCriteria[sortOrders][][field]","in":"query","type":"string","description":"Sorting field."},{"name":"searchCriteria[sortOrders][][direction]","in":"query","type":"string","description":"Sorting direction."},{"name":"searchCriteria[pageSize]","in":"query","type":"integer","description":"Page size."},{"name":"searchCriteria[currentPage]","in":"query","type":"integer","description":"Current page."}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/eav-data-attribute-set-search-results-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/eav/attribute-sets/{attributeSetId}":{"get":{"tags":["eavAttributeSetRepositoryV1"],"description":"Retrieve attribute set information based on given ID","operationId":"eavAttributeSetRepositoryV1GetGet","parameters":[{"name":"attributeSetId","in":"path","type":"integer","required":true}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/eav-data-attribute-set-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}},"delete":{"tags":["eavAttributeSetRepositoryV1"],"description":"Remove attribute set by given ID","operationId":"eavAttributeSetRepositoryV1DeleteByIdDelete","parameters":[{"name":"attributeSetId","in":"path","type":"integer","required":true}],"responses":{"200":{"description":"200 Success.","schema":{"type":"boolean"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}},"put":{"tags":["eavAttributeSetRepositoryV1"],"description":"Save attribute set data","operationId":"eavAttributeSetRepositoryV1SavePut","parameters":[{"name":"attributeSetId","in":"path","type":"string","required":true},{"name":"$body","in":"body","schema":{"required":["attributeSet"],"properties":{"attributeSet":{"$ref":"#/definitions/eav-data-attribute-set-interface"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/eav-data-attribute-set-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"500":{"description":"Internal Server error","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/eav/attribute-sets":{"post":{"tags":["eavAttributeSetManagementV1"],"description":"Create attribute set from data","operationId":"eavAttributeSetManagementV1CreatePost","parameters":[{"name":"$body","in":"body","schema":{"required":["entityTypeCode","attributeSet","skeletonId"],"properties":{"entityTypeCode":{"type":"string"},"attributeSet":{"$ref":"#/definitions/eav-data-attribute-set-interface"},"skeletonId":{"type":"integer"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/eav-data-attribute-set-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/customerGroups/{id}":{"get":{"tags":["customerGroupRepositoryV1"],"description":"Get customer group by group ID.","operationId":"customerGroupRepositoryV1GetByIdGet","parameters":[{"name":"id","in":"path","type":"integer","required":true}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/customer-data-group-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"500":{"description":"Internal Server error","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}},"put":{"tags":["customerGroupRepositoryV1"],"description":"Save customer group.","operationId":"customerGroupRepositoryV1SavePut","parameters":[{"name":"id","in":"path","type":"string","required":true},{"name":"$body","in":"body","schema":{"required":["group"],"properties":{"group":{"$ref":"#/definitions/customer-data-group-interface"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/customer-data-group-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"500":{"description":"Internal Server error","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}},"delete":{"tags":["customerGroupRepositoryV1"],"description":"Delete customer group by ID.","operationId":"customerGroupRepositoryV1DeleteByIdDelete","parameters":[{"name":"id","in":"path","type":"integer","required":true}],"responses":{"200":{"description":"200 Success.","schema":{"type":"boolean","description":"true on success"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"500":{"description":"Internal Server error","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/customerGroups/search":{"get":{"tags":["customerGroupRepositoryV1"],"description":"Retrieve customer groups. The list of groups can be filtered to exclude the NOT_LOGGED_IN group using the first parameter and/or it can be filtered by tax class. This call returns an array of objects, but detailed information about each object\u2019s attributes might not be included. See http://devdocs.magento.com/codelinks/attributes.html#GroupRepositoryInterface to determine which call to use to get detailed information about all attributes for an object.","operationId":"customerGroupRepositoryV1GetListGet","parameters":[{"name":"searchCriteria[filterGroups][][filters][][field]","in":"query","type":"string","description":"Field"},{"name":"searchCriteria[filterGroups][][filters][][value]","in":"query","type":"string","description":"Value"},{"name":"searchCriteria[filterGroups][][filters][][conditionType]","in":"query","type":"string","description":"Condition type"},{"name":"searchCriteria[sortOrders][][field]","in":"query","type":"string","description":"Sorting field."},{"name":"searchCriteria[sortOrders][][direction]","in":"query","type":"string","description":"Sorting direction."},{"name":"searchCriteria[pageSize]","in":"query","type":"integer","description":"Page size."},{"name":"searchCriteria[currentPage]","in":"query","type":"integer","description":"Current page."}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/customer-data-group-search-results-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"500":{"description":"Internal Server error","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/customerGroups":{"post":{"tags":["customerGroupRepositoryV1"],"description":"Save customer group.","operationId":"customerGroupRepositoryV1SavePost","parameters":[{"name":"$body","in":"body","schema":{"required":["group"],"properties":{"group":{"$ref":"#/definitions/customer-data-group-interface"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/customer-data-group-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"500":{"description":"Internal Server error","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/customerGroups/default/{storeId}":{"get":{"tags":["customerGroupManagementV1"],"description":"Get default customer group.","operationId":"customerGroupManagementV1GetDefaultGroupGet","parameters":[{"name":"storeId","in":"path","type":"integer","required":true}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/customer-data-group-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"500":{"description":"Internal Server error","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/customerGroups/default":{"get":{"tags":["customerGroupManagementV1"],"description":"Get default customer group.","operationId":"customerGroupManagementV1GetDefaultGroupGet","parameters":[{"name":"storeId","in":"query","type":"integer","required":false}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/customer-data-group-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"500":{"description":"Internal Server error","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/customerGroups/{id}/permissions":{"get":{"tags":["customerGroupManagementV1"],"description":"Check if customer group can be deleted.","operationId":"customerGroupManagementV1IsReadonlyGet","parameters":[{"name":"id","in":"path","type":"integer","required":true}],"responses":{"200":{"description":"200 Success.","schema":{"type":"boolean"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"500":{"description":"Internal Server error","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/customerGroups/default/{id}":{"put":{"tags":["customerCustomerGroupConfigV1"],"description":"Set system default customer group.","operationId":"customerCustomerGroupConfigV1SetDefaultCustomerGroupPut","parameters":[{"name":"id","in":"path","type":"integer","required":true}],"responses":{"200":{"description":"200 Success.","schema":{"type":"integer"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"500":{"description":"Internal Server error","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/attributeMetadata/customer/attribute/{attributeCode}":{"get":{"tags":["customerCustomerMetadataV1"],"description":"Retrieve attribute metadata.","operationId":"customerCustomerMetadataV1GetAttributeMetadataGet","parameters":[{"name":"attributeCode","in":"path","type":"string","required":true}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/customer-data-attribute-metadata-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"500":{"description":"Internal Server error","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/attributeMetadata/customer/form/{formCode}":{"get":{"tags":["customerCustomerMetadataV1"],"description":"Retrieve all attributes filtered by form code","operationId":"customerCustomerMetadataV1GetAttributesGet","parameters":[{"name":"formCode","in":"path","type":"string","required":true}],"responses":{"200":{"description":"200 Success.","schema":{"type":"array","items":{"$ref":"#/definitions/customer-data-attribute-metadata-interface"}}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"500":{"description":"Internal Server error","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/attributeMetadata/customer":{"get":{"tags":["customerCustomerMetadataV1"],"description":"Get all attribute metadata.","operationId":"customerCustomerMetadataV1GetAllAttributesMetadataGet","responses":{"200":{"description":"200 Success.","schema":{"type":"array","items":{"$ref":"#/definitions/customer-data-attribute-metadata-interface"}}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"500":{"description":"Internal Server error","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/attributeMetadata/customer/custom":{"get":{"tags":["customerCustomerMetadataV1"],"description":"Get custom attributes metadata for the given data interface.","operationId":"customerCustomerMetadataV1GetCustomAttributesMetadataGet","parameters":[{"name":"dataInterfaceName","in":"query","type":"string","required":false}],"responses":{"200":{"description":"200 Success.","schema":{"type":"array","items":{"$ref":"#/definitions/customer-data-attribute-metadata-interface"}}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"500":{"description":"Internal Server error","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/attributeMetadata/customerAddress/attribute/{attributeCode}":{"get":{"tags":["customerAddressMetadataV1"],"description":"Retrieve attribute metadata.","operationId":"customerAddressMetadataV1GetAttributeMetadataGet","parameters":[{"name":"attributeCode","in":"path","type":"string","required":true}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/customer-data-attribute-metadata-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"500":{"description":"Internal Server error","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/attributeMetadata/customerAddress/form/{formCode}":{"get":{"tags":["customerAddressMetadataV1"],"description":"Retrieve all attributes filtered by form code","operationId":"customerAddressMetadataV1GetAttributesGet","parameters":[{"name":"formCode","in":"path","type":"string","required":true}],"responses":{"200":{"description":"200 Success.","schema":{"type":"array","items":{"$ref":"#/definitions/customer-data-attribute-metadata-interface"}}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"500":{"description":"Internal Server error","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/attributeMetadata/customerAddress":{"get":{"tags":["customerAddressMetadataV1"],"description":"Get all attribute metadata.","operationId":"customerAddressMetadataV1GetAllAttributesMetadataGet","responses":{"200":{"description":"200 Success.","schema":{"type":"array","items":{"$ref":"#/definitions/customer-data-attribute-metadata-interface"}}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"500":{"description":"Internal Server error","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/attributeMetadata/customerAddress/custom":{"get":{"tags":["customerAddressMetadataV1"],"description":"Get custom attributes metadata for the given data interface.","operationId":"customerAddressMetadataV1GetCustomAttributesMetadataGet","parameters":[{"name":"dataInterfaceName","in":"query","type":"string","required":false}],"responses":{"200":{"description":"200 Success.","schema":{"type":"array","items":{"$ref":"#/definitions/customer-data-attribute-metadata-interface"}}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"500":{"description":"Internal Server error","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/customers/{customerId}":{"get":{"tags":["customerCustomerRepositoryV1"],"description":"Get customer by customer ID.","operationId":"customerCustomerRepositoryV1GetByIdGet","parameters":[{"name":"customerId","in":"path","type":"integer","required":true}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/customer-data-customer-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"500":{"description":"Internal Server error","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}},"delete":{"tags":["customerCustomerRepositoryV1"],"description":"Delete customer by ID.","operationId":"customerCustomerRepositoryV1DeleteByIdDelete","parameters":[{"name":"customerId","in":"path","type":"integer","required":true}],"responses":{"200":{"description":"200 Success.","schema":{"type":"boolean","description":"true on success"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"500":{"description":"Internal Server error","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/customers/{id}":{"put":{"tags":["customerCustomerRepositoryV1"],"description":"Create or update a customer.","operationId":"customerCustomerRepositoryV1SavePut","parameters":[{"name":"id","in":"path","type":"string","required":true},{"name":"$body","in":"body","schema":{"required":["customer"],"properties":{"customer":{"$ref":"#/definitions/customer-data-customer-interface"},"passwordHash":{"type":"string"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/customer-data-customer-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"500":{"description":"Internal Server error","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/customers/me":{"put":{"tags":["customerCustomerRepositoryV1"],"description":"Create or update a customer.","operationId":"customerCustomerRepositoryV1SavePut","parameters":[{"name":"$body","in":"body","schema":{"required":["customer"],"properties":{"customer":{"$ref":"#/definitions/customer-data-customer-interface"},"passwordHash":{"type":"string"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/customer-data-customer-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"500":{"description":"Internal Server error","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}},"get":{"tags":["customerCustomerRepositoryV1"],"description":"Get customer by customer ID.","operationId":"customerCustomerRepositoryV1GetByIdGet","responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/customer-data-customer-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"500":{"description":"Internal Server error","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/customers/search":{"get":{"tags":["customerCustomerRepositoryV1"],"description":"Retrieve customers which match a specified criteria. This call returns an array of objects, but detailed information about each object\u2019s attributes might not be included. See http://devdocs.magento.com/codelinks/attributes.html#CustomerRepositoryInterface to determine which call to use to get detailed information about all attributes for an object.","operationId":"customerCustomerRepositoryV1GetListGet","parameters":[{"name":"searchCriteria[filterGroups][][filters][][field]","in":"query","type":"string","description":"Field"},{"name":"searchCriteria[filterGroups][][filters][][value]","in":"query","type":"string","description":"Value"},{"name":"searchCriteria[filterGroups][][filters][][conditionType]","in":"query","type":"string","description":"Condition type"},{"name":"searchCriteria[sortOrders][][field]","in":"query","type":"string","description":"Sorting field."},{"name":"searchCriteria[sortOrders][][direction]","in":"query","type":"string","description":"Sorting direction."},{"name":"searchCriteria[pageSize]","in":"query","type":"integer","description":"Page size."},{"name":"searchCriteria[currentPage]","in":"query","type":"integer","description":"Current page."}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/customer-data-customer-search-results-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"500":{"description":"Internal Server error","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/customers":{"post":{"tags":["customerAccountManagementV1"],"description":"Create customer account. Perform necessary business operations like sending email.","operationId":"customerAccountManagementV1CreateAccountPost","parameters":[{"name":"$body","in":"body","schema":{"required":["customer"],"properties":{"customer":{"$ref":"#/definitions/customer-data-customer-interface"},"password":{"type":"string"},"redirectUrl":{"type":"string"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/customer-data-customer-interface"}},"500":{"description":"Internal Server error","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/customers/me/activate":{"put":{"tags":["customerAccountManagementV1"],"description":"Activate a customer account using a key that was sent in a confirmation email.","operationId":"customerAccountManagementV1ActivateByIdPut","parameters":[{"name":"$body","in":"body","schema":{"required":["confirmationKey"],"properties":{"confirmationKey":{"type":"string"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/customer-data-customer-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"500":{"description":"Internal Server error","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/customers/{email}/activate":{"put":{"tags":["customerAccountManagementV1"],"description":"Activate a customer account using a key that was sent in a confirmation email.","operationId":"customerAccountManagementV1ActivatePut","parameters":[{"name":"email","in":"path","type":"string","required":true},{"name":"$body","in":"body","schema":{"required":["confirmationKey"],"properties":{"confirmationKey":{"type":"string"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/customer-data-customer-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"500":{"description":"Internal Server error","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/customers/me/password":{"put":{"tags":["customerAccountManagementV1"],"description":"Change customer password.","operationId":"customerAccountManagementV1ChangePasswordByIdPut","parameters":[{"name":"$body","in":"body","schema":{"required":["currentPassword","newPassword"],"properties":{"currentPassword":{"type":"string"},"newPassword":{"type":"string"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"type":"boolean","description":"true on success"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"500":{"description":"Internal Server error","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/customers/{customerId}/password/resetLinkToken/{resetPasswordLinkToken}":{"get":{"tags":["customerAccountManagementV1"],"description":"Check if password reset token is valid.","operationId":"customerAccountManagementV1ValidateResetPasswordLinkTokenGet","parameters":[{"name":"customerId","in":"path","type":"integer","required":true},{"name":"resetPasswordLinkToken","in":"path","type":"string","required":true}],"responses":{"200":{"description":"200 Success.","schema":{"type":"boolean","description":"True if the token is valid"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"500":{"description":"Internal Server error","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/customers/password":{"put":{"tags":["customerAccountManagementV1"],"description":"Send an email to the customer with a password reset link.","operationId":"customerAccountManagementV1InitiatePasswordResetPut","parameters":[{"name":"$body","in":"body","schema":{"required":["email","template"],"properties":{"email":{"type":"string"},"template":{"type":"string"},"websiteId":{"type":"integer"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"type":"boolean","description":"true on success"}},"500":{"description":"Internal Server error","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/customers/{customerId}/confirm":{"get":{"tags":["customerAccountManagementV1"],"description":"Gets the account confirmation status.","operationId":"customerAccountManagementV1GetConfirmationStatusGet","parameters":[{"name":"customerId","in":"path","type":"integer","required":true}],"responses":{"200":{"description":"200 Success.","schema":{"type":"string"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"500":{"description":"Internal Server error","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/customers/confirm":{"post":{"tags":["customerAccountManagementV1"],"description":"Resend confirmation email.","operationId":"customerAccountManagementV1ResendConfirmationPost","parameters":[{"name":"$body","in":"body","schema":{"required":["email","websiteId"],"properties":{"email":{"type":"string"},"websiteId":{"type":"integer"},"redirectUrl":{"type":"string"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"type":"boolean","description":"true on success"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"500":{"description":"Internal Server error","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/customers/validate":{"put":{"tags":["customerAccountManagementV1"],"description":"Validate customer data.","operationId":"customerAccountManagementV1ValidatePut","parameters":[{"name":"$body","in":"body","schema":{"required":["customer"],"properties":{"customer":{"$ref":"#/definitions/customer-data-customer-interface"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/customer-data-validation-results-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"500":{"description":"Internal Server error","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/customers/{customerId}/permissions/readonly":{"get":{"tags":["customerAccountManagementV1"],"description":"Check if customer can be deleted.","operationId":"customerAccountManagementV1IsReadonlyGet","parameters":[{"name":"customerId","in":"path","type":"integer","required":true}],"responses":{"200":{"description":"200 Success.","schema":{"type":"boolean"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"500":{"description":"Internal Server error","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/customers/isEmailAvailable":{"post":{"tags":["customerAccountManagementV1"],"description":"Check if given email is associated with a customer account in given website.","operationId":"customerAccountManagementV1IsEmailAvailablePost","parameters":[{"name":"$body","in":"body","schema":{"required":["customerEmail"],"properties":{"customerEmail":{"type":"string"},"websiteId":{"type":"integer","description":"If not set, will use the current websiteId"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"type":"boolean"}},"500":{"description":"Internal Server error","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/customers/me/billingAddress":{"get":{"tags":["customerAccountManagementV1"],"description":"Retrieve default billing address for the given customerId.","operationId":"customerAccountManagementV1GetDefaultBillingAddressGet","responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/customer-data-address-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"500":{"description":"Internal Server error","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/customers/{customerId}/billingAddress":{"get":{"tags":["customerAccountManagementV1"],"description":"Retrieve default billing address for the given customerId.","operationId":"customerAccountManagementV1GetDefaultBillingAddressGet","parameters":[{"name":"customerId","in":"path","type":"integer","required":true}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/customer-data-address-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"500":{"description":"Internal Server error","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/customers/me/shippingAddress":{"get":{"tags":["customerAccountManagementV1"],"description":"Retrieve default shipping address for the given customerId.","operationId":"customerAccountManagementV1GetDefaultShippingAddressGet","responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/customer-data-address-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"500":{"description":"Internal Server error","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/customers/{customerId}/shippingAddress":{"get":{"tags":["customerAccountManagementV1"],"description":"Retrieve default shipping address for the given customerId.","operationId":"customerAccountManagementV1GetDefaultShippingAddressGet","parameters":[{"name":"customerId","in":"path","type":"integer","required":true}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/customer-data-address-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"500":{"description":"Internal Server error","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/customers/addresses/{addressId}":{"get":{"tags":["customerAddressRepositoryV1"],"description":"Retrieve customer address.","operationId":"customerAddressRepositoryV1GetByIdGet","parameters":[{"name":"addressId","in":"path","type":"integer","required":true}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/customer-data-address-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"500":{"description":"Internal Server error","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/addresses/{addressId}":{"delete":{"tags":["customerAddressRepositoryV1"],"description":"Delete customer address by ID.","operationId":"customerAddressRepositoryV1DeleteByIdDelete","parameters":[{"name":"addressId","in":"path","type":"integer","required":true}],"responses":{"200":{"description":"200 Success.","schema":{"type":"boolean","description":"true on success"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"500":{"description":"Internal Server error","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/modules":{"get":{"tags":["backendModuleServiceV1"],"description":"Returns an array of enabled modules","operationId":"backendModuleServiceV1GetModulesGet","responses":{"200":{"description":"200 Success.","schema":{"type":"array","items":{"type":"string"}}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/cmsPage/{pageId}":{"get":{"tags":["cmsPageRepositoryV1"],"description":"Retrieve page.","operationId":"cmsPageRepositoryV1GetByIdGet","parameters":[{"name":"pageId","in":"path","type":"integer","required":true}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/cms-data-page-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"500":{"description":"Internal Server error","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}},"delete":{"tags":["cmsPageRepositoryV1"],"description":"Delete page by ID.","operationId":"cmsPageRepositoryV1DeleteByIdDelete","parameters":[{"name":"pageId","in":"path","type":"integer","required":true}],"responses":{"200":{"description":"200 Success.","schema":{"type":"boolean","description":"true on success"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"500":{"description":"Internal Server error","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/cmsPage/search":{"get":{"tags":["cmsPageRepositoryV1"],"description":"Retrieve pages matching the specified criteria.","operationId":"cmsPageRepositoryV1GetListGet","parameters":[{"name":"searchCriteria[filterGroups][][filters][][field]","in":"query","type":"string","description":"Field"},{"name":"searchCriteria[filterGroups][][filters][][value]","in":"query","type":"string","description":"Value"},{"name":"searchCriteria[filterGroups][][filters][][conditionType]","in":"query","type":"string","description":"Condition type"},{"name":"searchCriteria[sortOrders][][field]","in":"query","type":"string","description":"Sorting field."},{"name":"searchCriteria[sortOrders][][direction]","in":"query","type":"string","description":"Sorting direction."},{"name":"searchCriteria[pageSize]","in":"query","type":"integer","description":"Page size."},{"name":"searchCriteria[currentPage]","in":"query","type":"integer","description":"Current page."}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/cms-data-page-search-results-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"500":{"description":"Internal Server error","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/cmsPage":{"post":{"tags":["cmsPageRepositoryV1"],"description":"Save page.","operationId":"cmsPageRepositoryV1SavePost","parameters":[{"name":"$body","in":"body","schema":{"required":["page"],"properties":{"page":{"$ref":"#/definitions/cms-data-page-interface"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/cms-data-page-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"500":{"description":"Internal Server error","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/cmsPage/{id}":{"put":{"tags":["cmsPageRepositoryV1"],"description":"Save page.","operationId":"cmsPageRepositoryV1SavePut","parameters":[{"name":"id","in":"path","type":"string","required":true},{"name":"$body","in":"body","schema":{"required":["page"],"properties":{"page":{"$ref":"#/definitions/cms-data-page-interface"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/cms-data-page-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"500":{"description":"Internal Server error","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/cmsBlock/{blockId}":{"get":{"tags":["cmsBlockRepositoryV1"],"description":"Retrieve block.","operationId":"cmsBlockRepositoryV1GetByIdGet","parameters":[{"name":"blockId","in":"path","type":"integer","required":true}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/cms-data-block-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"500":{"description":"Internal Server error","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}},"delete":{"tags":["cmsBlockRepositoryV1"],"description":"Delete block by ID.","operationId":"cmsBlockRepositoryV1DeleteByIdDelete","parameters":[{"name":"blockId","in":"path","type":"integer","required":true}],"responses":{"200":{"description":"200 Success.","schema":{"type":"boolean","description":"true on success"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"500":{"description":"Internal Server error","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/cmsBlock/search":{"get":{"tags":["cmsBlockRepositoryV1"],"description":"Retrieve blocks matching the specified criteria.","operationId":"cmsBlockRepositoryV1GetListGet","parameters":[{"name":"searchCriteria[filterGroups][][filters][][field]","in":"query","type":"string","description":"Field"},{"name":"searchCriteria[filterGroups][][filters][][value]","in":"query","type":"string","description":"Value"},{"name":"searchCriteria[filterGroups][][filters][][conditionType]","in":"query","type":"string","description":"Condition type"},{"name":"searchCriteria[sortOrders][][field]","in":"query","type":"string","description":"Sorting field."},{"name":"searchCriteria[sortOrders][][direction]","in":"query","type":"string","description":"Sorting direction."},{"name":"searchCriteria[pageSize]","in":"query","type":"integer","description":"Page size."},{"name":"searchCriteria[currentPage]","in":"query","type":"integer","description":"Current page."}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/cms-data-block-search-results-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"500":{"description":"Internal Server error","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/cmsBlock":{"post":{"tags":["cmsBlockRepositoryV1"],"description":"Save block.","operationId":"cmsBlockRepositoryV1SavePost","parameters":[{"name":"$body","in":"body","schema":{"required":["block"],"properties":{"block":{"$ref":"#/definitions/cms-data-block-interface"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/cms-data-block-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"500":{"description":"Internal Server error","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/cmsBlock/{id}":{"put":{"tags":["cmsBlockRepositoryV1"],"description":"Save block.","operationId":"cmsBlockRepositoryV1SavePut","parameters":[{"name":"id","in":"path","type":"string","required":true},{"name":"$body","in":"body","schema":{"required":["block"],"properties":{"block":{"$ref":"#/definitions/cms-data-block-interface"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/cms-data-block-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"500":{"description":"Internal Server error","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/products":{"post":{"tags":["catalogProductRepositoryV1"],"description":"Create product","operationId":"catalogProductRepositoryV1SavePost","parameters":[{"name":"$body","in":"body","schema":{"required":["product"],"properties":{"product":{"$ref":"#/definitions/catalog-data-product-interface"},"saveOptions":{"type":"boolean"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/catalog-data-product-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}},"get":{"tags":["catalogProductRepositoryV1"],"description":"Get product list","operationId":"catalogProductRepositoryV1GetListGet","parameters":[{"name":"searchCriteria[filterGroups][][filters][][field]","in":"query","type":"string","description":"Field"},{"name":"searchCriteria[filterGroups][][filters][][value]","in":"query","type":"string","description":"Value"},{"name":"searchCriteria[filterGroups][][filters][][conditionType]","in":"query","type":"string","description":"Condition type"},{"name":"searchCriteria[sortOrders][][field]","in":"query","type":"string","description":"Sorting field."},{"name":"searchCriteria[sortOrders][][direction]","in":"query","type":"string","description":"Sorting direction."},{"name":"searchCriteria[pageSize]","in":"query","type":"integer","description":"Page size."},{"name":"searchCriteria[currentPage]","in":"query","type":"integer","description":"Current page."}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/catalog-data-product-search-results-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/products/{sku}":{"put":{"tags":["catalogProductRepositoryV1"],"description":"Create product","operationId":"catalogProductRepositoryV1SavePut","parameters":[{"name":"sku","in":"path","type":"string","required":true},{"name":"$body","in":"body","schema":{"required":["product"],"properties":{"product":{"$ref":"#/definitions/catalog-data-product-interface"},"saveOptions":{"type":"boolean"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/catalog-data-product-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}},"delete":{"tags":["catalogProductRepositoryV1"],"description":"","operationId":"catalogProductRepositoryV1DeleteByIdDelete","parameters":[{"name":"sku","in":"path","type":"string","required":true}],"responses":{"200":{"description":"200 Success.","schema":{"type":"boolean","description":"Will returned True if deleted"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}},"get":{"tags":["catalogProductRepositoryV1"],"description":"Get info about product by product SKU","operationId":"catalogProductRepositoryV1GetGet","parameters":[{"name":"sku","in":"path","type":"string","required":true},{"name":"editMode","in":"query","type":"boolean","required":false},{"name":"storeId","in":"query","type":"integer","required":false},{"name":"forceReload","in":"query","type":"boolean","required":false}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/catalog-data-product-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/products/attributes/types":{"get":{"tags":["catalogProductAttributeTypesListV1"],"description":"Retrieve list of product attribute types","operationId":"catalogProductAttributeTypesListV1GetItemsGet","responses":{"200":{"description":"200 Success.","schema":{"type":"array","items":{"$ref":"#/definitions/catalog-data-product-attribute-type-interface"}}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/products/attributes/{attributeCode}":{"get":{"tags":["catalogProductAttributeRepositoryV1"],"description":"Retrieve specific attribute","operationId":"catalogProductAttributeRepositoryV1GetGet","parameters":[{"name":"attributeCode","in":"path","type":"string","required":true}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/catalog-data-product-attribute-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}},"put":{"tags":["catalogProductAttributeRepositoryV1"],"description":"Save attribute data","operationId":"catalogProductAttributeRepositoryV1SavePut","parameters":[{"name":"attributeCode","in":"path","type":"string","required":true},{"name":"$body","in":"body","schema":{"required":["attribute"],"properties":{"attribute":{"$ref":"#/definitions/catalog-data-product-attribute-interface"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/catalog-data-product-attribute-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}},"delete":{"tags":["catalogProductAttributeRepositoryV1"],"description":"Delete Attribute by id","operationId":"catalogProductAttributeRepositoryV1DeleteByIdDelete","parameters":[{"name":"attributeCode","in":"path","type":"string","required":true}],"responses":{"200":{"description":"200 Success.","schema":{"type":"boolean"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/products/attributes":{"get":{"tags":["catalogProductAttributeRepositoryV1"],"description":"Retrieve all attributes for entity type","operationId":"catalogProductAttributeRepositoryV1GetListGet","parameters":[{"name":"searchCriteria[filterGroups][][filters][][field]","in":"query","type":"string","description":"Field"},{"name":"searchCriteria[filterGroups][][filters][][value]","in":"query","type":"string","description":"Value"},{"name":"searchCriteria[filterGroups][][filters][][conditionType]","in":"query","type":"string","description":"Condition type"},{"name":"searchCriteria[sortOrders][][field]","in":"query","type":"string","description":"Sorting field."},{"name":"searchCriteria[sortOrders][][direction]","in":"query","type":"string","description":"Sorting direction."},{"name":"searchCriteria[pageSize]","in":"query","type":"integer","description":"Page size."},{"name":"searchCriteria[currentPage]","in":"query","type":"integer","description":"Current page."}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/catalog-data-product-attribute-search-results-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}},"post":{"tags":["catalogProductAttributeRepositoryV1"],"description":"Save attribute data","operationId":"catalogProductAttributeRepositoryV1SavePost","parameters":[{"name":"$body","in":"body","schema":{"required":["attribute"],"properties":{"attribute":{"$ref":"#/definitions/catalog-data-product-attribute-interface"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/catalog-data-product-attribute-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/categories/attributes/{attributeCode}":{"get":{"tags":["catalogCategoryAttributeRepositoryV1"],"description":"Retrieve specific attribute","operationId":"catalogCategoryAttributeRepositoryV1GetGet","parameters":[{"name":"attributeCode","in":"path","type":"string","required":true}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/catalog-data-category-attribute-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/categories/attributes":{"get":{"tags":["catalogCategoryAttributeRepositoryV1"],"description":"Retrieve all attributes for entity type","operationId":"catalogCategoryAttributeRepositoryV1GetListGet","parameters":[{"name":"searchCriteria[filterGroups][][filters][][field]","in":"query","type":"string","description":"Field"},{"name":"searchCriteria[filterGroups][][filters][][value]","in":"query","type":"string","description":"Value"},{"name":"searchCriteria[filterGroups][][filters][][conditionType]","in":"query","type":"string","description":"Condition type"},{"name":"searchCriteria[sortOrders][][field]","in":"query","type":"string","description":"Sorting field."},{"name":"searchCriteria[sortOrders][][direction]","in":"query","type":"string","description":"Sorting direction."},{"name":"searchCriteria[pageSize]","in":"query","type":"integer","description":"Page size."},{"name":"searchCriteria[currentPage]","in":"query","type":"integer","description":"Current page."}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/catalog-data-category-attribute-search-results-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/categories/attributes/{attributeCode}/options":{"get":{"tags":["catalogCategoryAttributeOptionManagementV1"],"description":"Retrieve list of attribute options","operationId":"catalogCategoryAttributeOptionManagementV1GetItemsGet","parameters":[{"name":"attributeCode","in":"path","type":"string","required":true}],"responses":{"200":{"description":"200 Success.","schema":{"type":"array","items":{"$ref":"#/definitions/eav-data-attribute-option-interface"}}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/products/types":{"get":{"tags":["catalogProductTypeListV1"],"description":"Retrieve available product types","operationId":"catalogProductTypeListV1GetProductTypesGet","responses":{"200":{"description":"200 Success.","schema":{"type":"array","items":{"$ref":"#/definitions/catalog-data-product-type-interface"}}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/products/attribute-sets/sets/list":{"get":{"tags":["catalogAttributeSetRepositoryV1"],"description":"Retrieve list of Attribute Sets","operationId":"catalogAttributeSetRepositoryV1GetListGet","parameters":[{"name":"searchCriteria[filterGroups][][filters][][field]","in":"query","type":"string","description":"Field"},{"name":"searchCriteria[filterGroups][][filters][][value]","in":"query","type":"string","description":"Value"},{"name":"searchCriteria[filterGroups][][filters][][conditionType]","in":"query","type":"string","description":"Condition type"},{"name":"searchCriteria[sortOrders][][field]","in":"query","type":"string","description":"Sorting field."},{"name":"searchCriteria[sortOrders][][direction]","in":"query","type":"string","description":"Sorting direction."},{"name":"searchCriteria[pageSize]","in":"query","type":"integer","description":"Page size."},{"name":"searchCriteria[currentPage]","in":"query","type":"integer","description":"Current page."}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/eav-data-attribute-set-search-results-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/products/attribute-sets/{attributeSetId}":{"get":{"tags":["catalogAttributeSetRepositoryV1"],"description":"Retrieve attribute set information based on given ID","operationId":"catalogAttributeSetRepositoryV1GetGet","parameters":[{"name":"attributeSetId","in":"path","type":"integer","required":true}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/eav-data-attribute-set-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}},"delete":{"tags":["catalogAttributeSetRepositoryV1"],"description":"Remove attribute set by given ID","operationId":"catalogAttributeSetRepositoryV1DeleteByIdDelete","parameters":[{"name":"attributeSetId","in":"path","type":"integer","required":true}],"responses":{"200":{"description":"200 Success.","schema":{"type":"boolean"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}},"put":{"tags":["catalogAttributeSetRepositoryV1"],"description":"Save attribute set data","operationId":"catalogAttributeSetRepositoryV1SavePut","parameters":[{"name":"attributeSetId","in":"path","type":"string","required":true},{"name":"$body","in":"body","schema":{"required":["attributeSet"],"properties":{"attributeSet":{"$ref":"#/definitions/eav-data-attribute-set-interface"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/eav-data-attribute-set-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"500":{"description":"Internal Server error","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/products/attribute-sets":{"post":{"tags":["catalogAttributeSetManagementV1"],"description":"Create attribute set from data","operationId":"catalogAttributeSetManagementV1CreatePost","parameters":[{"name":"$body","in":"body","schema":{"required":["attributeSet","skeletonId"],"properties":{"attributeSet":{"$ref":"#/definitions/eav-data-attribute-set-interface"},"skeletonId":{"type":"integer"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/eav-data-attribute-set-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/products/attribute-sets/{attributeSetId}/attributes":{"get":{"tags":["catalogProductAttributeManagementV1"],"description":"Retrieve related attributes based on given attribute set ID","operationId":"catalogProductAttributeManagementV1GetAttributesGet","parameters":[{"name":"attributeSetId","in":"path","type":"string","required":true}],"responses":{"200":{"description":"200 Success.","schema":{"type":"array","items":{"$ref":"#/definitions/catalog-data-product-attribute-interface"}}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/products/attribute-sets/attributes":{"post":{"tags":["catalogProductAttributeManagementV1"],"description":"Assign attribute to attribute set","operationId":"catalogProductAttributeManagementV1AssignPost","parameters":[{"name":"$body","in":"body","schema":{"required":["attributeSetId","attributeGroupId","attributeCode","sortOrder"],"properties":{"attributeSetId":{"type":"integer"},"attributeGroupId":{"type":"integer"},"attributeCode":{"type":"string"},"sortOrder":{"type":"integer"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"type":"integer"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/products/attribute-sets/{attributeSetId}/attributes/{attributeCode}":{"delete":{"tags":["catalogProductAttributeManagementV1"],"description":"Remove attribute from attribute set","operationId":"catalogProductAttributeManagementV1UnassignDelete","parameters":[{"name":"attributeSetId","in":"path","type":"string","required":true},{"name":"attributeCode","in":"path","type":"string","required":true}],"responses":{"200":{"description":"200 Success.","schema":{"type":"boolean"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/products/attribute-sets/groups/list":{"get":{"tags":["catalogProductAttributeGroupRepositoryV1"],"description":"Retrieve list of attribute groups","operationId":"catalogProductAttributeGroupRepositoryV1GetListGet","parameters":[{"name":"searchCriteria[filterGroups][][filters][][field]","in":"query","type":"string","description":"Field"},{"name":"searchCriteria[filterGroups][][filters][][value]","in":"query","type":"string","description":"Value"},{"name":"searchCriteria[filterGroups][][filters][][conditionType]","in":"query","type":"string","description":"Condition type"},{"name":"searchCriteria[sortOrders][][field]","in":"query","type":"string","description":"Sorting field."},{"name":"searchCriteria[sortOrders][][direction]","in":"query","type":"string","description":"Sorting direction."},{"name":"searchCriteria[pageSize]","in":"query","type":"integer","description":"Page size."},{"name":"searchCriteria[currentPage]","in":"query","type":"integer","description":"Current page."}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/eav-data-attribute-group-search-results-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/products/attribute-sets/groups":{"post":{"tags":["catalogProductAttributeGroupRepositoryV1"],"description":"Save attribute group","operationId":"catalogProductAttributeGroupRepositoryV1SavePost","parameters":[{"name":"$body","in":"body","schema":{"required":["group"],"properties":{"group":{"$ref":"#/definitions/eav-data-attribute-group-interface"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/eav-data-attribute-group-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/products/attribute-sets/{attributeSetId}/groups":{"put":{"tags":["catalogProductAttributeGroupRepositoryV1"],"description":"Save attribute group","operationId":"catalogProductAttributeGroupRepositoryV1SavePut","parameters":[{"name":"attributeSetId","in":"path","type":"string","required":true},{"name":"$body","in":"body","schema":{"required":["group"],"properties":{"group":{"$ref":"#/definitions/eav-data-attribute-group-interface"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/eav-data-attribute-group-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/products/attribute-sets/groups/{groupId}":{"delete":{"tags":["catalogProductAttributeGroupRepositoryV1"],"description":"Remove attribute group by id","operationId":"catalogProductAttributeGroupRepositoryV1DeleteByIdDelete","parameters":[{"name":"groupId","in":"path","type":"integer","required":true}],"responses":{"200":{"description":"200 Success.","schema":{"type":"boolean"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/products/attributes/{attributeCode}/options":{"get":{"tags":["catalogProductAttributeOptionManagementV1"],"description":"Retrieve list of attribute options","operationId":"catalogProductAttributeOptionManagementV1GetItemsGet","parameters":[{"name":"attributeCode","in":"path","type":"string","required":true}],"responses":{"200":{"description":"200 Success.","schema":{"type":"array","items":{"$ref":"#/definitions/eav-data-attribute-option-interface"}}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}},"post":{"tags":["catalogProductAttributeOptionManagementV1"],"description":"Add option to attribute","operationId":"catalogProductAttributeOptionManagementV1AddPost","parameters":[{"name":"attributeCode","in":"path","type":"string","required":true},{"name":"$body","in":"body","schema":{"required":["option"],"properties":{"option":{"$ref":"#/definitions/eav-data-attribute-option-interface"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"type":"boolean"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/products/attributes/{attributeCode}/options/{optionId}":{"delete":{"tags":["catalogProductAttributeOptionManagementV1"],"description":"Delete option from attribute","operationId":"catalogProductAttributeOptionManagementV1DeleteDelete","parameters":[{"name":"attributeCode","in":"path","type":"string","required":true},{"name":"optionId","in":"path","type":"string","required":true}],"responses":{"200":{"description":"200 Success.","schema":{"type":"boolean"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/products/media/types/{attributeSetName}":{"get":{"tags":["catalogProductMediaAttributeManagementV1"],"description":"Retrieve the list of media attributes (fronted input type is media_image) assigned to the given attribute set.","operationId":"catalogProductMediaAttributeManagementV1GetListGet","parameters":[{"name":"attributeSetName","in":"path","type":"string","required":true}],"responses":{"200":{"description":"200 Success.","schema":{"type":"array","description":"list of media attributes","items":{"$ref":"#/definitions/catalog-data-product-attribute-interface"}}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/products/{sku}/media/{entryId}":{"get":{"tags":["catalogProductAttributeMediaGalleryManagementV1"],"description":"Return information about gallery entry","operationId":"catalogProductAttributeMediaGalleryManagementV1GetGet","parameters":[{"name":"sku","in":"path","type":"string","required":true},{"name":"entryId","in":"path","type":"integer","required":true}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/catalog-data-product-attribute-media-gallery-entry-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}},"put":{"tags":["catalogProductAttributeMediaGalleryManagementV1"],"description":"Update gallery entry","operationId":"catalogProductAttributeMediaGalleryManagementV1UpdatePut","parameters":[{"name":"sku","in":"path","type":"string","required":true},{"name":"entryId","in":"path","type":"string","required":true},{"name":"$body","in":"body","schema":{"required":["entry"],"properties":{"entry":{"$ref":"#/definitions/catalog-data-product-attribute-media-gallery-entry-interface"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"type":"boolean"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}},"delete":{"tags":["catalogProductAttributeMediaGalleryManagementV1"],"description":"Remove gallery entry","operationId":"catalogProductAttributeMediaGalleryManagementV1RemoveDelete","parameters":[{"name":"sku","in":"path","type":"string","required":true},{"name":"entryId","in":"path","type":"integer","required":true}],"responses":{"200":{"description":"200 Success.","schema":{"type":"boolean"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/products/{sku}/media":{"post":{"tags":["catalogProductAttributeMediaGalleryManagementV1"],"description":"Create new gallery entry","operationId":"catalogProductAttributeMediaGalleryManagementV1CreatePost","parameters":[{"name":"sku","in":"path","type":"string","required":true},{"name":"$body","in":"body","schema":{"required":["entry"],"properties":{"entry":{"$ref":"#/definitions/catalog-data-product-attribute-media-gallery-entry-interface"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"type":"integer","description":"gallery entry ID"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}},"get":{"tags":["catalogProductAttributeMediaGalleryManagementV1"],"description":"Retrieve the list of gallery entries associated with given product","operationId":"catalogProductAttributeMediaGalleryManagementV1GetListGet","parameters":[{"name":"sku","in":"path","type":"string","required":true}],"responses":{"200":{"description":"200 Success.","schema":{"type":"array","items":{"$ref":"#/definitions/catalog-data-product-attribute-media-gallery-entry-interface"}}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/products/{sku}/group-prices/{customerGroupId}/tiers":{"get":{"tags":["catalogProductTierPriceManagementV1"],"description":"Get tier price of product","operationId":"catalogProductTierPriceManagementV1GetListGet","parameters":[{"name":"sku","in":"path","type":"string","required":true},{"name":"customerGroupId","in":"path","type":"string","required":true,"description":"'all' can be used to specify 'ALL GROUPS'"}],"responses":{"200":{"description":"200 Success.","schema":{"type":"array","items":{"$ref":"#/definitions/catalog-data-product-tier-price-interface"}}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/products/{sku}/group-prices/{customerGroupId}/tiers/{qty}/price/{price}":{"post":{"tags":["catalogProductTierPriceManagementV1"],"description":"Create tier price for product","operationId":"catalogProductTierPriceManagementV1AddPost","parameters":[{"name":"sku","in":"path","type":"string","required":true},{"name":"customerGroupId","in":"path","type":"string","required":true,"description":"'all' can be used to specify 'ALL GROUPS'"},{"name":"price","in":"path","type":"number","required":true},{"name":"qty","in":"path","type":"number","required":true}],"responses":{"200":{"description":"200 Success.","schema":{"type":"boolean"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/products/{sku}/group-prices/{customerGroupId}/tiers/{qty}":{"delete":{"tags":["catalogProductTierPriceManagementV1"],"description":"Remove tier price from product","operationId":"catalogProductTierPriceManagementV1RemoveDelete","parameters":[{"name":"sku","in":"path","type":"string","required":true},{"name":"customerGroupId","in":"path","type":"string","required":true,"description":"'all' can be used to specify 'ALL GROUPS'"},{"name":"qty","in":"path","type":"number","required":true}],"responses":{"200":{"description":"200 Success.","schema":{"type":"boolean"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/products/tier-prices-information":{"post":{"tags":["catalogTierPriceStorageV1"],"description":"Return product prices. In case of at least one of skus is not found exception will be thrown.","operationId":"catalogTierPriceStorageV1GetPost","parameters":[{"name":"$body","in":"body","schema":{"required":["skus"],"properties":{"skus":{"type":"array","items":{"type":"string"}}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"type":"array","items":{"$ref":"#/definitions/catalog-data-tier-price-interface"}}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/products/tier-prices":{"post":{"tags":["catalogTierPriceStorageV1"],"description":"Add or update product prices. If any items will have invalid price, price type, website id, sku, customer group or quantity, they will be marked as failed and excluded from update list and \\Magento\\Catalog\\Api\\Data\\PriceUpdateResultInterface[] with problem description will be returned. If there were no failed items during update empty array will be returned. If error occurred during the update exception will be thrown.","operationId":"catalogTierPriceStorageV1UpdatePost","parameters":[{"name":"$body","in":"body","schema":{"required":["prices"],"properties":{"prices":{"type":"array","items":{"$ref":"#/definitions/catalog-data-tier-price-interface"}}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"type":"array","items":{"$ref":"#/definitions/catalog-data-price-update-result-interface"}}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}},"put":{"tags":["catalogTierPriceStorageV1"],"description":"Remove existing tier prices and replace them with the new ones. If any items will have invalid price, price type, website id, sku, customer group or quantity, they will be marked as failed and excluded from replace list and \\Magento\\Catalog\\Api\\Data\\PriceUpdateResultInterface[] with problem description will be returned. If there were no failed items during update empty array will be returned. If error occurred during the update exception will be thrown.","operationId":"catalogTierPriceStorageV1ReplacePut","parameters":[{"name":"$body","in":"body","schema":{"required":["prices"],"properties":{"prices":{"type":"array","items":{"$ref":"#/definitions/catalog-data-tier-price-interface"}}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"type":"array","items":{"$ref":"#/definitions/catalog-data-price-update-result-interface"}}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/products/tier-prices-delete":{"post":{"tags":["catalogTierPriceStorageV1"],"description":"Delete product tier prices. If any items will have invalid price, price type, website id, sku, customer group or quantity, they will be marked as failed and excluded from delete list and \\Magento\\Catalog\\Api\\Data\\PriceUpdateResultInterface[] with problem description will be returned. If there were no failed items during update empty array will be returned. If error occurred during the update exception will be thrown.","operationId":"catalogTierPriceStorageV1DeletePost","parameters":[{"name":"$body","in":"body","schema":{"required":["prices"],"properties":{"prices":{"type":"array","items":{"$ref":"#/definitions/catalog-data-tier-price-interface"}}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"type":"array","items":{"$ref":"#/definitions/catalog-data-price-update-result-interface"}}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/products/base-prices-information":{"post":{"tags":["catalogBasePriceStorageV1"],"description":"Return product prices. In case of at least one of skus is not found exception will be thrown.","operationId":"catalogBasePriceStorageV1GetPost","parameters":[{"name":"$body","in":"body","schema":{"required":["skus"],"properties":{"skus":{"type":"array","items":{"type":"string"}}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"type":"array","items":{"$ref":"#/definitions/catalog-data-base-price-interface"}}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/products/base-prices":{"post":{"tags":["catalogBasePriceStorageV1"],"description":"Add or update product prices. Input item should correspond \\Magento\\Catalog\\Api\\Data\\CostInterface. If any items will have invalid price, store id or sku, they will be marked as failed and excluded from update list and \\Magento\\Catalog\\Api\\Data\\PriceUpdateResultInterface[] with problem description will be returned. If there were no failed items during update empty array will be returned. If error occurred during the update exception will be thrown.","operationId":"catalogBasePriceStorageV1UpdatePost","parameters":[{"name":"$body","in":"body","schema":{"required":["prices"],"properties":{"prices":{"type":"array","items":{"$ref":"#/definitions/catalog-data-base-price-interface"}}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"type":"array","items":{"$ref":"#/definitions/catalog-data-price-update-result-interface"}}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/products/cost-information":{"post":{"tags":["catalogCostStorageV1"],"description":"Return product prices. In case of at least one of skus is not found exception will be thrown.","operationId":"catalogCostStorageV1GetPost","parameters":[{"name":"$body","in":"body","schema":{"required":["skus"],"properties":{"skus":{"type":"array","items":{"type":"string"}}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"type":"array","items":{"$ref":"#/definitions/catalog-data-cost-interface"}}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/products/cost":{"post":{"tags":["catalogCostStorageV1"],"description":"Add or update product cost. Input item should correspond to \\Magento\\Catalog\\Api\\Data\\CostInterface. If any items will have invalid cost, store id or sku, they will be marked as failed and excluded from update list and \\Magento\\Catalog\\Api\\Data\\PriceUpdateResultInterface[] with problem description will be returned. If there were no failed items during update empty array will be returned. If error occurred during the update exception will be thrown.","operationId":"catalogCostStorageV1UpdatePost","parameters":[{"name":"$body","in":"body","schema":{"required":["prices"],"properties":{"prices":{"type":"array","items":{"$ref":"#/definitions/catalog-data-cost-interface"}}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"type":"array","items":{"$ref":"#/definitions/catalog-data-price-update-result-interface"}}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/products/cost-delete":{"post":{"tags":["catalogCostStorageV1"],"description":"Delete product cost. In case of at least one of skus is not found exception will be thrown. If error occurred during the delete exception will be thrown.","operationId":"catalogCostStorageV1DeletePost","parameters":[{"name":"$body","in":"body","schema":{"required":["skus"],"properties":{"skus":{"type":"array","items":{"type":"string"}}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"type":"boolean","description":"Will return True if deleted."}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/products/special-price-information":{"post":{"tags":["catalogSpecialPriceStorageV1"],"description":"Return product's special price. In case of at least one of skus is not found exception will be thrown.","operationId":"catalogSpecialPriceStorageV1GetPost","parameters":[{"name":"$body","in":"body","schema":{"required":["skus"],"properties":{"skus":{"type":"array","items":{"type":"string"}}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"type":"array","items":{"$ref":"#/definitions/catalog-data-special-price-interface"}}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/products/special-price":{"post":{"tags":["catalogSpecialPriceStorageV1"],"description":"Add or update product's special price. If any items will have invalid price, store id, sku or dates, they will be marked as failed and excluded from update list and \\Magento\\Catalog\\Api\\Data\\PriceUpdateResultInterface[] with problem description will be returned. If there were no failed items during update empty array will be returned. If error occurred during the update exception will be thrown.","operationId":"catalogSpecialPriceStorageV1UpdatePost","parameters":[{"name":"$body","in":"body","schema":{"required":["prices"],"properties":{"prices":{"type":"array","items":{"$ref":"#/definitions/catalog-data-special-price-interface"}}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"type":"array","items":{"$ref":"#/definitions/catalog-data-price-update-result-interface"}}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/products/special-price-delete":{"post":{"tags":["catalogSpecialPriceStorageV1"],"description":"Delete product's special price. If any items will have invalid price, store id, sku or dates, they will be marked as failed and excluded from delete list and \\Magento\\Catalog\\Api\\Data\\PriceUpdateResultInterface[] with problem description will be returned. If there were no failed items during update empty array will be returned. If error occurred during the delete exception will be thrown.","operationId":"catalogSpecialPriceStorageV1DeletePost","parameters":[{"name":"$body","in":"body","schema":{"required":["prices"],"properties":{"prices":{"type":"array","items":{"$ref":"#/definitions/catalog-data-special-price-interface"}}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"type":"array","items":{"$ref":"#/definitions/catalog-data-price-update-result-interface"}}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/categories/{categoryId}":{"delete":{"tags":["catalogCategoryRepositoryV1"],"description":"Delete category by identifier","operationId":"catalogCategoryRepositoryV1DeleteByIdentifierDelete","parameters":[{"name":"categoryId","in":"path","type":"integer","required":true}],"responses":{"200":{"description":"200 Success.","schema":{"type":"boolean","description":"Will returned True if deleted"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}},"get":{"tags":["catalogCategoryRepositoryV1"],"description":"Get info about category by category id","operationId":"catalogCategoryRepositoryV1GetGet","parameters":[{"name":"categoryId","in":"path","type":"integer","required":true},{"name":"storeId","in":"query","type":"integer","required":false}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/catalog-data-category-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/categories":{"post":{"tags":["catalogCategoryRepositoryV1"],"description":"Create category service","operationId":"catalogCategoryRepositoryV1SavePost","parameters":[{"name":"$body","in":"body","schema":{"required":["category"],"properties":{"category":{"$ref":"#/definitions/catalog-data-category-interface"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/catalog-data-category-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}},"get":{"tags":["catalogCategoryManagementV1"],"description":"Retrieve list of categories","operationId":"catalogCategoryManagementV1GetTreeGet","parameters":[{"name":"rootCategoryId","in":"query","type":"integer","required":false},{"name":"depth","in":"query","type":"integer","required":false}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/catalog-data-category-tree-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/categories/{id}":{"put":{"tags":["catalogCategoryRepositoryV1"],"description":"Create category service","operationId":"catalogCategoryRepositoryV1SavePut","parameters":[{"name":"id","in":"path","type":"string","required":true},{"name":"$body","in":"body","schema":{"required":["category"],"properties":{"category":{"$ref":"#/definitions/catalog-data-category-interface"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/catalog-data-category-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/categories/{categoryId}/move":{"put":{"tags":["catalogCategoryManagementV1"],"description":"Move category","operationId":"catalogCategoryManagementV1MovePut","parameters":[{"name":"categoryId","in":"path","type":"integer","required":true},{"name":"$body","in":"body","schema":{"required":["parentId"],"properties":{"parentId":{"type":"integer"},"afterId":{"type":"integer"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"type":"boolean"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"500":{"description":"Internal Server error","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/categories/list":{"get":{"tags":["catalogCategoryListV1"],"description":"Get category list","operationId":"catalogCategoryListV1GetListGet","parameters":[{"name":"searchCriteria[filterGroups][][filters][][field]","in":"query","type":"string","description":"Field"},{"name":"searchCriteria[filterGroups][][filters][][value]","in":"query","type":"string","description":"Value"},{"name":"searchCriteria[filterGroups][][filters][][conditionType]","in":"query","type":"string","description":"Condition type"},{"name":"searchCriteria[sortOrders][][field]","in":"query","type":"string","description":"Sorting field."},{"name":"searchCriteria[sortOrders][][direction]","in":"query","type":"string","description":"Sorting direction."},{"name":"searchCriteria[pageSize]","in":"query","type":"integer","description":"Page size."},{"name":"searchCriteria[currentPage]","in":"query","type":"integer","description":"Current page."}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/catalog-data-category-search-results-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/products/options/types":{"get":{"tags":["catalogProductCustomOptionTypeListV1"],"description":"Get custom option types","operationId":"catalogProductCustomOptionTypeListV1GetItemsGet","responses":{"200":{"description":"200 Success.","schema":{"type":"array","items":{"$ref":"#/definitions/catalog-data-product-custom-option-type-interface"}}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/products/{sku}/options":{"get":{"tags":["catalogProductCustomOptionRepositoryV1"],"description":"Get the list of custom options for a specific product","operationId":"catalogProductCustomOptionRepositoryV1GetListGet","parameters":[{"name":"sku","in":"path","type":"string","required":true}],"responses":{"200":{"description":"200 Success.","schema":{"type":"array","items":{"$ref":"#/definitions/catalog-data-product-custom-option-interface"}}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/products/{sku}/options/{optionId}":{"get":{"tags":["catalogProductCustomOptionRepositoryV1"],"description":"Get custom option for a specific product","operationId":"catalogProductCustomOptionRepositoryV1GetGet","parameters":[{"name":"sku","in":"path","type":"string","required":true},{"name":"optionId","in":"path","type":"integer","required":true}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/catalog-data-product-custom-option-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}},"delete":{"tags":["catalogProductCustomOptionRepositoryV1"],"description":"","operationId":"catalogProductCustomOptionRepositoryV1DeleteByIdentifierDelete","parameters":[{"name":"sku","in":"path","type":"string","required":true},{"name":"optionId","in":"path","type":"integer","required":true}],"responses":{"200":{"description":"200 Success.","schema":{"type":"boolean"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/products/options":{"post":{"tags":["catalogProductCustomOptionRepositoryV1"],"description":"Save Custom Option","operationId":"catalogProductCustomOptionRepositoryV1SavePost","parameters":[{"name":"$body","in":"body","schema":{"required":["option"],"properties":{"option":{"$ref":"#/definitions/catalog-data-product-custom-option-interface"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/catalog-data-product-custom-option-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/products/options/{optionId}":{"put":{"tags":["catalogProductCustomOptionRepositoryV1"],"description":"Save Custom Option","operationId":"catalogProductCustomOptionRepositoryV1SavePut","parameters":[{"name":"optionId","in":"path","type":"string","required":true},{"name":"$body","in":"body","schema":{"required":["option"],"properties":{"option":{"$ref":"#/definitions/catalog-data-product-custom-option-interface"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/catalog-data-product-custom-option-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/products/links/types":{"get":{"tags":["catalogProductLinkTypeListV1"],"description":"Retrieve information about available product link types","operationId":"catalogProductLinkTypeListV1GetItemsGet","responses":{"200":{"description":"200 Success.","schema":{"type":"array","items":{"$ref":"#/definitions/catalog-data-product-link-type-interface"}}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/products/links/{type}/attributes":{"get":{"tags":["catalogProductLinkTypeListV1"],"description":"Provide a list of the product link type attributes","operationId":"catalogProductLinkTypeListV1GetItemAttributesGet","parameters":[{"name":"type","in":"path","type":"string","required":true}],"responses":{"200":{"description":"200 Success.","schema":{"type":"array","items":{"$ref":"#/definitions/catalog-data-product-link-attribute-interface"}}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/products/{sku}/links/{type}":{"get":{"tags":["catalogProductLinkManagementV1"],"description":"Provide the list of links for a specific product","operationId":"catalogProductLinkManagementV1GetLinkedItemsByTypeGet","parameters":[{"name":"sku","in":"path","type":"string","required":true},{"name":"type","in":"path","type":"string","required":true}],"responses":{"200":{"description":"200 Success.","schema":{"type":"array","items":{"$ref":"#/definitions/catalog-data-product-link-interface"}}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/products/{sku}/links":{"post":{"tags":["catalogProductLinkManagementV1"],"description":"Assign a product link to another product","operationId":"catalogProductLinkManagementV1SetProductLinksPost","parameters":[{"name":"sku","in":"path","type":"string","required":true},{"name":"$body","in":"body","schema":{"required":["items"],"properties":{"items":{"type":"array","items":{"$ref":"#/definitions/catalog-data-product-link-interface"}}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"type":"boolean"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}},"put":{"tags":["catalogProductLinkRepositoryV1"],"description":"Save product link","operationId":"catalogProductLinkRepositoryV1SavePut","parameters":[{"name":"sku","in":"path","type":"string","required":true},{"name":"$body","in":"body","schema":{"required":["entity"],"properties":{"entity":{"$ref":"#/definitions/catalog-data-product-link-interface"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"type":"boolean"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/products/{sku}/links/{type}/{linkedProductSku}":{"delete":{"tags":["catalogProductLinkRepositoryV1"],"description":"","operationId":"catalogProductLinkRepositoryV1DeleteByIdDelete","parameters":[{"name":"sku","in":"path","type":"string","required":true},{"name":"type","in":"path","type":"string","required":true},{"name":"linkedProductSku","in":"path","type":"string","required":true}],"responses":{"200":{"description":"200 Success.","schema":{"type":"boolean"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/categories/{categoryId}/products":{"get":{"tags":["catalogCategoryLinkManagementV1"],"description":"Get products assigned to category","operationId":"catalogCategoryLinkManagementV1GetAssignedProductsGet","parameters":[{"name":"categoryId","in":"path","type":"integer","required":true}],"responses":{"200":{"description":"200 Success.","schema":{"type":"array","items":{"$ref":"#/definitions/catalog-data-category-product-link-interface"}}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}},"post":{"tags":["catalogCategoryLinkRepositoryV1"],"description":"Assign a product to the required category","operationId":"catalogCategoryLinkRepositoryV1SavePost","parameters":[{"name":"categoryId","in":"path","type":"string","required":true},{"name":"$body","in":"body","schema":{"required":["productLink"],"properties":{"productLink":{"$ref":"#/definitions/catalog-data-category-product-link-interface"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"type":"boolean","description":"will returned True if assigned"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}},"put":{"tags":["catalogCategoryLinkRepositoryV1"],"description":"Assign a product to the required category","operationId":"catalogCategoryLinkRepositoryV1SavePut","parameters":[{"name":"categoryId","in":"path","type":"string","required":true},{"name":"$body","in":"body","schema":{"required":["productLink"],"properties":{"productLink":{"$ref":"#/definitions/catalog-data-category-product-link-interface"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"type":"boolean","description":"will returned True if assigned"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/categories/{categoryId}/products/{sku}":{"delete":{"tags":["catalogCategoryLinkRepositoryV1"],"description":"Remove the product assignment from the category by category id and sku","operationId":"catalogCategoryLinkRepositoryV1DeleteByIdsDelete","parameters":[{"name":"categoryId","in":"path","type":"string","required":true},{"name":"sku","in":"path","type":"string","required":true}],"responses":{"200":{"description":"200 Success.","schema":{"type":"boolean","description":"will returned True if products successfully deleted"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/products/{sku}/websites":{"post":{"tags":["catalogProductWebsiteLinkRepositoryV1"],"description":"Assign a product to the website","operationId":"catalogProductWebsiteLinkRepositoryV1SavePost","parameters":[{"name":"sku","in":"path","type":"string","required":true},{"name":"$body","in":"body","schema":{"required":["productWebsiteLink"],"properties":{"productWebsiteLink":{"$ref":"#/definitions/catalog-data-product-website-link-interface"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"type":"boolean","description":"will returned True if website successfully assigned to product"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}},"put":{"tags":["catalogProductWebsiteLinkRepositoryV1"],"description":"Assign a product to the website","operationId":"catalogProductWebsiteLinkRepositoryV1SavePut","parameters":[{"name":"sku","in":"path","type":"string","required":true},{"name":"$body","in":"body","schema":{"required":["productWebsiteLink"],"properties":{"productWebsiteLink":{"$ref":"#/definitions/catalog-data-product-website-link-interface"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"type":"boolean","description":"will returned True if website successfully assigned to product"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/products/{sku}/websites/{websiteId}":{"delete":{"tags":["catalogProductWebsiteLinkRepositoryV1"],"description":"Remove the website assignment from the product by product sku","operationId":"catalogProductWebsiteLinkRepositoryV1DeleteByIdDelete","parameters":[{"name":"sku","in":"path","type":"string","required":true},{"name":"websiteId","in":"path","type":"integer","required":true}],"responses":{"200":{"description":"200 Success.","schema":{"type":"boolean","description":"will returned True if website successfully unassigned from product"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/products-render-info":{"get":{"tags":["catalogProductRenderListV1"],"description":"Collect and retrieve the list of product render info This info contains raw prices and formated prices, product name, stock status, store_id, etc","operationId":"catalogProductRenderListV1GetListGet","parameters":[{"name":"searchCriteria[filterGroups][][filters][][field]","in":"query","type":"string","description":"Field"},{"name":"searchCriteria[filterGroups][][filters][][value]","in":"query","type":"string","description":"Value"},{"name":"searchCriteria[filterGroups][][filters][][conditionType]","in":"query","type":"string","description":"Condition type"},{"name":"searchCriteria[sortOrders][][field]","in":"query","type":"string","description":"Sorting field."},{"name":"searchCriteria[sortOrders][][direction]","in":"query","type":"string","description":"Sorting direction."},{"name":"searchCriteria[pageSize]","in":"query","type":"integer","description":"Page size."},{"name":"searchCriteria[currentPage]","in":"query","type":"integer","description":"Current page."},{"name":"storeId","in":"query","type":"integer","required":true},{"name":"currencyCode","in":"query","type":"string","required":true}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/catalog-data-product-render-search-results-interface"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/stockItems/{productSku}":{"get":{"tags":["catalogInventoryStockRegistryV1"],"description":"","operationId":"catalogInventoryStockRegistryV1GetStockItemBySkuGet","parameters":[{"name":"productSku","in":"path","type":"string","required":true},{"name":"scopeId","in":"query","type":"integer","required":false}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/catalog-inventory-data-stock-item-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/products/{productSku}/stockItems/{itemId}":{"put":{"tags":["catalogInventoryStockRegistryV1"],"description":"","operationId":"catalogInventoryStockRegistryV1UpdateStockItemBySkuPut","parameters":[{"name":"productSku","in":"path","type":"string","required":true},{"name":"itemId","in":"path","type":"string","required":true},{"name":"$body","in":"body","schema":{"required":["stockItem"],"properties":{"stockItem":{"$ref":"#/definitions/catalog-inventory-data-stock-item-interface"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"type":"integer"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/stockItems/lowStock/":{"get":{"tags":["catalogInventoryStockRegistryV1"],"description":"Retrieves a list of SKU's with low inventory qty","operationId":"catalogInventoryStockRegistryV1GetLowStockItemsGet","parameters":[{"name":"scopeId","in":"query","type":"integer","required":true},{"name":"qty","in":"query","type":"number","required":true},{"name":"currentPage","in":"query","type":"integer","required":false},{"name":"pageSize","in":"query","type":"integer","required":false}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/catalog-inventory-data-stock-status-collection-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/stockStatuses/{productSku}":{"get":{"tags":["catalogInventoryStockRegistryV1"],"description":"","operationId":"catalogInventoryStockRegistryV1GetStockStatusBySkuGet","parameters":[{"name":"productSku","in":"path","type":"string","required":true},{"name":"scopeId","in":"query","type":"integer","required":false}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/catalog-inventory-data-stock-status-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/bundle-products/{sku}/links/{optionId}":{"post":{"tags":["bundleProductLinkManagementV1"],"description":"Add child product to specified Bundle option by product sku","operationId":"bundleProductLinkManagementV1AddChildByProductSkuPost","parameters":[{"name":"sku","in":"path","type":"string","required":true},{"name":"optionId","in":"path","type":"integer","required":true},{"name":"$body","in":"body","schema":{"required":["linkedProduct"],"properties":{"linkedProduct":{"$ref":"#/definitions/bundle-data-link-interface"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"type":"integer"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/bundle-products/{sku}/links/{id}":{"put":{"tags":["bundleProductLinkManagementV1"],"description":"","operationId":"bundleProductLinkManagementV1SaveChildPut","parameters":[{"name":"sku","in":"path","type":"string","required":true},{"name":"id","in":"path","type":"string","required":true},{"name":"$body","in":"body","schema":{"required":["linkedProduct"],"properties":{"linkedProduct":{"$ref":"#/definitions/bundle-data-link-interface"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"type":"boolean"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/bundle-products/{productSku}/children":{"get":{"tags":["bundleProductLinkManagementV1"],"description":"Get all children for Bundle product","operationId":"bundleProductLinkManagementV1GetChildrenGet","parameters":[{"name":"productSku","in":"path","type":"string","required":true},{"name":"optionId","in":"query","type":"integer","required":false}],"responses":{"200":{"description":"200 Success.","schema":{"type":"array","items":{"$ref":"#/definitions/bundle-data-link-interface"}}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/bundle-products/{sku}/options/{optionId}/children/{childSku}":{"delete":{"tags":["bundleProductLinkManagementV1"],"description":"Remove product from Bundle product option","operationId":"bundleProductLinkManagementV1RemoveChildDelete","parameters":[{"name":"sku","in":"path","type":"string","required":true},{"name":"optionId","in":"path","type":"integer","required":true},{"name":"childSku","in":"path","type":"string","required":true}],"responses":{"200":{"description":"200 Success.","schema":{"type":"boolean"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/bundle-products/{sku}/options/all":{"get":{"tags":["bundleProductOptionRepositoryV1"],"description":"Get all options for bundle product","operationId":"bundleProductOptionRepositoryV1GetListGet","parameters":[{"name":"sku","in":"path","type":"string","required":true}],"responses":{"200":{"description":"200 Success.","schema":{"type":"array","items":{"$ref":"#/definitions/bundle-data-option-interface"}}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/bundle-products/{sku}/options/{optionId}":{"get":{"tags":["bundleProductOptionRepositoryV1"],"description":"Get option for bundle product","operationId":"bundleProductOptionRepositoryV1GetGet","parameters":[{"name":"sku","in":"path","type":"string","required":true},{"name":"optionId","in":"path","type":"integer","required":true}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/bundle-data-option-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}},"delete":{"tags":["bundleProductOptionRepositoryV1"],"description":"Remove bundle option","operationId":"bundleProductOptionRepositoryV1DeleteByIdDelete","parameters":[{"name":"sku","in":"path","type":"string","required":true},{"name":"optionId","in":"path","type":"integer","required":true}],"responses":{"200":{"description":"200 Success.","schema":{"type":"boolean"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/bundle-products/options/types":{"get":{"tags":["bundleProductOptionTypeListV1"],"description":"Get all types for options for bundle products","operationId":"bundleProductOptionTypeListV1GetItemsGet","responses":{"200":{"description":"200 Success.","schema":{"type":"array","items":{"$ref":"#/definitions/bundle-data-option-type-interface"}}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/bundle-products/options/add":{"post":{"tags":["bundleProductOptionManagementV1"],"description":"Add new option for bundle product","operationId":"bundleProductOptionManagementV1SavePost","parameters":[{"name":"$body","in":"body","schema":{"required":["option"],"properties":{"option":{"$ref":"#/definitions/bundle-data-option-interface"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"type":"integer"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/bundle-products/options/{optionId}":{"put":{"tags":["bundleProductOptionManagementV1"],"description":"Add new option for bundle product","operationId":"bundleProductOptionManagementV1SavePut","parameters":[{"name":"optionId","in":"path","type":"string","required":true},{"name":"$body","in":"body","schema":{"required":["option"],"properties":{"option":{"$ref":"#/definitions/bundle-data-option-interface"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"type":"integer"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/carts/{cartId}":{"get":{"tags":["quoteCartRepositoryV1"],"description":"Enables an administrative user to return information for a specified cart.","operationId":"quoteCartRepositoryV1GetGet","parameters":[{"name":"cartId","in":"path","type":"integer","required":true}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/quote-data-cart-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}},"put":{"tags":["quoteCartManagementV1"],"description":"Assigns a specified customer to a specified shopping cart.","operationId":"quoteCartManagementV1AssignCustomerPut","parameters":[{"name":"cartId","in":"path","type":"integer","required":true,"description":"The cart ID."},{"name":"$body","in":"body","schema":{"required":["customerId","storeId"],"properties":{"customerId":{"type":"integer","description":"The customer ID."},"storeId":{"type":"integer"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"type":"boolean"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/carts/search":{"get":{"tags":["quoteCartRepositoryV1"],"description":"Enables administrative users to list carts that match specified search criteria. This call returns an array of objects, but detailed information about each object\u2019s attributes might not be included. See http://devdocs.magento.com/codelinks/attributes.html#CartRepositoryInterface to determine which call to use to get detailed information about all attributes for an object.","operationId":"quoteCartRepositoryV1GetListGet","parameters":[{"name":"searchCriteria[filterGroups][][filters][][field]","in":"query","type":"string","description":"Field"},{"name":"searchCriteria[filterGroups][][filters][][value]","in":"query","type":"string","description":"Value"},{"name":"searchCriteria[filterGroups][][filters][][conditionType]","in":"query","type":"string","description":"Condition type"},{"name":"searchCriteria[sortOrders][][field]","in":"query","type":"string","description":"Sorting field."},{"name":"searchCriteria[sortOrders][][direction]","in":"query","type":"string","description":"Sorting direction."},{"name":"searchCriteria[pageSize]","in":"query","type":"integer","description":"Page size."},{"name":"searchCriteria[currentPage]","in":"query","type":"integer","description":"Current page."}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/quote-data-cart-search-results-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/carts/mine":{"put":{"tags":["quoteCartRepositoryV1"],"description":"Save quote","operationId":"quoteCartRepositoryV1SavePut","parameters":[{"name":"$body","in":"body","schema":{"required":["quote"],"properties":{"quote":{"$ref":"#/definitions/quote-data-cart-interface"}},"type":"object"}}],"responses":{"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}},"post":{"tags":["quoteCartManagementV1"],"description":"Creates an empty cart and quote for a specified customer if customer does not have a cart yet.","operationId":"quoteCartManagementV1CreateEmptyCartForCustomerPost","responses":{"200":{"description":"200 Success.","schema":{"type":"integer","description":"new cart ID if customer did not have a cart or ID of the existing cart otherwise."}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}},"get":{"tags":["quoteCartManagementV1"],"description":"Returns information for the cart for a specified customer.","operationId":"quoteCartManagementV1GetCartForCustomerGet","responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/quote-data-cart-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/carts/":{"post":{"tags":["quoteCartManagementV1"],"description":"Creates an empty cart and quote for a guest.","operationId":"quoteCartManagementV1CreateEmptyCartPost","responses":{"200":{"description":"200 Success.","schema":{"type":"integer","description":"Cart ID."}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/customers/{customerId}/carts":{"post":{"tags":["quoteCartManagementV1"],"description":"Creates an empty cart and quote for a specified customer if customer does not have a cart yet.","operationId":"quoteCartManagementV1CreateEmptyCartForCustomerPost","parameters":[{"name":"customerId","in":"path","type":"integer","required":true,"description":"The customer ID."}],"responses":{"200":{"description":"200 Success.","schema":{"type":"integer","description":"new cart ID if customer did not have a cart or ID of the existing cart otherwise."}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/carts/mine/order":{"put":{"tags":["quoteCartManagementV1"],"description":"Places an order for a specified cart.","operationId":"quoteCartManagementV1PlaceOrderPut","parameters":[{"name":"$body","in":"body","schema":{"properties":{"paymentMethod":{"$ref":"#/definitions/quote-data-payment-interface"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"type":"integer","description":"Order ID."}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/carts/{cartId}/order":{"put":{"tags":["quoteCartManagementV1"],"description":"Places an order for a specified cart.","operationId":"quoteCartManagementV1PlaceOrderPut","parameters":[{"name":"cartId","in":"path","type":"integer","required":true,"description":"The cart ID."},{"name":"$body","in":"body","schema":{"properties":{"paymentMethod":{"$ref":"#/definitions/quote-data-payment-interface"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"type":"integer","description":"Order ID."}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/guest-carts/{cartId}":{"get":{"tags":["quoteGuestCartRepositoryV1"],"description":"Enable a guest user to return information for a specified cart.","operationId":"quoteGuestCartRepositoryV1GetGet","parameters":[{"name":"cartId","in":"path","type":"string","required":true}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/quote-data-cart-interface"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}},"put":{"tags":["quoteGuestCartManagementV1"],"description":"Assign a specified customer to a specified shopping cart.","operationId":"quoteGuestCartManagementV1AssignCustomerPut","parameters":[{"name":"cartId","in":"path","type":"string","required":true,"description":"The cart ID."},{"name":"$body","in":"body","schema":{"required":["customerId","storeId"],"properties":{"customerId":{"type":"integer","description":"The customer ID."},"storeId":{"type":"integer"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"type":"boolean"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/guest-carts":{"post":{"tags":["quoteGuestCartManagementV1"],"description":"Enable an customer or guest user to create an empty cart and quote for an anonymous customer.","operationId":"quoteGuestCartManagementV1CreateEmptyCartPost","responses":{"200":{"description":"200 Success.","schema":{"type":"string","description":"Cart ID."}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/guest-carts/{cartId}/order":{"put":{"tags":["quoteGuestCartManagementV1"],"description":"Place an order for a specified cart.","operationId":"quoteGuestCartManagementV1PlaceOrderPut","parameters":[{"name":"cartId","in":"path","type":"string","required":true,"description":"The cart ID."},{"name":"$body","in":"body","schema":{"properties":{"paymentMethod":{"$ref":"#/definitions/quote-data-payment-interface"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"type":"integer","description":"Order ID."}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/carts/{cartId}/shipping-methods":{"get":{"tags":["quoteShippingMethodManagementV1"],"description":"Lists applicable shipping methods for a specified quote.","operationId":"quoteShippingMethodManagementV1GetListGet","parameters":[{"name":"cartId","in":"path","type":"integer","required":true,"description":"The shopping cart ID."}],"responses":{"200":{"description":"200 Success.","schema":{"type":"array","description":"An array of shipping methods.","items":{"$ref":"#/definitions/quote-data-shipping-method-interface"}}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/carts/{cartId}/estimate-shipping-methods-by-address-id":{"post":{"tags":["quoteShippingMethodManagementV1"],"description":"Estimate shipping","operationId":"quoteShippingMethodManagementV1EstimateByAddressIdPost","parameters":[{"name":"cartId","in":"path","type":"integer","required":true,"description":"The shopping cart ID."},{"name":"$body","in":"body","schema":{"required":["addressId"],"properties":{"addressId":{"type":"integer","description":"The estimate address id"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"type":"array","description":"An array of shipping methods.","items":{"$ref":"#/definitions/quote-data-shipping-method-interface"}}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/carts/mine/shipping-methods":{"get":{"tags":["quoteShippingMethodManagementV1"],"description":"Lists applicable shipping methods for a specified quote.","operationId":"quoteShippingMethodManagementV1GetListGet","responses":{"200":{"description":"200 Success.","schema":{"type":"array","description":"An array of shipping methods.","items":{"$ref":"#/definitions/quote-data-shipping-method-interface"}}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/carts/mine/estimate-shipping-methods-by-address-id":{"post":{"tags":["quoteShippingMethodManagementV1"],"description":"Estimate shipping","operationId":"quoteShippingMethodManagementV1EstimateByAddressIdPost","parameters":[{"name":"$body","in":"body","schema":{"required":["addressId"],"properties":{"addressId":{"type":"integer","description":"The estimate address id"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"type":"array","description":"An array of shipping methods.","items":{"$ref":"#/definitions/quote-data-shipping-method-interface"}}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/carts/{cartId}/estimate-shipping-methods":{"post":{"tags":["quoteShipmentEstimationV1"],"description":"Estimate shipping by address and return list of available shipping methods","operationId":"quoteShipmentEstimationV1EstimateByExtendedAddressPost","parameters":[{"name":"cartId","in":"path","type":"string","required":true},{"name":"$body","in":"body","schema":{"required":["address"],"properties":{"address":{"$ref":"#/definitions/quote-data-address-interface"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"type":"array","description":"An array of shipping methods","items":{"$ref":"#/definitions/quote-data-shipping-method-interface"}}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/carts/mine/estimate-shipping-methods":{"post":{"tags":["quoteShipmentEstimationV1"],"description":"Estimate shipping by address and return list of available shipping methods","operationId":"quoteShipmentEstimationV1EstimateByExtendedAddressPost","parameters":[{"name":"$body","in":"body","schema":{"required":["address"],"properties":{"address":{"$ref":"#/definitions/quote-data-address-interface"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"type":"array","description":"An array of shipping methods","items":{"$ref":"#/definitions/quote-data-shipping-method-interface"}}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/guest-carts/{cartId}/shipping-methods":{"get":{"tags":["quoteGuestShippingMethodManagementV1"],"description":"List applicable shipping methods for a specified quote.","operationId":"quoteGuestShippingMethodManagementV1GetListGet","parameters":[{"name":"cartId","in":"path","type":"string","required":true,"description":"The shopping cart ID."}],"responses":{"200":{"description":"200 Success.","schema":{"type":"array","description":"An array of shipping methods.","items":{"$ref":"#/definitions/quote-data-shipping-method-interface"}}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/guest-carts/{cartId}/estimate-shipping-methods":{"post":{"tags":["quoteGuestShipmentEstimationV1"],"description":"Estimate shipping by address and return list of available shipping methods","operationId":"quoteGuestShipmentEstimationV1EstimateByExtendedAddressPost","parameters":[{"name":"cartId","in":"path","type":"string","required":true},{"name":"$body","in":"body","schema":{"required":["address"],"properties":{"address":{"$ref":"#/definitions/quote-data-address-interface"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"type":"array","description":"An array of shipping methods","items":{"$ref":"#/definitions/quote-data-shipping-method-interface"}}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/carts/{cartId}/items":{"get":{"tags":["quoteCartItemRepositoryV1"],"description":"Lists items that are assigned to a specified cart.","operationId":"quoteCartItemRepositoryV1GetListGet","parameters":[{"name":"cartId","in":"path","type":"integer","required":true,"description":"The cart ID."}],"responses":{"200":{"description":"200 Success.","schema":{"type":"array","description":"Array of items.","items":{"$ref":"#/definitions/quote-data-cart-item-interface"}}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/carts/{quoteId}/items":{"post":{"tags":["quoteCartItemRepositoryV1"],"description":"Add/update the specified cart item.","operationId":"quoteCartItemRepositoryV1SavePost","parameters":[{"name":"quoteId","in":"path","type":"string","required":true},{"name":"$body","in":"body","schema":{"required":["cartItem"],"properties":{"cartItem":{"$ref":"#/definitions/quote-data-cart-item-interface"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/quote-data-cart-item-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/carts/{cartId}/items/{itemId}":{"put":{"tags":["quoteCartItemRepositoryV1"],"description":"Add/update the specified cart item.","operationId":"quoteCartItemRepositoryV1SavePut","parameters":[{"name":"cartId","in":"path","type":"string","required":true},{"name":"itemId","in":"path","type":"string","required":true},{"name":"$body","in":"body","schema":{"required":["cartItem"],"properties":{"cartItem":{"$ref":"#/definitions/quote-data-cart-item-interface"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/quote-data-cart-item-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}},"delete":{"tags":["quoteCartItemRepositoryV1"],"description":"Removes the specified item from the specified cart.","operationId":"quoteCartItemRepositoryV1DeleteByIdDelete","parameters":[{"name":"cartId","in":"path","type":"integer","required":true,"description":"The cart ID."},{"name":"itemId","in":"path","type":"integer","required":true,"description":"The item ID of the item to be removed."}],"responses":{"200":{"description":"200 Success.","schema":{"type":"boolean"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/carts/mine/items":{"get":{"tags":["quoteCartItemRepositoryV1"],"description":"Lists items that are assigned to a specified cart.","operationId":"quoteCartItemRepositoryV1GetListGet","responses":{"200":{"description":"200 Success.","schema":{"type":"array","description":"Array of items.","items":{"$ref":"#/definitions/quote-data-cart-item-interface"}}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}},"post":{"tags":["quoteCartItemRepositoryV1"],"description":"Add/update the specified cart item.","operationId":"quoteCartItemRepositoryV1SavePost","parameters":[{"name":"$body","in":"body","schema":{"required":["cartItem"],"properties":{"cartItem":{"$ref":"#/definitions/quote-data-cart-item-interface"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/quote-data-cart-item-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/carts/mine/items/{itemId}":{"put":{"tags":["quoteCartItemRepositoryV1"],"description":"Add/update the specified cart item.","operationId":"quoteCartItemRepositoryV1SavePut","parameters":[{"name":"itemId","in":"path","type":"string","required":true},{"name":"$body","in":"body","schema":{"required":["cartItem"],"properties":{"cartItem":{"$ref":"#/definitions/quote-data-cart-item-interface"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/quote-data-cart-item-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}},"delete":{"tags":["quoteCartItemRepositoryV1"],"description":"Removes the specified item from the specified cart.","operationId":"quoteCartItemRepositoryV1DeleteByIdDelete","parameters":[{"name":"itemId","in":"path","type":"integer","required":true,"description":"The item ID of the item to be removed."}],"responses":{"200":{"description":"200 Success.","schema":{"type":"boolean"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/guest-carts/{cartId}/items":{"get":{"tags":["quoteGuestCartItemRepositoryV1"],"description":"List items that are assigned to a specified cart.","operationId":"quoteGuestCartItemRepositoryV1GetListGet","parameters":[{"name":"cartId","in":"path","type":"string","required":true,"description":"The cart ID."}],"responses":{"200":{"description":"200 Success.","schema":{"type":"array","description":"Array of items.","items":{"$ref":"#/definitions/quote-data-cart-item-interface"}}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}},"post":{"tags":["quoteGuestCartItemRepositoryV1"],"description":"Add/update the specified cart item.","operationId":"quoteGuestCartItemRepositoryV1SavePost","parameters":[{"name":"cartId","in":"path","type":"string","required":true},{"name":"$body","in":"body","schema":{"required":["cartItem"],"properties":{"cartItem":{"$ref":"#/definitions/quote-data-cart-item-interface"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/quote-data-cart-item-interface"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/guest-carts/{cartId}/items/{itemId}":{"put":{"tags":["quoteGuestCartItemRepositoryV1"],"description":"Add/update the specified cart item.","operationId":"quoteGuestCartItemRepositoryV1SavePut","parameters":[{"name":"cartId","in":"path","type":"string","required":true},{"name":"itemId","in":"path","type":"string","required":true},{"name":"$body","in":"body","schema":{"required":["cartItem"],"properties":{"cartItem":{"$ref":"#/definitions/quote-data-cart-item-interface"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/quote-data-cart-item-interface"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}},"delete":{"tags":["quoteGuestCartItemRepositoryV1"],"description":"Remove the specified item from the specified cart.","operationId":"quoteGuestCartItemRepositoryV1DeleteByIdDelete","parameters":[{"name":"cartId","in":"path","type":"string","required":true,"description":"The cart ID."},{"name":"itemId","in":"path","type":"integer","required":true,"description":"The item ID of the item to be removed."}],"responses":{"200":{"description":"200 Success.","schema":{"type":"boolean"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/carts/{cartId}/selected-payment-method":{"get":{"tags":["quotePaymentMethodManagementV1"],"description":"Returns the payment method for a specified shopping cart.","operationId":"quotePaymentMethodManagementV1GetGet","parameters":[{"name":"cartId","in":"path","type":"integer","required":true,"description":"The cart ID."}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/quote-data-payment-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}},"put":{"tags":["quotePaymentMethodManagementV1"],"description":"Adds a specified payment method to a specified shopping cart.","operationId":"quotePaymentMethodManagementV1SetPut","parameters":[{"name":"cartId","in":"path","type":"integer","required":true,"description":"The cart ID."},{"name":"$body","in":"body","schema":{"required":["method"],"properties":{"method":{"$ref":"#/definitions/quote-data-payment-interface"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"type":"string","description":"redirect url or error message."}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/carts/{cartId}/payment-methods":{"get":{"tags":["quotePaymentMethodManagementV1"],"description":"Lists available payment methods for a specified shopping cart. This call returns an array of objects, but detailed information about each object\u2019s attributes might not be included. See http://devdocs.magento.com/codelinks/attributes.html#PaymentMethodManagementInterface to determine which call to use to get detailed information about all attributes for an object.","operationId":"quotePaymentMethodManagementV1GetListGet","parameters":[{"name":"cartId","in":"path","type":"integer","required":true,"description":"The cart ID."}],"responses":{"200":{"description":"200 Success.","schema":{"type":"array","description":"Array of payment methods.","items":{"$ref":"#/definitions/quote-data-payment-method-interface"}}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/carts/mine/selected-payment-method":{"get":{"tags":["quotePaymentMethodManagementV1"],"description":"Returns the payment method for a specified shopping cart.","operationId":"quotePaymentMethodManagementV1GetGet","responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/quote-data-payment-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}},"put":{"tags":["quotePaymentMethodManagementV1"],"description":"Adds a specified payment method to a specified shopping cart.","operationId":"quotePaymentMethodManagementV1SetPut","parameters":[{"name":"$body","in":"body","schema":{"required":["method"],"properties":{"method":{"$ref":"#/definitions/quote-data-payment-interface"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"type":"string","description":"redirect url or error message."}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/carts/mine/payment-methods":{"get":{"tags":["quotePaymentMethodManagementV1"],"description":"Lists available payment methods for a specified shopping cart. This call returns an array of objects, but detailed information about each object\u2019s attributes might not be included. See http://devdocs.magento.com/codelinks/attributes.html#PaymentMethodManagementInterface to determine which call to use to get detailed information about all attributes for an object.","operationId":"quotePaymentMethodManagementV1GetListGet","responses":{"200":{"description":"200 Success.","schema":{"type":"array","description":"Array of payment methods.","items":{"$ref":"#/definitions/quote-data-payment-method-interface"}}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/guest-carts/{cartId}/selected-payment-method":{"get":{"tags":["quoteGuestPaymentMethodManagementV1"],"description":"Return the payment method for a specified shopping cart.","operationId":"quoteGuestPaymentMethodManagementV1GetGet","parameters":[{"name":"cartId","in":"path","type":"string","required":true,"description":"The cart ID."}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/quote-data-payment-interface"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}},"put":{"tags":["quoteGuestPaymentMethodManagementV1"],"description":"Add a specified payment method to a specified shopping cart.","operationId":"quoteGuestPaymentMethodManagementV1SetPut","parameters":[{"name":"cartId","in":"path","type":"string","required":true,"description":"The cart ID."},{"name":"$body","in":"body","schema":{"required":["method"],"properties":{"method":{"$ref":"#/definitions/quote-data-payment-interface"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"type":"integer","description":"Payment method ID."}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/guest-carts/{cartId}/payment-methods":{"get":{"tags":["quoteGuestPaymentMethodManagementV1"],"description":"List available payment methods for a specified shopping cart. This call returns an array of objects, but detailed information about each object\u2019s attributes might not be included. See http://devdocs.magento.com/codelinks/attributes.html#GuestPaymentMethodManagementInterface to determine which call to use to get detailed information about all attributes for an object.","operationId":"quoteGuestPaymentMethodManagementV1GetListGet","parameters":[{"name":"cartId","in":"path","type":"string","required":true,"description":"The cart ID."}],"responses":{"200":{"description":"200 Success.","schema":{"type":"array","description":"Array of payment methods.","items":{"$ref":"#/definitions/quote-data-payment-method-interface"}}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/carts/{cartId}/billing-address":{"get":{"tags":["quoteBillingAddressManagementV1"],"description":"Returns the billing address for a specified quote.","operationId":"quoteBillingAddressManagementV1GetGet","parameters":[{"name":"cartId","in":"path","type":"integer","required":true,"description":"The cart ID."}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/quote-data-address-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}},"post":{"tags":["quoteBillingAddressManagementV1"],"description":"Assigns a specified billing address to a specified cart.","operationId":"quoteBillingAddressManagementV1AssignPost","parameters":[{"name":"cartId","in":"path","type":"integer","required":true,"description":"The cart ID."},{"name":"$body","in":"body","schema":{"required":["address"],"properties":{"address":{"$ref":"#/definitions/quote-data-address-interface"},"useForShipping":{"type":"boolean"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"type":"integer","description":"Address ID."}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/carts/mine/billing-address":{"get":{"tags":["quoteBillingAddressManagementV1"],"description":"Returns the billing address for a specified quote.","operationId":"quoteBillingAddressManagementV1GetGet","responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/quote-data-address-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}},"post":{"tags":["quoteBillingAddressManagementV1"],"description":"Assigns a specified billing address to a specified cart.","operationId":"quoteBillingAddressManagementV1AssignPost","parameters":[{"name":"$body","in":"body","schema":{"required":["address"],"properties":{"address":{"$ref":"#/definitions/quote-data-address-interface"},"useForShipping":{"type":"boolean"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"type":"integer","description":"Address ID."}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/guest-carts/{cartId}/billing-address":{"get":{"tags":["quoteGuestBillingAddressManagementV1"],"description":"Return the billing address for a specified quote.","operationId":"quoteGuestBillingAddressManagementV1GetGet","parameters":[{"name":"cartId","in":"path","type":"string","required":true,"description":"The cart ID."}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/quote-data-address-interface"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}},"post":{"tags":["quoteGuestBillingAddressManagementV1"],"description":"Assign a specified billing address to a specified cart.","operationId":"quoteGuestBillingAddressManagementV1AssignPost","parameters":[{"name":"cartId","in":"path","type":"string","required":true,"description":"The cart ID."},{"name":"$body","in":"body","schema":{"required":["address"],"properties":{"address":{"$ref":"#/definitions/quote-data-address-interface"},"useForShipping":{"type":"boolean"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"type":"integer","description":"Address ID."}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/carts/{cartId}/coupons":{"get":{"tags":["quoteCouponManagementV1"],"description":"Returns information for a coupon in a specified cart.","operationId":"quoteCouponManagementV1GetGet","parameters":[{"name":"cartId","in":"path","type":"integer","required":true,"description":"The cart ID."}],"responses":{"200":{"description":"200 Success.","schema":{"type":"string","description":"The coupon code data."}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}},"delete":{"tags":["quoteCouponManagementV1"],"description":"Deletes a coupon from a specified cart.","operationId":"quoteCouponManagementV1RemoveDelete","parameters":[{"name":"cartId","in":"path","type":"integer","required":true,"description":"The cart ID."}],"responses":{"200":{"description":"200 Success.","schema":{"type":"boolean"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/carts/{cartId}/coupons/{couponCode}":{"put":{"tags":["quoteCouponManagementV1"],"description":"Adds a coupon by code to a specified cart.","operationId":"quoteCouponManagementV1SetPut","parameters":[{"name":"cartId","in":"path","type":"integer","required":true,"description":"The cart ID."},{"name":"couponCode","in":"path","type":"string","required":true,"description":"The coupon code data."}],"responses":{"200":{"description":"200 Success.","schema":{"type":"boolean"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/carts/mine/coupons":{"get":{"tags":["quoteCouponManagementV1"],"description":"Returns information for a coupon in a specified cart.","operationId":"quoteCouponManagementV1GetGet","responses":{"200":{"description":"200 Success.","schema":{"type":"string","description":"The coupon code data."}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}},"delete":{"tags":["quoteCouponManagementV1"],"description":"Deletes a coupon from a specified cart.","operationId":"quoteCouponManagementV1RemoveDelete","responses":{"200":{"description":"200 Success.","schema":{"type":"boolean"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/carts/mine/coupons/{couponCode}":{"put":{"tags":["quoteCouponManagementV1"],"description":"Adds a coupon by code to a specified cart.","operationId":"quoteCouponManagementV1SetPut","parameters":[{"name":"couponCode","in":"path","type":"string","required":true,"description":"The coupon code data."}],"responses":{"200":{"description":"200 Success.","schema":{"type":"boolean"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/guest-carts/{cartId}/coupons":{"get":{"tags":["quoteGuestCouponManagementV1"],"description":"Return information for a coupon in a specified cart.","operationId":"quoteGuestCouponManagementV1GetGet","parameters":[{"name":"cartId","in":"path","type":"string","required":true,"description":"The cart ID."}],"responses":{"200":{"description":"200 Success.","schema":{"type":"string","description":"The coupon code data."}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}},"delete":{"tags":["quoteGuestCouponManagementV1"],"description":"Delete a coupon from a specified cart.","operationId":"quoteGuestCouponManagementV1RemoveDelete","parameters":[{"name":"cartId","in":"path","type":"string","required":true,"description":"The cart ID."}],"responses":{"200":{"description":"200 Success.","schema":{"type":"boolean"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/guest-carts/{cartId}/coupons/{couponCode}":{"put":{"tags":["quoteGuestCouponManagementV1"],"description":"Add a coupon by code to a specified cart.","operationId":"quoteGuestCouponManagementV1SetPut","parameters":[{"name":"cartId","in":"path","type":"string","required":true,"description":"The cart ID."},{"name":"couponCode","in":"path","type":"string","required":true,"description":"The coupon code data."}],"responses":{"200":{"description":"200 Success.","schema":{"type":"boolean"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/carts/{cartId}/totals":{"get":{"tags":["quoteCartTotalRepositoryV1"],"description":"Returns quote totals data for a specified cart.","operationId":"quoteCartTotalRepositoryV1GetGet","parameters":[{"name":"cartId","in":"path","type":"integer","required":true,"description":"The cart ID."}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/quote-data-totals-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/carts/mine/totals":{"get":{"tags":["quoteCartTotalRepositoryV1"],"description":"Returns quote totals data for a specified cart.","operationId":"quoteCartTotalRepositoryV1GetGet","responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/quote-data-totals-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/guest-carts/{cartId}/collect-totals":{"put":{"tags":["quoteGuestCartTotalManagementV1"],"description":"Set shipping/billing methods and additional data for cart and collect totals for guest.","operationId":"quoteGuestCartTotalManagementV1CollectTotalsPut","parameters":[{"name":"cartId","in":"path","type":"string","required":true,"description":"The cart ID."},{"name":"$body","in":"body","schema":{"required":["paymentMethod"],"properties":{"paymentMethod":{"$ref":"#/definitions/quote-data-payment-interface"},"shippingCarrierCode":{"type":"string","description":"The carrier code."},"shippingMethodCode":{"type":"string","description":"The shipping method code."},"additionalData":{"$ref":"#/definitions/quote-data-totals-additional-data-interface"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/quote-data-totals-interface"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/guest-carts/{cartId}/totals":{"get":{"tags":["quoteGuestCartTotalRepositoryV1"],"description":"Return quote totals data for a specified cart.","operationId":"quoteGuestCartTotalRepositoryV1GetGet","parameters":[{"name":"cartId","in":"path","type":"string","required":true,"description":"The cart ID."}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/quote-data-totals-interface"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/carts/mine/collect-totals":{"put":{"tags":["quoteCartTotalManagementV1"],"description":"Set shipping/billing methods and additional data for cart and collect totals.","operationId":"quoteCartTotalManagementV1CollectTotalsPut","parameters":[{"name":"$body","in":"body","schema":{"required":["paymentMethod"],"properties":{"paymentMethod":{"$ref":"#/definitions/quote-data-payment-interface"},"shippingCarrierCode":{"type":"string","description":"The carrier code."},"shippingMethodCode":{"type":"string","description":"The shipping method code."},"additionalData":{"$ref":"#/definitions/quote-data-totals-additional-data-interface"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/quote-data-totals-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/search":{"get":{"tags":["searchV1"],"description":"Make Full Text Search and return found Documents","operationId":"searchV1SearchGet","parameters":[{"name":"searchCriteria[requestName]","in":"query","type":"string"},{"name":"searchCriteria[filterGroups][][filters][][field]","in":"query","type":"string","description":"Field"},{"name":"searchCriteria[filterGroups][][filters][][value]","in":"query","type":"string","description":"Value"},{"name":"searchCriteria[filterGroups][][filters][][conditionType]","in":"query","type":"string","description":"Condition type"},{"name":"searchCriteria[sortOrders][][field]","in":"query","type":"string","description":"Sorting field."},{"name":"searchCriteria[sortOrders][][direction]","in":"query","type":"string","description":"Sorting direction."},{"name":"searchCriteria[pageSize]","in":"query","type":"integer","description":"Page size."},{"name":"searchCriteria[currentPage]","in":"query","type":"integer","description":"Current page."}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/framework-search-search-result-interface"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/orders/{id}":{"get":{"tags":["salesOrderRepositoryV1"],"description":"Loads a specified order.","operationId":"salesOrderRepositoryV1GetGet","parameters":[{"name":"id","in":"path","type":"integer","required":true,"description":"The order ID."}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/sales-data-order-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/orders":{"get":{"tags":["salesOrderRepositoryV1"],"description":"Lists orders that match specified search criteria. This call returns an array of objects, but detailed information about each object\u2019s attributes might not be included. See http://devdocs.magento.com/codelinks/attributes.html#OrderRepositoryInterface to determine which call to use to get detailed information about all attributes for an object.","operationId":"salesOrderRepositoryV1GetListGet","parameters":[{"name":"searchCriteria[filterGroups][][filters][][field]","in":"query","type":"string","description":"Field"},{"name":"searchCriteria[filterGroups][][filters][][value]","in":"query","type":"string","description":"Value"},{"name":"searchCriteria[filterGroups][][filters][][conditionType]","in":"query","type":"string","description":"Condition type"},{"name":"searchCriteria[sortOrders][][field]","in":"query","type":"string","description":"Sorting field."},{"name":"searchCriteria[sortOrders][][direction]","in":"query","type":"string","description":"Sorting direction."},{"name":"searchCriteria[pageSize]","in":"query","type":"integer","description":"Page size."},{"name":"searchCriteria[currentPage]","in":"query","type":"integer","description":"Current page."}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/sales-data-order-search-result-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/orders/create":{"put":{"tags":["salesOrderRepositoryV1"],"description":"Performs persist operations for a specified order.","operationId":"salesOrderRepositoryV1SavePut","parameters":[{"name":"$body","in":"body","schema":{"required":["entity"],"properties":{"entity":{"$ref":"#/definitions/sales-data-order-interface"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/sales-data-order-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/orders/":{"post":{"tags":["salesOrderRepositoryV1"],"description":"Performs persist operations for a specified order.","operationId":"salesOrderRepositoryV1SavePost","parameters":[{"name":"$body","in":"body","schema":{"required":["entity"],"properties":{"entity":{"$ref":"#/definitions/sales-data-order-interface"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/sales-data-order-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/orders/{id}/statuses":{"get":{"tags":["salesOrderManagementV1"],"description":"Gets the status for a specified order.","operationId":"salesOrderManagementV1GetStatusGet","parameters":[{"name":"id","in":"path","type":"integer","required":true,"description":"The order ID."}],"responses":{"200":{"description":"200 Success.","schema":{"type":"string","description":"Order status."}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/orders/{id}/cancel":{"post":{"tags":["salesOrderManagementV1"],"description":"Cancels a specified order.","operationId":"salesOrderManagementV1CancelPost","parameters":[{"name":"id","in":"path","type":"integer","required":true,"description":"The order ID."}],"responses":{"200":{"description":"200 Success.","schema":{"type":"boolean"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/orders/{id}/emails":{"post":{"tags":["salesOrderManagementV1"],"description":"Emails a user a specified order.","operationId":"salesOrderManagementV1NotifyPost","parameters":[{"name":"id","in":"path","type":"integer","required":true,"description":"The order ID."}],"responses":{"200":{"description":"200 Success.","schema":{"type":"boolean"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/orders/{id}/hold":{"post":{"tags":["salesOrderManagementV1"],"description":"Holds a specified order.","operationId":"salesOrderManagementV1HoldPost","parameters":[{"name":"id","in":"path","type":"integer","required":true,"description":"The order ID."}],"responses":{"200":{"description":"200 Success.","schema":{"type":"boolean"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/orders/{id}/unhold":{"post":{"tags":["salesOrderManagementV1"],"description":"Releases a specified order from hold status.","operationId":"salesOrderManagementV1UnHoldPost","parameters":[{"name":"id","in":"path","type":"integer","required":true,"description":"The order ID."}],"responses":{"200":{"description":"200 Success.","schema":{"type":"boolean"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/orders/{id}/comments":{"post":{"tags":["salesOrderManagementV1"],"description":"Adds a comment to a specified order.","operationId":"salesOrderManagementV1AddCommentPost","parameters":[{"name":"id","in":"path","type":"integer","required":true,"description":"The order ID."},{"name":"$body","in":"body","schema":{"required":["statusHistory"],"properties":{"statusHistory":{"$ref":"#/definitions/sales-data-order-status-history-interface"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"type":"boolean"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}},"get":{"tags":["salesOrderManagementV1"],"description":"Lists comments for a specified order.","operationId":"salesOrderManagementV1GetCommentsListGet","parameters":[{"name":"id","in":"path","type":"integer","required":true,"description":"The order ID."}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/sales-data-order-status-history-search-result-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/orders/{parent_id}":{"put":{"tags":["salesOrderAddressRepositoryV1"],"description":"Performs persist operations for a specified order address.","operationId":"salesOrderAddressRepositoryV1SavePut","parameters":[{"name":"parent_id","in":"path","type":"string","required":true},{"name":"$body","in":"body","schema":{"required":["entity"],"properties":{"entity":{"$ref":"#/definitions/sales-data-order-address-interface"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/sales-data-order-address-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/orders/items/{id}":{"get":{"tags":["salesOrderItemRepositoryV1"],"description":"Loads a specified order item.","operationId":"salesOrderItemRepositoryV1GetGet","parameters":[{"name":"id","in":"path","type":"integer","required":true,"description":"The order item ID."}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/sales-data-order-item-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/orders/items":{"get":{"tags":["salesOrderItemRepositoryV1"],"description":"Lists order items that match specified search criteria. This call returns an array of objects, but detailed information about each object\u2019s attributes might not be included. See http://devdocs.magento.com/codelinks/attributes.html#OrderItemRepositoryInterface to determine which call to use to get detailed information about all attributes for an object.","operationId":"salesOrderItemRepositoryV1GetListGet","parameters":[{"name":"searchCriteria[filterGroups][][filters][][field]","in":"query","type":"string","description":"Field"},{"name":"searchCriteria[filterGroups][][filters][][value]","in":"query","type":"string","description":"Value"},{"name":"searchCriteria[filterGroups][][filters][][conditionType]","in":"query","type":"string","description":"Condition type"},{"name":"searchCriteria[sortOrders][][field]","in":"query","type":"string","description":"Sorting field."},{"name":"searchCriteria[sortOrders][][direction]","in":"query","type":"string","description":"Sorting direction."},{"name":"searchCriteria[pageSize]","in":"query","type":"integer","description":"Page size."},{"name":"searchCriteria[currentPage]","in":"query","type":"integer","description":"Current page."}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/sales-data-order-item-search-result-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/invoices/{id}":{"get":{"tags":["salesInvoiceRepositoryV1"],"description":"Loads a specified invoice.","operationId":"salesInvoiceRepositoryV1GetGet","parameters":[{"name":"id","in":"path","type":"integer","required":true,"description":"The invoice ID."}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/sales-data-invoice-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/invoices":{"get":{"tags":["salesInvoiceRepositoryV1"],"description":"Lists invoices that match specified search criteria. This call returns an array of objects, but detailed information about each object\u2019s attributes might not be included. See http://devdocs.magento.com/codelinks/attributes.html#InvoiceRepositoryInterface to determine which call to use to get detailed information about all attributes for an object.","operationId":"salesInvoiceRepositoryV1GetListGet","parameters":[{"name":"searchCriteria[filterGroups][][filters][][field]","in":"query","type":"string","description":"Field"},{"name":"searchCriteria[filterGroups][][filters][][value]","in":"query","type":"string","description":"Value"},{"name":"searchCriteria[filterGroups][][filters][][conditionType]","in":"query","type":"string","description":"Condition type"},{"name":"searchCriteria[sortOrders][][field]","in":"query","type":"string","description":"Sorting field."},{"name":"searchCriteria[sortOrders][][direction]","in":"query","type":"string","description":"Sorting direction."},{"name":"searchCriteria[pageSize]","in":"query","type":"integer","description":"Page size."},{"name":"searchCriteria[currentPage]","in":"query","type":"integer","description":"Current page."}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/sales-data-invoice-search-result-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/invoices/":{"post":{"tags":["salesInvoiceRepositoryV1"],"description":"Performs persist operations for a specified invoice.","operationId":"salesInvoiceRepositoryV1SavePost","parameters":[{"name":"$body","in":"body","schema":{"required":["entity"],"properties":{"entity":{"$ref":"#/definitions/sales-data-invoice-interface"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/sales-data-invoice-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/invoices/{id}/comments":{"get":{"tags":["salesInvoiceManagementV1"],"description":"Lists comments for a specified invoice.","operationId":"salesInvoiceManagementV1GetCommentsListGet","parameters":[{"name":"id","in":"path","type":"integer","required":true,"description":"The invoice ID."}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/sales-data-invoice-comment-search-result-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/invoices/{id}/emails":{"post":{"tags":["salesInvoiceManagementV1"],"description":"Emails a user a specified invoice.","operationId":"salesInvoiceManagementV1NotifyPost","parameters":[{"name":"id","in":"path","type":"integer","required":true,"description":"The invoice ID."}],"responses":{"200":{"description":"200 Success.","schema":{"type":"boolean"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/invoices/{id}/void":{"post":{"tags":["salesInvoiceManagementV1"],"description":"Voids a specified invoice.","operationId":"salesInvoiceManagementV1SetVoidPost","parameters":[{"name":"id","in":"path","type":"integer","required":true,"description":"The invoice ID."}],"responses":{"200":{"description":"200 Success.","schema":{"type":"boolean"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/invoices/{id}/capture":{"post":{"tags":["salesInvoiceManagementV1"],"description":"Sets invoice capture.","operationId":"salesInvoiceManagementV1SetCapturePost","parameters":[{"name":"id","in":"path","type":"integer","required":true}],"responses":{"200":{"description":"200 Success.","schema":{"type":"string"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/invoices/comments":{"post":{"tags":["salesInvoiceCommentRepositoryV1"],"description":"Performs persist operations for a specified invoice comment.","operationId":"salesInvoiceCommentRepositoryV1SavePost","parameters":[{"name":"$body","in":"body","schema":{"required":["entity"],"properties":{"entity":{"$ref":"#/definitions/sales-data-invoice-comment-interface"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/sales-data-invoice-comment-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/invoice/{invoiceId}/refund":{"post":{"tags":["salesRefundInvoiceV1"],"description":"Create refund for invoice","operationId":"salesRefundInvoiceV1ExecutePost","parameters":[{"name":"invoiceId","in":"path","type":"integer","required":true},{"name":"$body","in":"body","schema":{"properties":{"items":{"type":"array","items":{"$ref":"#/definitions/sales-data-creditmemo-item-creation-interface"}},"isOnline":{"type":"boolean"},"notify":{"type":"boolean"},"appendComment":{"type":"boolean"},"comment":{"$ref":"#/definitions/sales-data-creditmemo-comment-creation-interface"},"arguments":{"$ref":"#/definitions/sales-data-creditmemo-creation-arguments-interface"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"type":"integer"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/creditmemo/{id}/comments":{"get":{"tags":["salesCreditmemoManagementV1"],"description":"Lists comments for a specified credit memo.","operationId":"salesCreditmemoManagementV1GetCommentsListGet","parameters":[{"name":"id","in":"path","type":"integer","required":true,"description":"The credit memo ID."}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/sales-data-creditmemo-comment-search-result-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}},"post":{"tags":["salesCreditmemoCommentRepositoryV1"],"description":"Performs persist operations for a specified entity.","operationId":"salesCreditmemoCommentRepositoryV1SavePost","parameters":[{"name":"id","in":"path","type":"string","required":true},{"name":"$body","in":"body","schema":{"required":["entity"],"properties":{"entity":{"$ref":"#/definitions/sales-data-creditmemo-comment-interface"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/sales-data-creditmemo-comment-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/creditmemo/{id}":{"put":{"tags":["salesCreditmemoManagementV1"],"description":"Cancels a specified credit memo.","operationId":"salesCreditmemoManagementV1CancelPut","parameters":[{"name":"id","in":"path","type":"integer","required":true,"description":"The credit memo ID."}],"responses":{"200":{"description":"200 Success.","schema":{"type":"boolean"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"500":{"description":"Internal Server error","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}},"get":{"tags":["salesCreditmemoRepositoryV1"],"description":"Loads a specified credit memo.","operationId":"salesCreditmemoRepositoryV1GetGet","parameters":[{"name":"id","in":"path","type":"integer","required":true,"description":"The credit memo ID."}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/sales-data-creditmemo-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/creditmemo/{id}/emails":{"post":{"tags":["salesCreditmemoManagementV1"],"description":"Emails a user a specified credit memo.","operationId":"salesCreditmemoManagementV1NotifyPost","parameters":[{"name":"id","in":"path","type":"integer","required":true,"description":"The credit memo ID."}],"responses":{"200":{"description":"200 Success.","schema":{"type":"boolean"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/creditmemo/refund":{"post":{"tags":["salesCreditmemoManagementV1"],"description":"Prepare creditmemo to refund and save it.","operationId":"salesCreditmemoManagementV1RefundPost","parameters":[{"name":"$body","in":"body","schema":{"required":["creditmemo"],"properties":{"creditmemo":{"$ref":"#/definitions/sales-data-creditmemo-interface"},"offlineRequested":{"type":"boolean"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/sales-data-creditmemo-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/creditmemos":{"get":{"tags":["salesCreditmemoRepositoryV1"],"description":"Lists credit memos that match specified search criteria. This call returns an array of objects, but detailed information about each object\u2019s attributes might not be included. See http://devdocs.magento.com/codelinks/attributes.html#CreditmemoRepositoryInterface to determine which call to use to get detailed information about all attributes for an object.","operationId":"salesCreditmemoRepositoryV1GetListGet","parameters":[{"name":"searchCriteria[filterGroups][][filters][][field]","in":"query","type":"string","description":"Field"},{"name":"searchCriteria[filterGroups][][filters][][value]","in":"query","type":"string","description":"Value"},{"name":"searchCriteria[filterGroups][][filters][][conditionType]","in":"query","type":"string","description":"Condition type"},{"name":"searchCriteria[sortOrders][][field]","in":"query","type":"string","description":"Sorting field."},{"name":"searchCriteria[sortOrders][][direction]","in":"query","type":"string","description":"Sorting direction."},{"name":"searchCriteria[pageSize]","in":"query","type":"integer","description":"Page size."},{"name":"searchCriteria[currentPage]","in":"query","type":"integer","description":"Current page."}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/sales-data-creditmemo-search-result-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/creditmemo":{"post":{"tags":["salesCreditmemoRepositoryV1"],"description":"Performs persist operations for a specified credit memo.","operationId":"salesCreditmemoRepositoryV1SavePost","parameters":[{"name":"$body","in":"body","schema":{"required":["entity"],"properties":{"entity":{"$ref":"#/definitions/sales-data-creditmemo-interface"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/sales-data-creditmemo-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/order/{orderId}/refund":{"post":{"tags":["salesRefundOrderV1"],"description":"Create offline refund for order","operationId":"salesRefundOrderV1ExecutePost","parameters":[{"name":"orderId","in":"path","type":"integer","required":true},{"name":"$body","in":"body","schema":{"properties":{"items":{"type":"array","items":{"$ref":"#/definitions/sales-data-creditmemo-item-creation-interface"}},"notify":{"type":"boolean"},"appendComment":{"type":"boolean"},"comment":{"$ref":"#/definitions/sales-data-creditmemo-comment-creation-interface"},"arguments":{"$ref":"#/definitions/sales-data-creditmemo-creation-arguments-interface"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"type":"integer"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/shipment/{id}":{"get":{"tags":["salesShipmentRepositoryV1"],"description":"Loads a specified shipment.","operationId":"salesShipmentRepositoryV1GetGet","parameters":[{"name":"id","in":"path","type":"integer","required":true,"description":"The shipment ID."}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/sales-data-shipment-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/shipments":{"get":{"tags":["salesShipmentRepositoryV1"],"description":"Lists shipments that match specified search criteria. This call returns an array of objects, but detailed information about each object\u2019s attributes might not be included. See http://devdocs.magento.com/codelinks/attributes.html#ShipmentRepositoryInterface to determine which call to use to get detailed information about all attributes for an object.","operationId":"salesShipmentRepositoryV1GetListGet","parameters":[{"name":"searchCriteria[filterGroups][][filters][][field]","in":"query","type":"string","description":"Field"},{"name":"searchCriteria[filterGroups][][filters][][value]","in":"query","type":"string","description":"Value"},{"name":"searchCriteria[filterGroups][][filters][][conditionType]","in":"query","type":"string","description":"Condition type"},{"name":"searchCriteria[sortOrders][][field]","in":"query","type":"string","description":"Sorting field."},{"name":"searchCriteria[sortOrders][][direction]","in":"query","type":"string","description":"Sorting direction."},{"name":"searchCriteria[pageSize]","in":"query","type":"integer","description":"Page size."},{"name":"searchCriteria[currentPage]","in":"query","type":"integer","description":"Current page."}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/sales-data-shipment-search-result-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/shipment/":{"post":{"tags":["salesShipmentRepositoryV1"],"description":"Performs persist operations for a specified shipment.","operationId":"salesShipmentRepositoryV1SavePost","parameters":[{"name":"$body","in":"body","schema":{"required":["entity"],"properties":{"entity":{"$ref":"#/definitions/sales-data-shipment-interface"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/sales-data-shipment-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/shipment/{id}/comments":{"get":{"tags":["salesShipmentManagementV1"],"description":"Lists comments for a specified shipment.","operationId":"salesShipmentManagementV1GetCommentsListGet","parameters":[{"name":"id","in":"path","type":"integer","required":true,"description":"The shipment ID."}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/sales-data-shipment-comment-search-result-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}},"post":{"tags":["salesShipmentCommentRepositoryV1"],"description":"Performs persist operations for a specified shipment comment.","operationId":"salesShipmentCommentRepositoryV1SavePost","parameters":[{"name":"id","in":"path","type":"string","required":true},{"name":"$body","in":"body","schema":{"required":["entity"],"properties":{"entity":{"$ref":"#/definitions/sales-data-shipment-comment-interface"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/sales-data-shipment-comment-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/shipment/{id}/emails":{"post":{"tags":["salesShipmentManagementV1"],"description":"Emails user a specified shipment.","operationId":"salesShipmentManagementV1NotifyPost","parameters":[{"name":"id","in":"path","type":"integer","required":true,"description":"The shipment ID."}],"responses":{"200":{"description":"200 Success.","schema":{"type":"boolean"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/shipment/{id}/label":{"get":{"tags":["salesShipmentManagementV1"],"description":"Gets a specified shipment label.","operationId":"salesShipmentManagementV1GetLabelGet","parameters":[{"name":"id","in":"path","type":"integer","required":true,"description":"The shipment label ID."}],"responses":{"200":{"description":"200 Success.","schema":{"type":"string","description":"Shipment label."}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/shipment/track":{"post":{"tags":["salesShipmentTrackRepositoryV1"],"description":"Performs persist operations for a specified shipment track.","operationId":"salesShipmentTrackRepositoryV1SavePost","parameters":[{"name":"$body","in":"body","schema":{"required":["entity"],"properties":{"entity":{"$ref":"#/definitions/sales-data-shipment-track-interface"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/sales-data-shipment-track-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/shipment/track/{id}":{"delete":{"tags":["salesShipmentTrackRepositoryV1"],"description":"Deletes a specified shipment track by ID.","operationId":"salesShipmentTrackRepositoryV1DeleteByIdDelete","parameters":[{"name":"id","in":"path","type":"integer","required":true,"description":"The shipment track ID."}],"responses":{"200":{"description":"200 Success.","schema":{"type":"boolean"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/order/{orderId}/ship":{"post":{"tags":["salesShipOrderV1"],"description":"Creates new Shipment for given Order.","operationId":"salesShipOrderV1ExecutePost","parameters":[{"name":"orderId","in":"path","type":"integer","required":true},{"name":"$body","in":"body","schema":{"properties":{"items":{"type":"array","items":{"$ref":"#/definitions/sales-data-shipment-item-creation-interface"}},"notify":{"type":"boolean"},"appendComment":{"type":"boolean"},"comment":{"$ref":"#/definitions/sales-data-shipment-comment-creation-interface"},"tracks":{"type":"array","items":{"$ref":"#/definitions/sales-data-shipment-track-creation-interface"}},"packages":{"type":"array","items":{"$ref":"#/definitions/sales-data-shipment-package-creation-interface"}},"arguments":{"$ref":"#/definitions/sales-data-shipment-creation-arguments-interface"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"type":"integer","description":"Id of created Shipment."}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/transactions/{id}":{"get":{"tags":["salesTransactionRepositoryV1"],"description":"Loads a specified transaction.","operationId":"salesTransactionRepositoryV1GetGet","parameters":[{"name":"id","in":"path","type":"integer","required":true,"description":"The transaction ID."}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/sales-data-transaction-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/transactions":{"get":{"tags":["salesTransactionRepositoryV1"],"description":"Lists transactions that match specified search criteria. This call returns an array of objects, but detailed information about each object\u2019s attributes might not be included. See http://devdocs.magento.com/codelinks/attributes.html#TransactionRepositoryInterface to determine which call to use to get detailed information about all attributes for an object.","operationId":"salesTransactionRepositoryV1GetListGet","parameters":[{"name":"searchCriteria[filterGroups][][filters][][field]","in":"query","type":"string","description":"Field"},{"name":"searchCriteria[filterGroups][][filters][][value]","in":"query","type":"string","description":"Value"},{"name":"searchCriteria[filterGroups][][filters][][conditionType]","in":"query","type":"string","description":"Condition type"},{"name":"searchCriteria[sortOrders][][field]","in":"query","type":"string","description":"Sorting field."},{"name":"searchCriteria[sortOrders][][direction]","in":"query","type":"string","description":"Sorting direction."},{"name":"searchCriteria[pageSize]","in":"query","type":"integer","description":"Page size."},{"name":"searchCriteria[currentPage]","in":"query","type":"integer","description":"Current page."}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/sales-data-transaction-search-result-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/order/{orderId}/invoice":{"post":{"tags":["salesInvoiceOrderV1"],"description":"","operationId":"salesInvoiceOrderV1ExecutePost","parameters":[{"name":"orderId","in":"path","type":"integer","required":true},{"name":"$body","in":"body","schema":{"properties":{"capture":{"type":"boolean"},"items":{"type":"array","items":{"$ref":"#/definitions/sales-data-invoice-item-creation-interface"}},"notify":{"type":"boolean"},"appendComment":{"type":"boolean"},"comment":{"$ref":"#/definitions/sales-data-invoice-comment-creation-interface"},"arguments":{"$ref":"#/definitions/sales-data-invoice-creation-arguments-interface"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"type":"integer"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/guest-carts/{cartId}/shipping-information":{"post":{"tags":["checkoutGuestShippingInformationManagementV1"],"description":"","operationId":"checkoutGuestShippingInformationManagementV1SaveAddressInformationPost","parameters":[{"name":"cartId","in":"path","type":"string","required":true},{"name":"$body","in":"body","schema":{"required":["addressInformation"],"properties":{"addressInformation":{"$ref":"#/definitions/checkout-data-shipping-information-interface"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/checkout-data-payment-details-interface"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/carts/mine/shipping-information":{"post":{"tags":["checkoutShippingInformationManagementV1"],"description":"","operationId":"checkoutShippingInformationManagementV1SaveAddressInformationPost","parameters":[{"name":"$body","in":"body","schema":{"required":["addressInformation"],"properties":{"addressInformation":{"$ref":"#/definitions/checkout-data-shipping-information-interface"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/checkout-data-payment-details-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/carts/{cartId}/shipping-information":{"post":{"tags":["checkoutShippingInformationManagementV1"],"description":"","operationId":"checkoutShippingInformationManagementV1SaveAddressInformationPost","parameters":[{"name":"cartId","in":"path","type":"integer","required":true},{"name":"$body","in":"body","schema":{"required":["addressInformation"],"properties":{"addressInformation":{"$ref":"#/definitions/checkout-data-shipping-information-interface"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/checkout-data-payment-details-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/carts/{cartId}/totals-information":{"post":{"tags":["checkoutTotalsInformationManagementV1"],"description":"Calculate quote totals based on address and shipping method.","operationId":"checkoutTotalsInformationManagementV1CalculatePost","parameters":[{"name":"cartId","in":"path","type":"integer","required":true},{"name":"$body","in":"body","schema":{"required":["addressInformation"],"properties":{"addressInformation":{"$ref":"#/definitions/checkout-data-totals-information-interface"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/quote-data-totals-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/carts/mine/totals-information":{"post":{"tags":["checkoutTotalsInformationManagementV1"],"description":"Calculate quote totals based on address and shipping method.","operationId":"checkoutTotalsInformationManagementV1CalculatePost","parameters":[{"name":"$body","in":"body","schema":{"required":["addressInformation"],"properties":{"addressInformation":{"$ref":"#/definitions/checkout-data-totals-information-interface"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/quote-data-totals-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/guest-carts/{cartId}/totals-information":{"post":{"tags":["checkoutGuestTotalsInformationManagementV1"],"description":"Calculate quote totals based on address and shipping method.","operationId":"checkoutGuestTotalsInformationManagementV1CalculatePost","parameters":[{"name":"cartId","in":"path","type":"string","required":true},{"name":"$body","in":"body","schema":{"required":["addressInformation"],"properties":{"addressInformation":{"$ref":"#/definitions/checkout-data-totals-information-interface"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/quote-data-totals-interface"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/guest-carts/{cartId}/payment-information":{"post":{"tags":["checkoutGuestPaymentInformationManagementV1"],"description":"Set payment information and place order for a specified cart.","operationId":"checkoutGuestPaymentInformationManagementV1SavePaymentInformationAndPlaceOrderPost","parameters":[{"name":"cartId","in":"path","type":"string","required":true},{"name":"$body","in":"body","schema":{"required":["email","paymentMethod"],"properties":{"email":{"type":"string"},"paymentMethod":{"$ref":"#/definitions/quote-data-payment-interface"},"billingAddress":{"$ref":"#/definitions/quote-data-address-interface"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"type":"integer","description":"Order ID."}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}},"get":{"tags":["checkoutGuestPaymentInformationManagementV1"],"description":"Get payment information","operationId":"checkoutGuestPaymentInformationManagementV1GetPaymentInformationGet","parameters":[{"name":"cartId","in":"path","type":"string","required":true}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/checkout-data-payment-details-interface"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/guest-carts/{cartId}/set-payment-information":{"post":{"tags":["checkoutGuestPaymentInformationManagementV1"],"description":"Set payment information for a specified cart.","operationId":"checkoutGuestPaymentInformationManagementV1SavePaymentInformationPost","parameters":[{"name":"cartId","in":"path","type":"string","required":true},{"name":"$body","in":"body","schema":{"required":["email","paymentMethod"],"properties":{"email":{"type":"string"},"paymentMethod":{"$ref":"#/definitions/quote-data-payment-interface"},"billingAddress":{"$ref":"#/definitions/quote-data-address-interface"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"type":"integer","description":"Order ID."}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/carts/mine/payment-information":{"post":{"tags":["checkoutPaymentInformationManagementV1"],"description":"Set payment information and place order for a specified cart.","operationId":"checkoutPaymentInformationManagementV1SavePaymentInformationAndPlaceOrderPost","parameters":[{"name":"$body","in":"body","schema":{"required":["paymentMethod"],"properties":{"paymentMethod":{"$ref":"#/definitions/quote-data-payment-interface"},"billingAddress":{"$ref":"#/definitions/quote-data-address-interface"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"type":"integer","description":"Order ID."}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}},"get":{"tags":["checkoutPaymentInformationManagementV1"],"description":"Get payment information","operationId":"checkoutPaymentInformationManagementV1GetPaymentInformationGet","responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/checkout-data-payment-details-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/carts/mine/set-payment-information":{"post":{"tags":["checkoutPaymentInformationManagementV1"],"description":"Set payment information for a specified cart.","operationId":"checkoutPaymentInformationManagementV1SavePaymentInformationPost","parameters":[{"name":"$body","in":"body","schema":{"required":["paymentMethod"],"properties":{"paymentMethod":{"$ref":"#/definitions/quote-data-payment-interface"},"billingAddress":{"$ref":"#/definitions/quote-data-address-interface"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"type":"integer","description":"Order ID."}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/products/{sku}/downloadable-links":{"get":{"tags":["downloadableLinkRepositoryV1"],"description":"List of links with associated samples","operationId":"downloadableLinkRepositoryV1GetListGet","parameters":[{"name":"sku","in":"path","type":"string","required":true}],"responses":{"200":{"description":"200 Success.","schema":{"type":"array","items":{"$ref":"#/definitions/downloadable-data-link-interface"}}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}},"post":{"tags":["downloadableLinkRepositoryV1"],"description":"Update downloadable link of the given product (link type and its resources cannot be changed)","operationId":"downloadableLinkRepositoryV1SavePost","parameters":[{"name":"sku","in":"path","type":"string","required":true},{"name":"$body","in":"body","schema":{"required":["link"],"properties":{"link":{"$ref":"#/definitions/downloadable-data-link-interface"},"isGlobalScopeContent":{"type":"boolean"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"type":"integer"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/products/{sku}/downloadable-links/{id}":{"put":{"tags":["downloadableLinkRepositoryV1"],"description":"Update downloadable link of the given product (link type and its resources cannot be changed)","operationId":"downloadableLinkRepositoryV1SavePut","parameters":[{"name":"sku","in":"path","type":"string","required":true},{"name":"id","in":"path","type":"string","required":true},{"name":"$body","in":"body","schema":{"required":["link"],"properties":{"link":{"$ref":"#/definitions/downloadable-data-link-interface"},"isGlobalScopeContent":{"type":"boolean"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"type":"integer"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/products/downloadable-links/{id}":{"delete":{"tags":["downloadableLinkRepositoryV1"],"description":"Delete downloadable link","operationId":"downloadableLinkRepositoryV1DeleteDelete","parameters":[{"name":"id","in":"path","type":"integer","required":true}],"responses":{"200":{"description":"200 Success.","schema":{"type":"boolean"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/products/{sku}/downloadable-links/samples":{"get":{"tags":["downloadableSampleRepositoryV1"],"description":"List of samples for downloadable product","operationId":"downloadableSampleRepositoryV1GetListGet","parameters":[{"name":"sku","in":"path","type":"string","required":true}],"responses":{"200":{"description":"200 Success.","schema":{"type":"array","items":{"$ref":"#/definitions/downloadable-data-sample-interface"}}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}},"post":{"tags":["downloadableSampleRepositoryV1"],"description":"Update downloadable sample of the given product","operationId":"downloadableSampleRepositoryV1SavePost","parameters":[{"name":"sku","in":"path","type":"string","required":true},{"name":"$body","in":"body","schema":{"required":["sample"],"properties":{"sample":{"$ref":"#/definitions/downloadable-data-sample-interface"},"isGlobalScopeContent":{"type":"boolean"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"type":"integer"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/products/{sku}/downloadable-links/samples/{id}":{"put":{"tags":["downloadableSampleRepositoryV1"],"description":"Update downloadable sample of the given product","operationId":"downloadableSampleRepositoryV1SavePut","parameters":[{"name":"sku","in":"path","type":"string","required":true},{"name":"id","in":"path","type":"string","required":true},{"name":"$body","in":"body","schema":{"required":["sample"],"properties":{"sample":{"$ref":"#/definitions/downloadable-data-sample-interface"},"isGlobalScopeContent":{"type":"boolean"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"type":"integer"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/products/downloadable-links/samples/{id}":{"delete":{"tags":["downloadableSampleRepositoryV1"],"description":"Delete downloadable sample","operationId":"downloadableSampleRepositoryV1DeleteDelete","parameters":[{"name":"id","in":"path","type":"integer","required":true}],"responses":{"200":{"description":"200 Success.","schema":{"type":"boolean"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/carts/licence":{"get":{"tags":["checkoutAgreementsCheckoutAgreementsRepositoryV1"],"description":"Lists active checkout agreements.","operationId":"checkoutAgreementsCheckoutAgreementsRepositoryV1GetListGet","responses":{"200":{"description":"200 Success.","schema":{"type":"array","items":{"$ref":"#/definitions/checkout-agreements-data-agreement-interface"}}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/configurable-products/{sku}/children":{"get":{"tags":["configurableProductLinkManagementV1"],"description":"Get all children for Configurable product","operationId":"configurableProductLinkManagementV1GetChildrenGet","parameters":[{"name":"sku","in":"path","type":"string","required":true}],"responses":{"200":{"description":"200 Success.","schema":{"type":"array","items":{"$ref":"#/definitions/catalog-data-product-interface"}}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/configurable-products/{sku}/children/{childSku}":{"delete":{"tags":["configurableProductLinkManagementV1"],"description":"Remove configurable product option","operationId":"configurableProductLinkManagementV1RemoveChildDelete","parameters":[{"name":"sku","in":"path","type":"string","required":true},{"name":"childSku","in":"path","type":"string","required":true}],"responses":{"200":{"description":"200 Success.","schema":{"type":"boolean"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/configurable-products/{sku}/child":{"post":{"tags":["configurableProductLinkManagementV1"],"description":"","operationId":"configurableProductLinkManagementV1AddChildPost","parameters":[{"name":"sku","in":"path","type":"string","required":true},{"name":"$body","in":"body","schema":{"required":["childSku"],"properties":{"childSku":{"type":"string"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"type":"boolean"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/configurable-products/variation":{"put":{"tags":["configurableProductConfigurableProductManagementV1"],"description":"Generate variation based on same product","operationId":"configurableProductConfigurableProductManagementV1GenerateVariationPut","parameters":[{"name":"$body","in":"body","schema":{"required":["product","options"],"properties":{"product":{"$ref":"#/definitions/catalog-data-product-interface"},"options":{"type":"array","items":{"$ref":"#/definitions/configurable-product-data-option-interface"}}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"type":"array","items":{"$ref":"#/definitions/catalog-data-product-interface"}}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/configurable-products/{sku}/options/{id}":{"get":{"tags":["configurableProductOptionRepositoryV1"],"description":"Get option for configurable product","operationId":"configurableProductOptionRepositoryV1GetGet","parameters":[{"name":"sku","in":"path","type":"string","required":true},{"name":"id","in":"path","type":"integer","required":true}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/configurable-product-data-option-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}},"put":{"tags":["configurableProductOptionRepositoryV1"],"description":"Save option","operationId":"configurableProductOptionRepositoryV1SavePut","parameters":[{"name":"sku","in":"path","type":"string","required":true},{"name":"id","in":"path","type":"string","required":true},{"name":"$body","in":"body","schema":{"required":["option"],"properties":{"option":{"$ref":"#/definitions/configurable-product-data-option-interface"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"type":"integer"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"500":{"description":"Internal Server error","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}},"delete":{"tags":["configurableProductOptionRepositoryV1"],"description":"Remove option from configurable product","operationId":"configurableProductOptionRepositoryV1DeleteByIdDelete","parameters":[{"name":"sku","in":"path","type":"string","required":true},{"name":"id","in":"path","type":"integer","required":true}],"responses":{"200":{"description":"200 Success.","schema":{"type":"boolean"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/configurable-products/{sku}/options/all":{"get":{"tags":["configurableProductOptionRepositoryV1"],"description":"Get all options for configurable product","operationId":"configurableProductOptionRepositoryV1GetListGet","parameters":[{"name":"sku","in":"path","type":"string","required":true}],"responses":{"200":{"description":"200 Success.","schema":{"type":"array","items":{"$ref":"#/definitions/configurable-product-data-option-interface"}}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/configurable-products/{sku}/options":{"post":{"tags":["configurableProductOptionRepositoryV1"],"description":"Save option","operationId":"configurableProductOptionRepositoryV1SavePost","parameters":[{"name":"sku","in":"path","type":"string","required":true},{"name":"$body","in":"body","schema":{"required":["option"],"properties":{"option":{"$ref":"#/definitions/configurable-product-data-option-interface"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"type":"integer"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"500":{"description":"Internal Server error","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/carts/mine/balance/apply":{"post":{"tags":["customerBalanceBalanceManagementV1"],"description":"Apply store credit","operationId":"customerBalanceBalanceManagementV1ApplyPost","responses":{"200":{"description":"200 Success.","schema":{"type":"boolean"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/carts/{quoteId}/giftCards":{"get":{"tags":["giftCardAccountGiftCardAccountManagementV1"],"description":"Return GiftCard Account cards","operationId":"giftCardAccountGiftCardAccountManagementV1GetListByQuoteIdGet","parameters":[{"name":"quoteId","in":"path","type":"integer","required":true}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/gift-card-account-data-gift-card-account-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/carts/{cartId}/giftCards":{"put":{"tags":["giftCardAccountGiftCardAccountManagementV1"],"description":"","operationId":"giftCardAccountGiftCardAccountManagementV1SaveByQuoteIdPut","parameters":[{"name":"cartId","in":"path","type":"integer","required":true},{"name":"$body","in":"body","schema":{"required":["giftCardAccountData"],"properties":{"giftCardAccountData":{"$ref":"#/definitions/gift-card-account-data-gift-card-account-interface"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"type":"boolean"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/carts/{cartId}/giftCards/{giftCardCode}":{"delete":{"tags":["giftCardAccountGiftCardAccountManagementV1"],"description":"Remove GiftCard Account entity","operationId":"giftCardAccountGiftCardAccountManagementV1DeleteByQuoteIdDelete","parameters":[{"name":"cartId","in":"path","type":"integer","required":true},{"name":"giftCardCode","in":"path","type":"string","required":true}],"responses":{"200":{"description":"200 Success.","schema":{"type":"boolean"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/carts/mine/giftCards/{giftCardCode}":{"delete":{"tags":["giftCardAccountGiftCardAccountManagementV1"],"description":"Remove GiftCard Account entity","operationId":"giftCardAccountGiftCardAccountManagementV1DeleteByQuoteIdDelete","parameters":[{"name":"giftCardCode","in":"path","type":"string","required":true}],"responses":{"200":{"description":"200 Success.","schema":{"type":"boolean"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/carts/mine/giftCards":{"post":{"tags":["giftCardAccountGiftCardAccountManagementV1"],"description":"","operationId":"giftCardAccountGiftCardAccountManagementV1SaveByQuoteIdPost","parameters":[{"name":"$body","in":"body","schema":{"required":["giftCardAccountData"],"properties":{"giftCardAccountData":{"$ref":"#/definitions/gift-card-account-data-gift-card-account-interface"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"type":"boolean"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/carts/mine/checkGiftCard/{giftCardCode}":{"get":{"tags":["giftCardAccountGiftCardAccountManagementV1"],"description":"","operationId":"giftCardAccountGiftCardAccountManagementV1CheckGiftCardGet","parameters":[{"name":"giftCardCode","in":"path","type":"string","required":true}],"responses":{"200":{"description":"200 Success.","schema":{"type":"number"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/carts/guest-carts/{cartId}/giftCards/{giftCardCode}":{"delete":{"tags":["giftCardAccountGuestGiftCardAccountManagementV1"],"description":"Remove GiftCard Account entity","operationId":"giftCardAccountGuestGiftCardAccountManagementV1DeleteByQuoteIdDelete","parameters":[{"name":"cartId","in":"path","type":"string","required":true},{"name":"giftCardCode","in":"path","type":"string","required":true}],"responses":{"200":{"description":"200 Success.","schema":{"type":"boolean"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/carts/guest-carts/{cartId}/giftCards":{"post":{"tags":["giftCardAccountGuestGiftCardAccountManagementV1"],"description":"","operationId":"giftCardAccountGuestGiftCardAccountManagementV1AddGiftCardPost","parameters":[{"name":"cartId","in":"path","type":"string","required":true},{"name":"$body","in":"body","schema":{"required":["giftCardAccountData"],"properties":{"giftCardAccountData":{"$ref":"#/definitions/gift-card-account-data-gift-card-account-interface"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"type":"boolean"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/carts/guest-carts/{cartId}/checkGiftCard/{giftCardCode}":{"get":{"tags":["giftCardAccountGuestGiftCardAccountManagementV1"],"description":"","operationId":"giftCardAccountGuestGiftCardAccountManagementV1CheckGiftCardGet","parameters":[{"name":"cartId","in":"path","type":"string","required":true},{"name":"giftCardCode","in":"path","type":"string","required":true}],"responses":{"200":{"description":"200 Success.","schema":{"type":"number"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/taxRates":{"post":{"tags":["taxTaxRateRepositoryV1"],"description":"Create or update tax rate","operationId":"taxTaxRateRepositoryV1SavePost","parameters":[{"name":"$body","in":"body","schema":{"required":["taxRate"],"properties":{"taxRate":{"$ref":"#/definitions/tax-data-tax-rate-interface"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/tax-data-tax-rate-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"500":{"description":"Internal Server error","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}},"put":{"tags":["taxTaxRateRepositoryV1"],"description":"Create or update tax rate","operationId":"taxTaxRateRepositoryV1SavePut","parameters":[{"name":"$body","in":"body","schema":{"required":["taxRate"],"properties":{"taxRate":{"$ref":"#/definitions/tax-data-tax-rate-interface"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/tax-data-tax-rate-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"500":{"description":"Internal Server error","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/taxRates/{rateId}":{"get":{"tags":["taxTaxRateRepositoryV1"],"description":"Get tax rate","operationId":"taxTaxRateRepositoryV1GetGet","parameters":[{"name":"rateId","in":"path","type":"integer","required":true}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/tax-data-tax-rate-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}},"delete":{"tags":["taxTaxRateRepositoryV1"],"description":"Delete tax rate","operationId":"taxTaxRateRepositoryV1DeleteByIdDelete","parameters":[{"name":"rateId","in":"path","type":"integer","required":true}],"responses":{"200":{"description":"200 Success.","schema":{"type":"boolean"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"500":{"description":"Internal Server error","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/taxRates/search":{"get":{"tags":["taxTaxRateRepositoryV1"],"description":"Search TaxRates This call returns an array of objects, but detailed information about each object\u2019s attributes might not be included. See http://devdocs.magento.com/codelinks/attributes.html#TaxRateRepositoryInterface to determine which call to use to get detailed information about all attributes for an object.","operationId":"taxTaxRateRepositoryV1GetListGet","parameters":[{"name":"searchCriteria[filterGroups][][filters][][field]","in":"query","type":"string","description":"Field"},{"name":"searchCriteria[filterGroups][][filters][][value]","in":"query","type":"string","description":"Value"},{"name":"searchCriteria[filterGroups][][filters][][conditionType]","in":"query","type":"string","description":"Condition type"},{"name":"searchCriteria[sortOrders][][field]","in":"query","type":"string","description":"Sorting field."},{"name":"searchCriteria[sortOrders][][direction]","in":"query","type":"string","description":"Sorting direction."},{"name":"searchCriteria[pageSize]","in":"query","type":"integer","description":"Page size."},{"name":"searchCriteria[currentPage]","in":"query","type":"integer","description":"Current page."}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/tax-data-tax-rate-search-results-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/taxRules":{"post":{"tags":["taxTaxRuleRepositoryV1"],"description":"Save TaxRule","operationId":"taxTaxRuleRepositoryV1SavePost","parameters":[{"name":"$body","in":"body","schema":{"required":["rule"],"properties":{"rule":{"$ref":"#/definitions/tax-data-tax-rule-interface"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/tax-data-tax-rule-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"500":{"description":"Internal Server error","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}},"put":{"tags":["taxTaxRuleRepositoryV1"],"description":"Save TaxRule","operationId":"taxTaxRuleRepositoryV1SavePut","parameters":[{"name":"$body","in":"body","schema":{"required":["rule"],"properties":{"rule":{"$ref":"#/definitions/tax-data-tax-rule-interface"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/tax-data-tax-rule-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"500":{"description":"Internal Server error","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/taxRules/{ruleId}":{"delete":{"tags":["taxTaxRuleRepositoryV1"],"description":"Delete TaxRule","operationId":"taxTaxRuleRepositoryV1DeleteByIdDelete","parameters":[{"name":"ruleId","in":"path","type":"integer","required":true}],"responses":{"200":{"description":"200 Success.","schema":{"type":"boolean"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"500":{"description":"Internal Server error","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}},"get":{"tags":["taxTaxRuleRepositoryV1"],"description":"Get TaxRule","operationId":"taxTaxRuleRepositoryV1GetGet","parameters":[{"name":"ruleId","in":"path","type":"integer","required":true}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/tax-data-tax-rule-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/taxRules/search":{"get":{"tags":["taxTaxRuleRepositoryV1"],"description":"Search TaxRules This call returns an array of objects, but detailed information about each object\u2019s attributes might not be included. See http://devdocs.magento.com/codelinks/attributes.html#TaxRuleRepositoryInterface to determine which call to use to get detailed information about all attributes for an object.","operationId":"taxTaxRuleRepositoryV1GetListGet","parameters":[{"name":"searchCriteria[filterGroups][][filters][][field]","in":"query","type":"string","description":"Field"},{"name":"searchCriteria[filterGroups][][filters][][value]","in":"query","type":"string","description":"Value"},{"name":"searchCriteria[filterGroups][][filters][][conditionType]","in":"query","type":"string","description":"Condition type"},{"name":"searchCriteria[sortOrders][][field]","in":"query","type":"string","description":"Sorting field."},{"name":"searchCriteria[sortOrders][][direction]","in":"query","type":"string","description":"Sorting direction."},{"name":"searchCriteria[pageSize]","in":"query","type":"integer","description":"Page size."},{"name":"searchCriteria[currentPage]","in":"query","type":"integer","description":"Current page."}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/tax-data-tax-rule-search-results-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/taxClasses":{"post":{"tags":["taxTaxClassRepositoryV1"],"description":"Create a Tax Class","operationId":"taxTaxClassRepositoryV1SavePost","parameters":[{"name":"$body","in":"body","schema":{"required":["taxClass"],"properties":{"taxClass":{"$ref":"#/definitions/tax-data-tax-class-interface"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"type":"string","description":"id for the newly created Tax class"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"500":{"description":"Internal Server error","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/taxClasses/{taxClassId}":{"get":{"tags":["taxTaxClassRepositoryV1"],"description":"Get a tax class with the given tax class id.","operationId":"taxTaxClassRepositoryV1GetGet","parameters":[{"name":"taxClassId","in":"path","type":"integer","required":true}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/tax-data-tax-class-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}},"delete":{"tags":["taxTaxClassRepositoryV1"],"description":"Delete a tax class with the given tax class id.","operationId":"taxTaxClassRepositoryV1DeleteByIdDelete","parameters":[{"name":"taxClassId","in":"path","type":"integer","required":true}],"responses":{"200":{"description":"200 Success.","schema":{"type":"boolean","description":"True if the tax class was deleted, false otherwise"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/taxClasses/{classId}":{"put":{"tags":["taxTaxClassRepositoryV1"],"description":"Create a Tax Class","operationId":"taxTaxClassRepositoryV1SavePut","parameters":[{"name":"classId","in":"path","type":"string","required":true},{"name":"$body","in":"body","schema":{"required":["taxClass"],"properties":{"taxClass":{"$ref":"#/definitions/tax-data-tax-class-interface"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"type":"string","description":"id for the newly created Tax class"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"500":{"description":"Internal Server error","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/taxClasses/search":{"get":{"tags":["taxTaxClassRepositoryV1"],"description":"Retrieve tax classes which match a specific criteria. This call returns an array of objects, but detailed information about each object\u2019s attributes might not be included. See http://devdocs.magento.com/codelinks/attributes.html#TaxClassRepositoryInterface to determine which call to use to get detailed information about all attributes for an object.","operationId":"taxTaxClassRepositoryV1GetListGet","parameters":[{"name":"searchCriteria[filterGroups][][filters][][field]","in":"query","type":"string","description":"Field"},{"name":"searchCriteria[filterGroups][][filters][][value]","in":"query","type":"string","description":"Value"},{"name":"searchCriteria[filterGroups][][filters][][conditionType]","in":"query","type":"string","description":"Condition type"},{"name":"searchCriteria[sortOrders][][field]","in":"query","type":"string","description":"Sorting field."},{"name":"searchCriteria[sortOrders][][direction]","in":"query","type":"string","description":"Sorting direction."},{"name":"searchCriteria[pageSize]","in":"query","type":"integer","description":"Page size."},{"name":"searchCriteria[currentPage]","in":"query","type":"integer","description":"Current page."}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/tax-data-tax-class-search-results-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/carts/{cartId}/gift-message":{"get":{"tags":["giftMessageCartRepositoryV1"],"description":"Return the gift message for a specified order.","operationId":"giftMessageCartRepositoryV1GetGet","parameters":[{"name":"cartId","in":"path","type":"integer","required":true,"description":"The shopping cart ID."}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/gift-message-data-message-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}},"post":{"tags":["giftMessageCartRepositoryV1"],"description":"Set the gift message for an entire order.","operationId":"giftMessageCartRepositoryV1SavePost","parameters":[{"name":"cartId","in":"path","type":"integer","required":true,"description":"The cart ID."},{"name":"$body","in":"body","schema":{"required":["giftMessage"],"properties":{"giftMessage":{"$ref":"#/definitions/gift-message-data-message-interface"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"type":"boolean"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/carts/mine/gift-message":{"get":{"tags":["giftMessageCartRepositoryV1"],"description":"Return the gift message for a specified order.","operationId":"giftMessageCartRepositoryV1GetGet","responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/gift-message-data-message-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}},"post":{"tags":["giftMessageCartRepositoryV1"],"description":"Set the gift message for an entire order.","operationId":"giftMessageCartRepositoryV1SavePost","parameters":[{"name":"$body","in":"body","schema":{"required":["giftMessage"],"properties":{"giftMessage":{"$ref":"#/definitions/gift-message-data-message-interface"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"type":"boolean"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/carts/{cartId}/gift-message/{itemId}":{"get":{"tags":["giftMessageItemRepositoryV1"],"description":"Return the gift message for a specified item in a specified shopping cart.","operationId":"giftMessageItemRepositoryV1GetGet","parameters":[{"name":"cartId","in":"path","type":"integer","required":true,"description":"The shopping cart ID."},{"name":"itemId","in":"path","type":"integer","required":true,"description":"The item ID."}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/gift-message-data-message-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}},"post":{"tags":["giftMessageItemRepositoryV1"],"description":"Set the gift message for a specified item in a specified shopping cart.","operationId":"giftMessageItemRepositoryV1SavePost","parameters":[{"name":"cartId","in":"path","type":"integer","required":true,"description":"The cart ID."},{"name":"itemId","in":"path","type":"integer","required":true,"description":"The item ID."},{"name":"$body","in":"body","schema":{"required":["giftMessage"],"properties":{"giftMessage":{"$ref":"#/definitions/gift-message-data-message-interface"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"type":"boolean"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/carts/mine/gift-message/{itemId}":{"get":{"tags":["giftMessageItemRepositoryV1"],"description":"Return the gift message for a specified item in a specified shopping cart.","operationId":"giftMessageItemRepositoryV1GetGet","parameters":[{"name":"itemId","in":"path","type":"integer","required":true,"description":"The item ID."}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/gift-message-data-message-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}},"post":{"tags":["giftMessageItemRepositoryV1"],"description":"Set the gift message for a specified item in a specified shopping cart.","operationId":"giftMessageItemRepositoryV1SavePost","parameters":[{"name":"itemId","in":"path","type":"integer","required":true,"description":"The item ID."},{"name":"$body","in":"body","schema":{"required":["giftMessage"],"properties":{"giftMessage":{"$ref":"#/definitions/gift-message-data-message-interface"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"type":"boolean"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/guest-carts/{cartId}/gift-message":{"get":{"tags":["giftMessageGuestCartRepositoryV1"],"description":"Return the gift message for a specified order.","operationId":"giftMessageGuestCartRepositoryV1GetGet","parameters":[{"name":"cartId","in":"path","type":"string","required":true,"description":"The shopping cart ID."}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/gift-message-data-message-interface"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}},"post":{"tags":["giftMessageGuestCartRepositoryV1"],"description":"Set the gift message for an entire order.","operationId":"giftMessageGuestCartRepositoryV1SavePost","parameters":[{"name":"cartId","in":"path","type":"string","required":true,"description":"The cart ID."},{"name":"$body","in":"body","schema":{"required":["giftMessage"],"properties":{"giftMessage":{"$ref":"#/definitions/gift-message-data-message-interface"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"type":"boolean"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/guest-carts/{cartId}/gift-message/{itemId}":{"get":{"tags":["giftMessageGuestItemRepositoryV1"],"description":"Return the gift message for a specified item in a specified shopping cart.","operationId":"giftMessageGuestItemRepositoryV1GetGet","parameters":[{"name":"cartId","in":"path","type":"string","required":true,"description":"The shopping cart ID."},{"name":"itemId","in":"path","type":"integer","required":true,"description":"The item ID."}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/gift-message-data-message-interface"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}},"post":{"tags":["giftMessageGuestItemRepositoryV1"],"description":"Set the gift message for a specified item in a specified shopping cart.","operationId":"giftMessageGuestItemRepositoryV1SavePost","parameters":[{"name":"cartId","in":"path","type":"string","required":true,"description":"The cart ID."},{"name":"itemId","in":"path","type":"integer","required":true,"description":"The item ID."},{"name":"$body","in":"body","schema":{"required":["giftMessage"],"properties":{"giftMessage":{"$ref":"#/definitions/gift-message-data-message-interface"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"type":"boolean"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/gift-wrappings/{id}":{"get":{"tags":["giftWrappingWrappingRepositoryV1"],"description":"Return data object for specified wrapping ID and store.","operationId":"giftWrappingWrappingRepositoryV1GetGet","parameters":[{"name":"id","in":"path","type":"integer","required":true},{"name":"storeId","in":"query","type":"integer","required":false}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/gift-wrapping-data-wrapping-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}},"delete":{"tags":["giftWrappingWrappingRepositoryV1"],"description":"Delete gift wrapping","operationId":"giftWrappingWrappingRepositoryV1DeleteByIdDelete","parameters":[{"name":"id","in":"path","type":"integer","required":true}],"responses":{"200":{"description":"200 Success.","schema":{"type":"boolean"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/gift-wrappings":{"post":{"tags":["giftWrappingWrappingRepositoryV1"],"description":"Create/Update new gift wrapping with data object values","operationId":"giftWrappingWrappingRepositoryV1SavePost","parameters":[{"name":"$body","in":"body","schema":{"required":["data"],"properties":{"data":{"$ref":"#/definitions/gift-wrapping-data-wrapping-interface"},"storeId":{"type":"integer"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/gift-wrapping-data-wrapping-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}},"get":{"tags":["giftWrappingWrappingRepositoryV1"],"description":"Return list of gift wrapping data objects based on search criteria","operationId":"giftWrappingWrappingRepositoryV1GetListGet","parameters":[{"name":"searchCriteria[filterGroups][][filters][][field]","in":"query","type":"string","description":"Field"},{"name":"searchCriteria[filterGroups][][filters][][value]","in":"query","type":"string","description":"Value"},{"name":"searchCriteria[filterGroups][][filters][][conditionType]","in":"query","type":"string","description":"Condition type"},{"name":"searchCriteria[sortOrders][][field]","in":"query","type":"string","description":"Sorting field."},{"name":"searchCriteria[sortOrders][][direction]","in":"query","type":"string","description":"Sorting direction."},{"name":"searchCriteria[pageSize]","in":"query","type":"integer","description":"Page size."},{"name":"searchCriteria[currentPage]","in":"query","type":"integer","description":"Current page."}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/gift-wrapping-data-wrapping-search-results-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/gift-wrappings/{wrappingId}":{"put":{"tags":["giftWrappingWrappingRepositoryV1"],"description":"Create/Update new gift wrapping with data object values","operationId":"giftWrappingWrappingRepositoryV1SavePut","parameters":[{"name":"wrappingId","in":"path","type":"string","required":true},{"name":"$body","in":"body","schema":{"required":["data"],"properties":{"data":{"$ref":"#/definitions/gift-wrapping-data-wrapping-interface"},"storeId":{"type":"integer"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/gift-wrapping-data-wrapping-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/salesRules/{ruleId}":{"get":{"tags":["salesRuleRuleRepositoryV1"],"description":"Get rule by ID.","operationId":"salesRuleRuleRepositoryV1GetByIdGet","parameters":[{"name":"ruleId","in":"path","type":"integer","required":true}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/sales-rule-data-rule-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"500":{"description":"Internal Server error","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}},"put":{"tags":["salesRuleRuleRepositoryV1"],"description":"Save sales rule.","operationId":"salesRuleRuleRepositoryV1SavePut","parameters":[{"name":"ruleId","in":"path","type":"string","required":true},{"name":"$body","in":"body","schema":{"required":["rule"],"properties":{"rule":{"$ref":"#/definitions/sales-rule-data-rule-interface"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/sales-rule-data-rule-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"500":{"description":"Internal Server error","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}},"delete":{"tags":["salesRuleRuleRepositoryV1"],"description":"Delete rule by ID.","operationId":"salesRuleRuleRepositoryV1DeleteByIdDelete","parameters":[{"name":"ruleId","in":"path","type":"integer","required":true}],"responses":{"200":{"description":"200 Success.","schema":{"type":"boolean","description":"true on success"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"500":{"description":"Internal Server error","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/salesRules/search":{"get":{"tags":["salesRuleRuleRepositoryV1"],"description":"Retrieve sales rules that match te specified criteria. This call returns an array of objects, but detailed information about each object\u2019s attributes might not be included. See http://devdocs.magento.com/codelinks/attributes.html#RuleRepositoryInterface to determine which call to use to get detailed information about all attributes for an object.","operationId":"salesRuleRuleRepositoryV1GetListGet","parameters":[{"name":"searchCriteria[filterGroups][][filters][][field]","in":"query","type":"string","description":"Field"},{"name":"searchCriteria[filterGroups][][filters][][value]","in":"query","type":"string","description":"Value"},{"name":"searchCriteria[filterGroups][][filters][][conditionType]","in":"query","type":"string","description":"Condition type"},{"name":"searchCriteria[sortOrders][][field]","in":"query","type":"string","description":"Sorting field."},{"name":"searchCriteria[sortOrders][][direction]","in":"query","type":"string","description":"Sorting direction."},{"name":"searchCriteria[pageSize]","in":"query","type":"integer","description":"Page size."},{"name":"searchCriteria[currentPage]","in":"query","type":"integer","description":"Current page."}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/sales-rule-data-rule-search-result-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"500":{"description":"Internal Server error","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/salesRules":{"post":{"tags":["salesRuleRuleRepositoryV1"],"description":"Save sales rule.","operationId":"salesRuleRuleRepositoryV1SavePost","parameters":[{"name":"$body","in":"body","schema":{"required":["rule"],"properties":{"rule":{"$ref":"#/definitions/sales-rule-data-rule-interface"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/sales-rule-data-rule-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"500":{"description":"Internal Server error","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/coupons/{couponId}":{"get":{"tags":["salesRuleCouponRepositoryV1"],"description":"Get coupon by coupon id.","operationId":"salesRuleCouponRepositoryV1GetByIdGet","parameters":[{"name":"couponId","in":"path","type":"integer","required":true}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/sales-rule-data-coupon-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"500":{"description":"Internal Server error","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}},"put":{"tags":["salesRuleCouponRepositoryV1"],"description":"Save a coupon.","operationId":"salesRuleCouponRepositoryV1SavePut","parameters":[{"name":"couponId","in":"path","type":"string","required":true},{"name":"$body","in":"body","schema":{"required":["coupon"],"properties":{"coupon":{"$ref":"#/definitions/sales-rule-data-coupon-interface"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/sales-rule-data-coupon-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"500":{"description":"Internal Server error","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}},"delete":{"tags":["salesRuleCouponRepositoryV1"],"description":"Delete coupon by coupon id.","operationId":"salesRuleCouponRepositoryV1DeleteByIdDelete","parameters":[{"name":"couponId","in":"path","type":"integer","required":true}],"responses":{"200":{"description":"200 Success.","schema":{"type":"boolean","description":"true on success"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"500":{"description":"Internal Server error","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/coupons/search":{"get":{"tags":["salesRuleCouponRepositoryV1"],"description":"Retrieve a coupon using the specified search criteria. This call returns an array of objects, but detailed information about each object\u2019s attributes might not be included. See http://devdocs.magento.com/codelinks/attributes.html#CouponRepositoryInterface to determine which call to use to get detailed information about all attributes for an object.","operationId":"salesRuleCouponRepositoryV1GetListGet","parameters":[{"name":"searchCriteria[filterGroups][][filters][][field]","in":"query","type":"string","description":"Field"},{"name":"searchCriteria[filterGroups][][filters][][value]","in":"query","type":"string","description":"Value"},{"name":"searchCriteria[filterGroups][][filters][][conditionType]","in":"query","type":"string","description":"Condition type"},{"name":"searchCriteria[sortOrders][][field]","in":"query","type":"string","description":"Sorting field."},{"name":"searchCriteria[sortOrders][][direction]","in":"query","type":"string","description":"Sorting direction."},{"name":"searchCriteria[pageSize]","in":"query","type":"integer","description":"Page size."},{"name":"searchCriteria[currentPage]","in":"query","type":"integer","description":"Current page."}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/sales-rule-data-coupon-search-result-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"500":{"description":"Internal Server error","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/coupons":{"post":{"tags":["salesRuleCouponRepositoryV1"],"description":"Save a coupon.","operationId":"salesRuleCouponRepositoryV1SavePost","parameters":[{"name":"$body","in":"body","schema":{"required":["coupon"],"properties":{"coupon":{"$ref":"#/definitions/sales-rule-data-coupon-interface"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/sales-rule-data-coupon-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"500":{"description":"Internal Server error","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/coupons/generate":{"post":{"tags":["salesRuleCouponManagementV1"],"description":"Generate coupon for a rule","operationId":"salesRuleCouponManagementV1GeneratePost","parameters":[{"name":"$body","in":"body","schema":{"required":["couponSpec"],"properties":{"couponSpec":{"$ref":"#/definitions/sales-rule-data-coupon-generation-spec-interface"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"type":"array","items":{"type":"string"}}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"500":{"description":"Internal Server error","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/coupons/deleteByIds":{"post":{"tags":["salesRuleCouponManagementV1"],"description":"Delete coupon by coupon ids.","operationId":"salesRuleCouponManagementV1DeleteByIdsPost","parameters":[{"name":"$body","in":"body","schema":{"required":["ids"],"properties":{"ids":{"type":"array","items":{"type":"integer"}},"ignoreInvalidCoupons":{"type":"boolean"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/sales-rule-data-coupon-mass-delete-result-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"500":{"description":"Internal Server error","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/coupons/deleteByCodes":{"post":{"tags":["salesRuleCouponManagementV1"],"description":"Delete coupon by coupon codes.","operationId":"salesRuleCouponManagementV1DeleteByCodesPost","parameters":[{"name":"$body","in":"body","schema":{"required":["codes"],"properties":{"codes":{"type":"array","items":{"type":"string"}},"ignoreInvalidCoupons":{"type":"boolean"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/sales-rule-data-coupon-mass-delete-result-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"500":{"description":"Internal Server error","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/giftregistry/mine/estimate-shipping-methods":{"post":{"tags":["giftRegistryShippingMethodManagementV1"],"description":"Estimate shipping","operationId":"giftRegistryShippingMethodManagementV1EstimateByRegistryIdPost","parameters":[{"name":"$body","in":"body","schema":{"required":["registryId"],"properties":{"registryId":{"type":"integer","description":"The estimate registry id"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"type":"array","description":"An array of shipping methods.","items":{"$ref":"#/definitions/quote-data-shipping-method-interface"}}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/guest-giftregistry/{cartId}/estimate-shipping-methods":{"post":{"tags":["giftRegistryGuestCartShippingMethodManagementV1"],"description":"Estimate shipping","operationId":"giftRegistryGuestCartShippingMethodManagementV1EstimateByRegistryIdPost","parameters":[{"name":"cartId","in":"path","type":"string","required":true,"description":"The shopping cart ID."},{"name":"$body","in":"body","schema":{"required":["registryId"],"properties":{"registryId":{"type":"integer","description":"The estimate registry id"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"type":"array","description":"An array of shipping methods.","items":{"$ref":"#/definitions/quote-data-shipping-method-interface"}}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/reward/mine/use-reward":{"post":{"tags":["rewardRewardManagementV1"],"description":"Set reward points to quote","operationId":"rewardRewardManagementV1SetPost","responses":{"200":{"description":"200 Success.","schema":{"type":"boolean"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/returns/{id}/tracking-numbers":{"post":{"tags":["rmaTrackManagementV1"],"description":"Add track","operationId":"rmaTrackManagementV1AddTrackPost","parameters":[{"name":"id","in":"path","type":"integer","required":true},{"name":"$body","in":"body","schema":{"required":["track"],"properties":{"track":{"$ref":"#/definitions/rma-data-track-interface"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"type":"boolean"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}},"get":{"tags":["rmaTrackManagementV1"],"description":"Get track list","operationId":"rmaTrackManagementV1GetTracksGet","parameters":[{"name":"id","in":"path","type":"integer","required":true}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/rma-data-track-search-result-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/returns/{id}/tracking-numbers/{trackId}":{"delete":{"tags":["rmaTrackManagementV1"],"description":"Remove track by id","operationId":"rmaTrackManagementV1RemoveTrackByIdDelete","parameters":[{"name":"id","in":"path","type":"integer","required":true},{"name":"trackId","in":"path","type":"integer","required":true}],"responses":{"200":{"description":"200 Success.","schema":{"type":"boolean"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/returns/{id}/labels":{"get":{"tags":["rmaTrackManagementV1"],"description":"Get shipping label int the PDF format","operationId":"rmaTrackManagementV1GetShippingLabelPdfGet","parameters":[{"name":"id","in":"path","type":"integer","required":true}],"responses":{"200":{"description":"200 Success.","schema":{"type":"string"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/returns/{id}":{"get":{"tags":["rmaRmaRepositoryV1"],"description":"Return data object for specified RMA id","operationId":"rmaRmaRepositoryV1GetGet","parameters":[{"name":"id","in":"path","type":"integer","required":true}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/rma-data-rma-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}},"delete":{"tags":["rmaRmaRepositoryV1"],"description":"Delete RMA","operationId":"rmaRmaRepositoryV1DeleteDelete","parameters":[{"name":"id","in":"path","type":"string","required":true},{"name":"$body","in":"body","schema":{"required":["rmaDataObject"],"properties":{"rmaDataObject":{"$ref":"#/definitions/rma-data-rma-interface"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"type":"boolean"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}},"put":{"tags":["rmaRmaManagementV1"],"description":"Save RMA","operationId":"rmaRmaManagementV1SaveRmaPut","parameters":[{"name":"id","in":"path","type":"string","required":true},{"name":"$body","in":"body","schema":{"required":["rmaDataObject"],"properties":{"rmaDataObject":{"$ref":"#/definitions/rma-data-rma-interface"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/rma-data-rma-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/returns/{id}/comments":{"post":{"tags":["rmaCommentManagementV1"],"description":"Add comment","operationId":"rmaCommentManagementV1AddCommentPost","parameters":[{"name":"id","in":"path","type":"string","required":true},{"name":"$body","in":"body","schema":{"required":["data"],"properties":{"data":{"$ref":"#/definitions/rma-data-comment-interface"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"type":"boolean"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"500":{"description":"Internal Server error","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}},"get":{"tags":["rmaCommentManagementV1"],"description":"Comments list","operationId":"rmaCommentManagementV1CommentsListGet","parameters":[{"name":"id","in":"path","type":"integer","required":true}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/rma-data-comment-search-result-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/returns":{"post":{"tags":["rmaRmaManagementV1"],"description":"Save RMA","operationId":"rmaRmaManagementV1SaveRmaPost","parameters":[{"name":"$body","in":"body","schema":{"required":["rmaDataObject"],"properties":{"rmaDataObject":{"$ref":"#/definitions/rma-data-rma-interface"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/rma-data-rma-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}},"get":{"tags":["rmaRmaManagementV1"],"description":"Return list of rma data objects based on search criteria","operationId":"rmaRmaManagementV1SearchGet","parameters":[{"name":"searchCriteria[filterGroups][][filters][][field]","in":"query","type":"string","description":"Field"},{"name":"searchCriteria[filterGroups][][filters][][value]","in":"query","type":"string","description":"Value"},{"name":"searchCriteria[filterGroups][][filters][][conditionType]","in":"query","type":"string","description":"Condition type"},{"name":"searchCriteria[sortOrders][][field]","in":"query","type":"string","description":"Sorting field."},{"name":"searchCriteria[sortOrders][][direction]","in":"query","type":"string","description":"Sorting direction."},{"name":"searchCriteria[pageSize]","in":"query","type":"integer","description":"Page size."},{"name":"searchCriteria[currentPage]","in":"query","type":"integer","description":"Current page."}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/rma-data-rma-search-result-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/returnsAttributeMetadata/{attributeCode}":{"get":{"tags":["rmaRmaAttributesManagementV1"],"description":"Retrieve attribute metadata.","operationId":"rmaRmaAttributesManagementV1GetAttributeMetadataGet","parameters":[{"name":"attributeCode","in":"path","type":"string","required":true}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/customer-data-attribute-metadata-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/returnsAttributeMetadata/form/{formCode}":{"get":{"tags":["rmaRmaAttributesManagementV1"],"description":"Retrieve all attributes filtered by form code","operationId":"rmaRmaAttributesManagementV1GetAttributesGet","parameters":[{"name":"formCode","in":"path","type":"string","required":true}],"responses":{"200":{"description":"200 Success.","schema":{"type":"array","items":{"$ref":"#/definitions/customer-data-attribute-metadata-interface"}}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/returnsAttributeMetadata":{"get":{"tags":["rmaRmaAttributesManagementV1"],"description":"Get all attribute metadata.","operationId":"rmaRmaAttributesManagementV1GetAllAttributesMetadataGet","responses":{"200":{"description":"200 Success.","schema":{"type":"array","items":{"$ref":"#/definitions/customer-data-attribute-metadata-interface"}}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/returnsAttributeMetadata/custom":{"get":{"tags":["rmaRmaAttributesManagementV1"],"description":"Get custom attribute metadata for the given Data object's attribute set","operationId":"rmaRmaAttributesManagementV1GetCustomAttributesMetadataGet","parameters":[{"name":"dataObjectClassName","in":"query","type":"string","description":"Data object class name","required":false}],"responses":{"200":{"description":"200 Success.","schema":{"type":"array","items":{"$ref":"#/definitions/framework-metadata-object-interface"}}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/integration/admin/token":{"post":{"tags":["integrationAdminTokenServiceV1"],"description":"Create access token for admin given the admin credentials.","operationId":"integrationAdminTokenServiceV1CreateAdminAccessTokenPost","parameters":[{"name":"$body","in":"body","schema":{"required":["username","password"],"properties":{"username":{"type":"string"},"password":{"type":"string"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"type":"string","description":"Token created"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"500":{"description":"Internal Server error","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/integration/customer/token":{"post":{"tags":["integrationCustomerTokenServiceV1"],"description":"Create access token for admin given the customer credentials.","operationId":"integrationCustomerTokenServiceV1CreateCustomerAccessTokenPost","parameters":[{"name":"$body","in":"body","schema":{"required":["username","password"],"properties":{"username":{"type":"string"},"password":{"type":"string"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"type":"string","description":"Token created"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/testmodule1/overwritten":{"get":{"tags":["testModule1AllSoapAndRestV1"],"description":"","operationId":"testModule1AllSoapAndRestV1ItemGet","responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/test-module1-v1-entity-item"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/testmodule1/testOptionalParam":{"post":{"tags":["testModule1AllSoapAndRestV1"],"description":"","operationId":"testModule1AllSoapAndRestV1TestOptionalParamPost","parameters":[{"name":"$body","in":"body","schema":{"properties":{"name":{"type":"string"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/test-module1-v1-entity-item"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/testmodule1/{itemId}":{"get":{"tags":["testModule1AllSoapAndRestV1"],"description":"","operationId":"testModule1AllSoapAndRestV1ItemGet","parameters":[{"name":"itemId","in":"path","type":"integer","required":true}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/test-module1-v1-entity-item"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}},"put":{"tags":["testModule1AllSoapAndRestV1"],"description":"","operationId":"testModule1AllSoapAndRestV1UpdatePut","parameters":[{"name":"itemId","in":"path","type":"string","required":true},{"name":"$body","in":"body","schema":{"required":["entityItem"],"properties":{"entityItem":{"$ref":"#/definitions/test-module1-v1-entity-item"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/test-module1-v1-entity-item"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/testmodule1":{"get":{"tags":["testModule1AllSoapAndRestV1"],"description":"","operationId":"testModule1AllSoapAndRestV1ItemsGet","responses":{"200":{"description":"200 Success.","schema":{"type":"array","items":{"$ref":"#/definitions/test-module1-v1-entity-item"}}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}},"post":{"tags":["testModule1AllSoapAndRestV1"],"description":"","operationId":"testModule1AllSoapAndRestV1CreatePost","parameters":[{"name":"$body","in":"body","schema":{"required":["name"],"properties":{"name":{"type":"string"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/test-module1-v1-entity-item"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/testmodule1/resource1/{itemId}":{"get":{"tags":["testModule1AllSoapAndRestV1"],"description":"","operationId":"testModule1AllSoapAndRestV1ItemGet","parameters":[{"name":"itemId","in":"path","type":"integer","required":true}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/test-module1-v1-entity-item"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/testmodule1/itemAnyType":{"post":{"tags":["testModule1AllSoapAndRestV1"],"description":"","operationId":"testModule1AllSoapAndRestV1ItemAnyTypePost","parameters":[{"name":"$body","in":"body","schema":{"required":["entityItem"],"properties":{"entityItem":{"$ref":"#/definitions/test-module1-v1-entity-item"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/test-module1-v1-entity-item"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/testmodule1/itemPreconfigured":{"get":{"tags":["testModule1AllSoapAndRestV1"],"description":"","operationId":"testModule1AllSoapAndRestV1GetPreconfiguredItemGet","responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/test-module1-v1-entity-item"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V2/testmodule1/{id}":{"get":{"tags":["testModule1AllSoapAndRestV2"],"description":"Get item.","operationId":"testModule1AllSoapAndRestV2ItemGet","parameters":[{"name":"id","in":"path","type":"integer","required":true}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/test-module1-v2-entity-item"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}},"put":{"tags":["testModule1AllSoapAndRestV2"],"description":"Update item.","operationId":"testModule1AllSoapAndRestV2UpdatePut","parameters":[{"name":"id","in":"path","type":"string","required":true},{"name":"$body","in":"body","schema":{"required":["entityItem"],"properties":{"entityItem":{"$ref":"#/definitions/test-module1-v2-entity-item"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/test-module1-v2-entity-item"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}},"delete":{"tags":["testModule1AllSoapAndRestV2"],"description":"Delete an item.","operationId":"testModule1AllSoapAndRestV2DeleteDelete","parameters":[{"name":"id","in":"path","type":"integer","required":true}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/test-module1-v2-entity-item"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V2/testmodule1":{"get":{"tags":["testModule1AllSoapAndRestV2"],"description":"Retrieve a list of items.","operationId":"testModule1AllSoapAndRestV2ItemsGet","parameters":[{"name":"filters[][field]","in":"query","type":"string","description":"Field"},{"name":"filters[][value]","in":"query","type":"string","description":"Value"},{"name":"filters[][conditionType]","in":"query","type":"string","description":"Condition type"},{"name":"sortOrder","in":"query","type":"string","required":false}],"responses":{"200":{"description":"200 Success.","schema":{"type":"array","items":{"$ref":"#/definitions/test-module1-v2-entity-item"}}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}},"post":{"tags":["testModule1AllSoapAndRestV2"],"description":"Create item.","operationId":"testModule1AllSoapAndRestV2CreatePost","parameters":[{"name":"$body","in":"body","schema":{"required":["name"],"properties":{"name":{"type":"string"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/test-module1-v2-entity-item"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/testModule2SubsetRest/{id}":{"get":{"tags":["testModule2SubsetRestV1"],"description":"Return a single item.","operationId":"testModule2SubsetRestV1ItemGet","parameters":[{"name":"id","in":"path","type":"integer","required":true}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/test-module2-v1-entity-item"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/testModule2SubsetRest":{"get":{"tags":["testModule2SubsetRestV1"],"description":"Return multiple items.","operationId":"testModule2SubsetRestV1ItemsGet","responses":{"200":{"description":"200 Success.","schema":{"type":"array","items":{"$ref":"#/definitions/test-module2-v1-entity-item"}}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/errortest/success":{"get":{"tags":["testModule3ErrorV1"],"description":"","operationId":"testModule3ErrorV1SuccessGet","responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/test-module3-v1-entity-parameter"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/errortest/notfound":{"get":{"tags":["testModule3ErrorV1"],"description":"","operationId":"testModule3ErrorV1ResourceNotFoundExceptionGet","responses":{"200":{"description":"200 Success.","schema":{"type":"integer","description":"Status"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/errortest/serviceexception":{"get":{"tags":["testModule3ErrorV1"],"description":"","operationId":"testModule3ErrorV1ServiceExceptionGet","responses":{"200":{"description":"200 Success.","schema":{"type":"integer","description":"Status"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/errortest/unauthorized":{"get":{"tags":["testModule3ErrorV1"],"description":"","operationId":"testModule3ErrorV1AuthorizationExceptionGet","responses":{"200":{"description":"200 Success.","schema":{"type":"integer","description":"Status"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/errortest/otherException":{"get":{"tags":["testModule3ErrorV1"],"description":"","operationId":"testModule3ErrorV1OtherExceptionGet","responses":{"200":{"description":"200 Success.","schema":{"type":"integer","description":"Status"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/errortest/returnIncompatibleDataType":{"get":{"tags":["testModule3ErrorV1"],"description":"","operationId":"testModule3ErrorV1ReturnIncompatibleDataTypeGet","responses":{"200":{"description":"200 Success.","schema":{"type":"integer","description":"Status"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/errortest/webapiException":{"get":{"tags":["testModule3ErrorV1"],"description":"","operationId":"testModule3ErrorV1WebapiExceptionGet","responses":{"200":{"description":"200 Success.","schema":{"type":"integer","description":"Status"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/errortest/inputException":{"post":{"tags":["testModule3ErrorV1"],"description":"","operationId":"testModule3ErrorV1InputExceptionPost","parameters":[{"name":"$body","in":"body","schema":{"required":["wrappedErrorParameters"],"properties":{"wrappedErrorParameters":{"type":"array","items":{"$ref":"#/definitions/test-module3-v1-entity-wrapped-error-parameter"}}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"type":"integer","description":"Status"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/testmodule4/{id}":{"get":{"tags":["testModule4DataObjectServiceV1"],"description":"","operationId":"testModule4DataObjectServiceV1GetDataGet","parameters":[{"name":"id","in":"path","type":"integer","required":true}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/test-module4-v1-entity-data-object-response"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}},"post":{"tags":["testModule4DataObjectServiceV1"],"description":"","operationId":"testModule4DataObjectServiceV1UpdateDataPost","parameters":[{"name":"id","in":"path","type":"integer","required":true},{"name":"$body","in":"body","schema":{"required":["request"],"properties":{"request":{"$ref":"#/definitions/test-module4-v1-entity-data-object-request"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/test-module4-v1-entity-data-object-response"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/testmodule4/scalar/{id}":{"get":{"tags":["testModule4DataObjectServiceV1"],"description":"Test return scalar value","operationId":"testModule4DataObjectServiceV1ScalarResponseGet","parameters":[{"name":"id","in":"path","type":"integer","required":true}],"responses":{"200":{"description":"200 Success.","schema":{"type":"integer"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/testmodule4/{id}/nested":{"post":{"tags":["testModule4DataObjectServiceV1"],"description":"","operationId":"testModule4DataObjectServiceV1NestedDataPost","parameters":[{"name":"id","in":"path","type":"integer","required":true},{"name":"$body","in":"body","schema":{"required":["request"],"properties":{"request":{"$ref":"#/definitions/test-module4-v1-entity-nested-data-object-request"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/test-module4-v1-entity-data-object-response"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/testmodule4/extensibleDataObject/{id}":{"post":{"tags":["testModule4DataObjectServiceV1"],"description":"","operationId":"testModule4DataObjectServiceV1ExtensibleDataObjectPost","parameters":[{"name":"id","in":"path","type":"integer","required":true},{"name":"$body","in":"body","schema":{"required":["request"],"properties":{"request":{"$ref":"#/definitions/test-module4-v1-entity-extensible-request-interface"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/test-module4-v1-entity-data-object-response"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/TestModule5/{entityId}":{"get":{"tags":["testModule5AllSoapAndRestV1"],"description":"Retrieve an item.","operationId":"testModule5AllSoapAndRestV1ItemGet","parameters":[{"name":"entityId","in":"path","type":"integer","required":true}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/test-module5-v1-entity-all-soap-and-rest"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}},"put":{"tags":["testModule5AllSoapAndRestV1"],"description":"Update existing item.","operationId":"testModule5AllSoapAndRestV1UpdatePut","parameters":[{"name":"entityId","in":"path","type":"string","required":true},{"name":"$body","in":"body","schema":{"required":["entityItem"],"properties":{"entityItem":{"$ref":"#/definitions/test-module5-v1-entity-all-soap-and-rest"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/test-module5-v1-entity-all-soap-and-rest"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/TestModule5":{"get":{"tags":["testModule5AllSoapAndRestV1"],"description":"Retrieve all items.","operationId":"testModule5AllSoapAndRestV1ItemsGet","responses":{"200":{"description":"200 Success.","schema":{"type":"array","items":{"$ref":"#/definitions/test-module5-v1-entity-all-soap-and-rest"}}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}},"post":{"tags":["testModule5AllSoapAndRestV1"],"description":"Create a new item.","operationId":"testModule5AllSoapAndRestV1CreatePost","parameters":[{"name":"$body","in":"body","schema":{"required":["item"],"properties":{"item":{"$ref":"#/definitions/test-module5-v1-entity-all-soap-and-rest"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/test-module5-v1-entity-all-soap-and-rest"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/TestModule5/{parentId}/nestedResource/{entityId}":{"put":{"tags":["testModule5AllSoapAndRestV1"],"description":"Update existing item.","operationId":"testModule5AllSoapAndRestV1NestedUpdatePut","parameters":[{"name":"parentId","in":"path","type":"string","required":true},{"name":"entityId","in":"path","type":"string","required":true},{"name":"$body","in":"body","schema":{"required":["entityItem"],"properties":{"entityItem":{"$ref":"#/definitions/test-module5-v1-entity-all-soap-and-rest"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/test-module5-v1-entity-all-soap-and-rest"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/TestModule5/OverrideService/{parentId}/nestedResource/{entityId}":{"put":{"tags":["testModule5OverrideServiceV1"],"description":"Update existing item.","operationId":"testModule5OverrideServiceV1ScalarUpdatePut","parameters":[{"name":"entityId","in":"path","type":"string","required":true},{"name":"parentId","in":"path","type":"string","required":true},{"name":"$body","in":"body","schema":{"required":["name","orders"],"properties":{"name":{"type":"string"},"orders":{"type":"boolean"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/test-module5-v1-entity-all-soap-and-rest"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V2/TestModule5/{id}":{"get":{"tags":["testModule5AllSoapAndRestV2"],"description":"Retrieve existing item.","operationId":"testModule5AllSoapAndRestV2ItemGet","parameters":[{"name":"id","in":"path","type":"integer","required":true}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/test-module5-v2-entity-all-soap-and-rest"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}},"put":{"tags":["testModule5AllSoapAndRestV2"],"description":"Update one item.","operationId":"testModule5AllSoapAndRestV2UpdatePut","parameters":[{"name":"id","in":"path","type":"string","required":true},{"name":"$body","in":"body","schema":{"required":["item"],"properties":{"item":{"$ref":"#/definitions/test-module5-v2-entity-all-soap-and-rest"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/test-module5-v2-entity-all-soap-and-rest"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}},"delete":{"tags":["testModule5AllSoapAndRestV2"],"description":"Delete existing item.","operationId":"testModule5AllSoapAndRestV2DeleteDelete","parameters":[{"name":"id","in":"path","type":"string","required":true}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/test-module5-v2-entity-all-soap-and-rest"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V2/TestModule5":{"get":{"tags":["testModule5AllSoapAndRestV2"],"description":"Retrieve a list of all existing items.","operationId":"testModule5AllSoapAndRestV2ItemsGet","responses":{"200":{"description":"200 Success.","schema":{"type":"array","items":{"$ref":"#/definitions/test-module5-v2-entity-all-soap-and-rest"}}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}},"post":{"tags":["testModule5AllSoapAndRestV2"],"description":"Add new item.","operationId":"testModule5AllSoapAndRestV2CreatePost","parameters":[{"name":"$body","in":"body","schema":{"required":["item"],"properties":{"item":{"$ref":"#/definitions/test-module5-v2-entity-all-soap-and-rest"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/test-module5-v2-entity-all-soap-and-rest"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/TestModuleDefaultHydrator":{"post":{"tags":["testModuleDefaultHydratorCustomerPersistenceV1"],"description":"Create customer","operationId":"testModuleDefaultHydratorCustomerPersistenceV1SavePost","parameters":[{"name":"$body","in":"body","schema":{"required":["customer"],"properties":{"customer":{"$ref":"#/definitions/customer-data-customer-interface"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/customer-data-customer-interface"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"500":{"description":"Internal Server error","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/TestModuleDefaultHydrator/{id}":{"get":{"tags":["testModuleDefaultHydratorCustomerPersistenceV1"],"description":"Retrieve customer by id","operationId":"testModuleDefaultHydratorCustomerPersistenceV1GetByIdGet","parameters":[{"name":"id","in":"path","type":"integer","required":true},{"name":"websiteId","in":"query","type":"integer","required":false}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/customer-data-customer-interface"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"500":{"description":"Internal Server error","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}},"delete":{"tags":["testModuleDefaultHydratorCustomerPersistenceV1"],"description":"Delete customer by id","operationId":"testModuleDefaultHydratorCustomerPersistenceV1DeleteDelete","parameters":[{"name":"id","in":"path","type":"integer","required":true}],"responses":{"200":{"description":"200 Success.","schema":{"type":"boolean"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"500":{"description":"Internal Server error","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}},"put":{"tags":["testModuleDefaultHydratorCustomerPersistenceV1"],"description":"Create customer","operationId":"testModuleDefaultHydratorCustomerPersistenceV1SavePut","parameters":[{"name":"id","in":"path","type":"string","required":true},{"name":"$body","in":"body","schema":{"required":["customer"],"properties":{"customer":{"$ref":"#/definitions/customer-data-customer-interface"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/customer-data-customer-interface"}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"500":{"description":"Internal Server error","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/TestModuleJoinDirectives":{"get":{"tags":["testModuleJoinDirectivesTestRepositoryV1"],"description":"Get list of quotes","operationId":"testModuleJoinDirectivesTestRepositoryV1GetListGet","parameters":[{"name":"searchCriteria[filterGroups][][filters][][field]","in":"query","type":"string","description":"Field"},{"name":"searchCriteria[filterGroups][][filters][][value]","in":"query","type":"string","description":"Value"},{"name":"searchCriteria[filterGroups][][filters][][conditionType]","in":"query","type":"string","description":"Condition type"},{"name":"searchCriteria[sortOrders][][field]","in":"query","type":"string","description":"Sorting field."},{"name":"searchCriteria[sortOrders][][direction]","in":"query","type":"string","description":"Sorting direction."},{"name":"searchCriteria[pageSize]","in":"query","type":"integer","description":"Page size."},{"name":"searchCriteria[currentPage]","in":"query","type":"integer","description":"Current page."}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/quote-data-cart-search-results-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/testmoduleMSC/overwritten":{"get":{"tags":["testModuleMSCAllSoapAndRestV1"],"description":"","operationId":"testModuleMSCAllSoapAndRestV1ItemGet","responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/test-module-ms-cdata-item-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/testmoduleMSC/testOptionalParam":{"post":{"tags":["testModuleMSCAllSoapAndRestV1"],"description":"","operationId":"testModuleMSCAllSoapAndRestV1TestOptionalParamPost","parameters":[{"name":"$body","in":"body","schema":{"properties":{"name":{"type":"string"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/test-module-ms-cdata-item-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/testmoduleMSC/{itemId}":{"get":{"tags":["testModuleMSCAllSoapAndRestV1"],"description":"","operationId":"testModuleMSCAllSoapAndRestV1ItemGet","parameters":[{"name":"itemId","in":"path","type":"integer","required":true}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/test-module-ms-cdata-item-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}},"put":{"tags":["testModuleMSCAllSoapAndRestV1"],"description":"","operationId":"testModuleMSCAllSoapAndRestV1UpdatePut","parameters":[{"name":"itemId","in":"path","type":"string","required":true},{"name":"$body","in":"body","schema":{"required":["entityItem"],"properties":{"entityItem":{"$ref":"#/definitions/test-module-ms-cdata-item-interface"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/test-module-ms-cdata-item-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/testmoduleMSC":{"get":{"tags":["testModuleMSCAllSoapAndRestV1"],"description":"","operationId":"testModuleMSCAllSoapAndRestV1ItemsGet","responses":{"200":{"description":"200 Success.","schema":{"type":"array","items":{"$ref":"#/definitions/test-module-ms-cdata-item-interface"}}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}},"post":{"tags":["testModuleMSCAllSoapAndRestV1"],"description":"","operationId":"testModuleMSCAllSoapAndRestV1CreatePost","parameters":[{"name":"$body","in":"body","schema":{"required":["name"],"properties":{"name":{"type":"string"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/test-module-ms-cdata-item-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/testmoduleMSC/itemAnyType":{"post":{"tags":["testModuleMSCAllSoapAndRestV1"],"description":"","operationId":"testModuleMSCAllSoapAndRestV1ItemAnyTypePost","parameters":[{"name":"$body","in":"body","schema":{"required":["entityItem"],"properties":{"entityItem":{"$ref":"#/definitions/test-module-ms-cdata-item-interface"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/test-module-ms-cdata-item-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/testmoduleMSC/itemPreconfigured":{"get":{"tags":["testModuleMSCAllSoapAndRestV1"],"description":"","operationId":"testModuleMSCAllSoapAndRestV1GetPreconfiguredItemGet","responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/test-module-ms-cdata-item-interface"}},"401":{"description":"401 Unauthorized","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}},"/V1/worldpay-guest-carts/{cartId}/payment-information":{"post":{"tags":["worldpayGuestPaymentInformationManagementProxyV1"],"description":"Proxy handler for guest place order","operationId":"worldpayGuestPaymentInformationManagementProxyV1SavePaymentInformationAndPlaceOrderPost","parameters":[{"name":"cartId","in":"path","type":"string","required":true},{"name":"$body","in":"body","schema":{"required":["email","paymentMethod"],"properties":{"email":{"type":"string"},"paymentMethod":{"$ref":"#/definitions/quote-data-payment-interface"},"billingAddress":{"$ref":"#/definitions/quote-data-address-interface"}},"type":"object"}}],"responses":{"200":{"description":"200 Success.","schema":{"type":"integer","description":"Order ID."}},"400":{"description":"400 Bad Request","schema":{"$ref":"#/definitions/error-response"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}}},"definitions":{"error-response":{"type":"object","properties":{"message":{"type":"string","description":"Error message"},"errors":{"$ref":"#/definitions/error-errors"},"code":{"type":"integer","description":"Error code"},"parameters":{"$ref":"#/definitions/error-parameters"},"trace":{"type":"string","description":"Stack trace"}},"required":["message"]},"error-errors":{"type":"array","description":"Errors list","items":{"$ref":"#/definitions/error-errors-item"}},"error-errors-item":{"type":"object","description":"Error details","properties":{"message":{"type":"string","description":"Error message"},"parameters":{"$ref":"#/definitions/error-parameters"}}},"error-parameters":{"type":"array","description":"Error parameters list","items":{"$ref":"#/definitions/error-parameters-item"}},"error-parameters-item":{"type":"object","description":"Error parameters item","properties":{"resources":{"type":"string","description":"ACL resource"},"fieldName":{"type":"string","description":"Missing or invalid field name"},"fieldValue":{"type":"string","description":"Incorrect field value"}}},"store-data-store-interface":{"type":"object","description":"Store interface","properties":{"id":{"type":"integer"},"code":{"type":"string"},"name":{"type":"string","description":"Store name"},"website_id":{"type":"integer"},"store_group_id":{"type":"integer"},"extension_attributes":{"$ref":"#/definitions/store-data-store-extension-interface"}},"required":["id","code","name","website_id","store_group_id"]},"store-data-store-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\Store\\Api\\Data\\StoreInterface"},"store-data-group-interface":{"type":"object","description":"Group interface","properties":{"id":{"type":"integer"},"website_id":{"type":"integer"},"root_category_id":{"type":"integer"},"default_store_id":{"type":"integer"},"name":{"type":"string"},"code":{"type":"string","description":"Group code."},"extension_attributes":{"$ref":"#/definitions/store-data-group-extension-interface"}},"required":["id","website_id","root_category_id","default_store_id","name","code"]},"store-data-group-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\Store\\Api\\Data\\GroupInterface"},"store-data-website-interface":{"type":"object","description":"Website interface","properties":{"id":{"type":"integer"},"code":{"type":"string"},"name":{"type":"string","description":"Website name"},"default_group_id":{"type":"integer"},"extension_attributes":{"$ref":"#/definitions/store-data-website-extension-interface"}},"required":["id","code","name","default_group_id"]},"store-data-website-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\Store\\Api\\Data\\WebsiteInterface"},"store-data-store-config-interface":{"type":"object","description":"StoreConfig interface","properties":{"id":{"type":"integer","description":"Store id"},"code":{"type":"string","description":"Store code"},"website_id":{"type":"integer","description":"Website id of the store"},"locale":{"type":"string","description":"Store locale"},"base_currency_code":{"type":"string","description":"Base currency code"},"default_display_currency_code":{"type":"string","description":"Default display currency code"},"timezone":{"type":"string","description":"Timezone of the store"},"weight_unit":{"type":"string","description":"The unit of weight"},"base_url":{"type":"string","description":"Base URL for the store"},"base_link_url":{"type":"string","description":"Base link URL for the store"},"base_static_url":{"type":"string","description":"Base static URL for the store"},"base_media_url":{"type":"string","description":"Base media URL for the store"},"secure_base_url":{"type":"string","description":"Secure base URL for the store"},"secure_base_link_url":{"type":"string","description":"Secure base link URL for the store"},"secure_base_static_url":{"type":"string","description":"Secure base static URL for the store"},"secure_base_media_url":{"type":"string","description":"Secure base media URL for the store"},"extension_attributes":{"$ref":"#/definitions/store-data-store-config-extension-interface"}},"required":["id","code","website_id","locale","base_currency_code","default_display_currency_code","timezone","weight_unit","base_url","base_link_url","base_static_url","base_media_url","secure_base_url","secure_base_link_url","secure_base_static_url","secure_base_media_url"]},"store-data-store-config-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\Store\\Api\\Data\\StoreConfigInterface"},"directory-data-currency-information-interface":{"type":"object","description":"Currency Information interface.","properties":{"base_currency_code":{"type":"string","description":"The base currency code for the store."},"base_currency_symbol":{"type":"string","description":"The currency symbol of the base currency for the store."},"default_display_currency_code":{"type":"string","description":"The default display currency code for the store."},"default_display_currency_symbol":{"type":"string","description":"The currency symbol of the default display currency for the store."},"available_currency_codes":{"type":"array","description":"The list of allowed currency codes for the store.","items":{"type":"string"}},"exchange_rates":{"type":"array","description":"The list of exchange rate information for the store.","items":{"$ref":"#/definitions/directory-data-exchange-rate-interface"}},"extension_attributes":{"$ref":"#/definitions/directory-data-currency-information-extension-interface"}},"required":["base_currency_code","base_currency_symbol","default_display_currency_code","default_display_currency_symbol","available_currency_codes","exchange_rates"]},"directory-data-exchange-rate-interface":{"type":"object","description":"Exchange Rate interface.","properties":{"currency_to":{"type":"string","description":"The currency code associated with the exchange rate."},"rate":{"type":"number","description":"The exchange rate for the associated currency and the store's base currency."},"extension_attributes":{"$ref":"#/definitions/directory-data-exchange-rate-extension-interface"}},"required":["currency_to","rate"]},"directory-data-exchange-rate-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\Directory\\Api\\Data\\ExchangeRateInterface"},"directory-data-currency-information-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\Directory\\Api\\Data\\CurrencyInformationInterface"},"directory-data-country-information-interface":{"type":"object","description":"Country Information interface.","properties":{"id":{"type":"string","description":"The country id for the store."},"two_letter_abbreviation":{"type":"string","description":"The country 2 letter abbreviation for the store."},"three_letter_abbreviation":{"type":"string","description":"The country 3 letter abbreviation for the store."},"full_name_locale":{"type":"string","description":"The country full name (in store locale) for the store."},"full_name_english":{"type":"string","description":"The country full name (in English) for the store."},"available_regions":{"type":"array","description":"The available regions for the store.","items":{"$ref":"#/definitions/directory-data-region-information-interface"}},"extension_attributes":{"$ref":"#/definitions/directory-data-country-information-extension-interface"}},"required":["id","two_letter_abbreviation","three_letter_abbreviation","full_name_locale","full_name_english"]},"directory-data-region-information-interface":{"type":"object","description":"Region Information interface.","properties":{"id":{"type":"string","description":"Region id"},"code":{"type":"string","description":"Region code"},"name":{"type":"string","description":"Region name"},"extension_attributes":{"$ref":"#/definitions/directory-data-region-information-extension-interface"}},"required":["id","code","name"]},"directory-data-region-information-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\Directory\\Api\\Data\\RegionInformationInterface"},"directory-data-country-information-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\Directory\\Api\\Data\\CountryInformationInterface"},"eav-data-attribute-set-search-results-interface":{"type":"object","description":"Interface AttributeSetSearchResultsInterface","properties":{"items":{"type":"array","description":"Attribute sets list.","items":{"$ref":"#/definitions/eav-data-attribute-set-interface"}},"search_criteria":{"$ref":"#/definitions/framework-search-criteria-interface"},"total_count":{"type":"integer","description":"Total count."}},"required":["items","search_criteria","total_count"]},"eav-data-attribute-set-interface":{"type":"object","description":"Interface AttributeSetInterface","properties":{"attribute_set_id":{"type":"integer","description":"Attribute set ID"},"attribute_set_name":{"type":"string","description":"Attribute set name"},"sort_order":{"type":"integer","description":"Attribute set sort order index"},"entity_type_id":{"type":"integer","description":"Attribute set entity type id"},"extension_attributes":{"$ref":"#/definitions/eav-data-attribute-set-extension-interface"}},"required":["attribute_set_name","sort_order"]},"eav-data-attribute-set-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\Eav\\Api\\Data\\AttributeSetInterface"},"framework-search-criteria-interface":{"type":"object","description":"Search criteria interface.","properties":{"filter_groups":{"type":"array","description":"A list of filter groups.","items":{"$ref":"#/definitions/framework-search-filter-group"}},"sort_orders":{"type":"array","description":"Sort order.","items":{"$ref":"#/definitions/framework-sort-order"}},"page_size":{"type":"integer","description":"Page size."},"current_page":{"type":"integer","description":"Current page."}},"required":["filter_groups"]},"framework-search-filter-group":{"type":"object","description":"Groups two or more filters together using a logical OR","properties":{"filters":{"type":"array","description":"A list of filters in this group","items":{"$ref":"#/definitions/framework-filter"}}}},"framework-filter":{"type":"object","description":"Filter which can be used by any methods from service layer.","properties":{"field":{"type":"string","description":"Field"},"value":{"type":"string","description":"Value"},"condition_type":{"type":"string","description":"Condition type"}},"required":["field","value"]},"framework-sort-order":{"type":"object","description":"Data object for sort order.","properties":{"field":{"type":"string","description":"Sorting field."},"direction":{"type":"string","description":"Sorting direction."}},"required":["field","direction"]},"customer-data-group-interface":{"type":"object","description":"Customer group interface.","properties":{"id":{"type":"integer","description":"Id"},"code":{"type":"string","description":"Code"},"tax_class_id":{"type":"integer","description":"Tax class id"},"tax_class_name":{"type":"string","description":"Tax class name"},"extension_attributes":{"$ref":"#/definitions/customer-data-group-extension-interface"}},"required":["code","tax_class_id"]},"customer-data-group-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\Customer\\Api\\Data\\GroupInterface"},"customer-data-group-search-results-interface":{"type":"object","description":"Interface for customer groups search results.","properties":{"items":{"type":"array","description":"Customer groups list.","items":{"$ref":"#/definitions/customer-data-group-interface"}},"search_criteria":{"$ref":"#/definitions/framework-search-criteria-interface"},"total_count":{"type":"integer","description":"Total count."}},"required":["items","search_criteria","total_count"]},"customer-data-attribute-metadata-interface":{"type":"object","description":"Customer attribute metadata interface.","properties":{"frontend_input":{"type":"string","description":"HTML for input element."},"input_filter":{"type":"string","description":"Template used for input (e.g. \"date\")"},"store_label":{"type":"string","description":"Label of the store."},"validation_rules":{"type":"array","description":"Validation rules.","items":{"$ref":"#/definitions/customer-data-validation-rule-interface"}},"multiline_count":{"type":"integer","description":"Of lines of the attribute value."},"visible":{"type":"boolean","description":"Attribute is visible on frontend."},"required":{"type":"boolean","description":"Attribute is required."},"data_model":{"type":"string","description":"Data model for attribute."},"options":{"type":"array","description":"Options of the attribute (key => value pairs for select)","items":{"$ref":"#/definitions/customer-data-option-interface"}},"frontend_class":{"type":"string","description":"Class which is used to display the attribute on frontend."},"user_defined":{"type":"boolean","description":"Current attribute has been defined by a user."},"sort_order":{"type":"integer","description":"Attributes sort order."},"frontend_label":{"type":"string","description":"Label which supposed to be displayed on frontend."},"note":{"type":"string","description":"The note attribute for the element."},"system":{"type":"boolean","description":"This is a system attribute."},"backend_type":{"type":"string","description":"Backend type."},"is_used_in_grid":{"type":"boolean","description":"It is used in customer grid"},"is_visible_in_grid":{"type":"boolean","description":"It is visible in customer grid"},"is_filterable_in_grid":{"type":"boolean","description":"It is filterable in customer grid"},"is_searchable_in_grid":{"type":"boolean","description":"It is searchable in customer grid"},"attribute_code":{"type":"string","description":"Code of the attribute."}},"required":["frontend_input","input_filter","store_label","validation_rules","multiline_count","visible","required","data_model","options","frontend_class","user_defined","sort_order","frontend_label","note","system","backend_type","attribute_code"]},"customer-data-validation-rule-interface":{"type":"object","description":"Validation rule interface.","properties":{"name":{"type":"string","description":"Validation rule name"},"value":{"type":"string","description":"Validation rule value"}},"required":["name","value"]},"customer-data-option-interface":{"type":"object","description":"Option interface.","properties":{"label":{"type":"string","description":"Option label"},"value":{"type":"string","description":"Option value"},"options":{"type":"array","description":"Nested options","items":{"$ref":"#/definitions/customer-data-option-interface"}}},"required":["label"]},"customer-data-customer-interface":{"type":"object","description":"Customer interface.","properties":{"id":{"type":"integer","description":"Customer id"},"group_id":{"type":"integer","description":"Group id"},"default_billing":{"type":"string","description":"Default billing address id"},"default_shipping":{"type":"string","description":"Default shipping address id"},"confirmation":{"type":"string","description":"Confirmation"},"created_at":{"type":"string","description":"Created at time"},"updated_at":{"type":"string","description":"Updated at time"},"created_in":{"type":"string","description":"Created in area"},"dob":{"type":"string","description":"Date of birth"},"email":{"type":"string","description":"Email address"},"firstname":{"type":"string","description":"First name"},"lastname":{"type":"string","description":"Last name"},"middlename":{"type":"string","description":"Middle name"},"prefix":{"type":"string","description":"Prefix"},"suffix":{"type":"string","description":"Suffix"},"gender":{"type":"integer","description":"Gender"},"store_id":{"type":"integer","description":"Store id"},"taxvat":{"type":"string","description":"Tax Vat"},"website_id":{"type":"integer","description":"Website id"},"addresses":{"type":"array","description":"Customer addresses.","items":{"$ref":"#/definitions/customer-data-address-interface"}},"disable_auto_group_change":{"type":"integer","description":"Disable auto group change flag."},"extension_attributes":{"$ref":"#/definitions/customer-data-customer-extension-interface"},"custom_attributes":{"type":"array","description":"Custom attributes values.","items":{"$ref":"#/definitions/framework-attribute-interface"}}},"required":["email","firstname","lastname"]},"customer-data-address-interface":{"type":"object","description":"Customer address interface.","properties":{"id":{"type":"integer","description":"ID"},"customer_id":{"type":"integer","description":"Customer ID"},"region":{"$ref":"#/definitions/customer-data-region-interface"},"region_id":{"type":"integer","description":"Region ID"},"country_id":{"type":"string","description":"Country code in ISO_3166-2 format"},"street":{"type":"array","description":"Street","items":{"type":"string"}},"company":{"type":"string","description":"Company"},"telephone":{"type":"string","description":"Telephone number"},"fax":{"type":"string","description":"Fax number"},"postcode":{"type":"string","description":"Postcode"},"city":{"type":"string","description":"City name"},"firstname":{"type":"string","description":"First name"},"lastname":{"type":"string","description":"Last name"},"middlename":{"type":"string","description":"Middle name"},"prefix":{"type":"string","description":"Prefix"},"suffix":{"type":"string","description":"Suffix"},"vat_id":{"type":"string","description":"Vat id"},"default_shipping":{"type":"boolean","description":"If this address is default shipping address."},"default_billing":{"type":"boolean","description":"If this address is default billing address"},"extension_attributes":{"$ref":"#/definitions/customer-data-address-extension-interface"},"custom_attributes":{"type":"array","description":"Custom attributes values.","items":{"$ref":"#/definitions/framework-attribute-interface"}}}},"customer-data-region-interface":{"type":"object","description":"Customer address region interface.","properties":{"region_code":{"type":"string","description":"Region code"},"region":{"type":"string","description":"Region"},"region_id":{"type":"integer","description":"Region id"},"extension_attributes":{"$ref":"#/definitions/customer-data-region-extension-interface"}},"required":["region_code","region","region_id"]},"customer-data-region-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\Customer\\Api\\Data\\RegionInterface"},"customer-data-address-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\Customer\\Api\\Data\\AddressInterface"},"framework-attribute-interface":{"type":"object","description":"Interface for custom attribute value.","properties":{"attribute_code":{"type":"string","description":"Attribute code"},"value":{"type":"string","description":"Attribute value"}},"required":["attribute_code","value"]},"customer-data-customer-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\Customer\\Api\\Data\\CustomerInterface","properties":{"is_subscribed":{"type":"boolean"},"extension_attribute":{"$ref":"#/definitions/test-module-default-hydrator-data-extension-attribute-interface"}}},"test-module-default-hydrator-data-extension-attribute-interface":{"type":"object","description":"","properties":{"id":{"type":"integer","description":"ID"},"customer_id":{"type":"integer","description":"Customer ID"},"value":{"type":"string","description":"Value"}}},"customer-data-customer-search-results-interface":{"type":"object","description":"Interface for customer search results.","properties":{"items":{"type":"array","description":"Customers list.","items":{"$ref":"#/definitions/customer-data-customer-interface"}},"search_criteria":{"$ref":"#/definitions/framework-search-criteria-interface"},"total_count":{"type":"integer","description":"Total count."}},"required":["items","search_criteria","total_count"]},"customer-data-validation-results-interface":{"type":"object","description":"Validation results interface.","properties":{"valid":{"type":"boolean","description":"If the provided data is valid."},"messages":{"type":"array","description":"Error messages as array in case of validation failure, else return empty array.","items":{"type":"string"}}},"required":["valid","messages"]},"cms-data-page-interface":{"type":"object","description":"CMS page interface.","properties":{"id":{"type":"integer","description":"ID"},"identifier":{"type":"string","description":"Identifier"},"title":{"type":"string","description":"Title"},"page_layout":{"type":"string","description":"Page layout"},"meta_title":{"type":"string","description":"Meta title"},"meta_keywords":{"type":"string","description":"Meta keywords"},"meta_description":{"type":"string","description":"Meta description"},"content_heading":{"type":"string","description":"Content heading"},"content":{"type":"string","description":"Content"},"creation_time":{"type":"string","description":"Creation time"},"update_time":{"type":"string","description":"Update time"},"sort_order":{"type":"string","description":"Sort order"},"layout_update_xml":{"type":"string","description":"Layout update xml"},"custom_theme":{"type":"string","description":"Custom theme"},"custom_root_template":{"type":"string","description":"Custom root template"},"custom_layout_update_xml":{"type":"string","description":"Custom layout update xml"},"custom_theme_from":{"type":"string","description":"Custom theme from"},"custom_theme_to":{"type":"string","description":"Custom theme to"},"active":{"type":"boolean","description":"Active"}},"required":["identifier"]},"cms-data-page-search-results-interface":{"type":"object","description":"Interface for cms page search results.","properties":{"items":{"type":"array","description":"Pages list.","items":{"$ref":"#/definitions/cms-data-page-interface"}},"search_criteria":{"$ref":"#/definitions/framework-search-criteria-interface"},"total_count":{"type":"integer","description":"Total count."}},"required":["items","search_criteria","total_count"]},"cms-data-block-interface":{"type":"object","description":"CMS block interface.","properties":{"id":{"type":"integer","description":"ID"},"identifier":{"type":"string","description":"Identifier"},"title":{"type":"string","description":"Title"},"content":{"type":"string","description":"Content"},"creation_time":{"type":"string","description":"Creation time"},"update_time":{"type":"string","description":"Update time"},"active":{"type":"boolean","description":"Active"}},"required":["identifier"]},"cms-data-block-search-results-interface":{"type":"object","description":"Interface for cms block search results.","properties":{"items":{"type":"array","description":"Blocks list.","items":{"$ref":"#/definitions/cms-data-block-interface"}},"search_criteria":{"$ref":"#/definitions/framework-search-criteria-interface"},"total_count":{"type":"integer","description":"Total count."}},"required":["items","search_criteria","total_count"]},"catalog-data-product-interface":{"type":"object","description":"","properties":{"id":{"type":"integer","description":"Id"},"sku":{"type":"string","description":"Sku"},"name":{"type":"string","description":"Name"},"attribute_set_id":{"type":"integer","description":"Attribute set id"},"price":{"type":"number","description":"Price"},"status":{"type":"integer","description":"Status"},"visibility":{"type":"integer","description":"Visibility"},"type_id":{"type":"string","description":"Type id"},"created_at":{"type":"string","description":"Created date"},"updated_at":{"type":"string","description":"Updated date"},"weight":{"type":"number","description":"Weight"},"extension_attributes":{"$ref":"#/definitions/catalog-data-product-extension-interface"},"product_links":{"type":"array","description":"Product links info","items":{"$ref":"#/definitions/catalog-data-product-link-interface"}},"options":{"type":"array","description":"List of product options","items":{"$ref":"#/definitions/catalog-data-product-custom-option-interface"}},"media_gallery_entries":{"type":"array","description":"Media gallery entries","items":{"$ref":"#/definitions/catalog-data-product-attribute-media-gallery-entry-interface"}},"tier_prices":{"type":"array","description":"List of product tier prices","items":{"$ref":"#/definitions/catalog-data-product-tier-price-interface"}},"custom_attributes":{"type":"array","description":"Custom attributes values.","items":{"$ref":"#/definitions/framework-attribute-interface"}}},"required":["sku"]},"catalog-data-product-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\Catalog\\Api\\Data\\ProductInterface","properties":{"website_ids":{"type":"array","items":{"type":"integer"}},"category_links":{"type":"array","items":{"$ref":"#/definitions/catalog-data-category-link-interface"}},"stock_item":{"$ref":"#/definitions/catalog-inventory-data-stock-item-interface"},"bundle_product_options":{"type":"array","items":{"$ref":"#/definitions/bundle-data-option-interface"}},"downloadable_product_links":{"type":"array","items":{"$ref":"#/definitions/downloadable-data-link-interface"}},"downloadable_product_samples":{"type":"array","items":{"$ref":"#/definitions/downloadable-data-sample-interface"}},"giftcard_amounts":{"type":"array","items":{"$ref":"#/definitions/gift-card-data-giftcard-amount-interface"}},"configurable_product_options":{"type":"array","items":{"$ref":"#/definitions/configurable-product-data-option-interface"}},"configurable_product_links":{"type":"array","items":{"type":"integer"}}}},"catalog-data-category-link-interface":{"type":"object","description":"","properties":{"position":{"type":"integer"},"category_id":{"type":"string","description":"Category id"},"extension_attributes":{"$ref":"#/definitions/catalog-data-category-link-extension-interface"}},"required":["category_id"]},"catalog-data-category-link-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\Catalog\\Api\\Data\\CategoryLinkInterface"},"catalog-inventory-data-stock-item-interface":{"type":"object","description":"Interface StockItem","properties":{"item_id":{"type":"integer"},"product_id":{"type":"integer"},"stock_id":{"type":"integer","description":"Stock identifier"},"qty":{"type":"number"},"is_in_stock":{"type":"boolean","description":"Stock Availability"},"is_qty_decimal":{"type":"boolean"},"show_default_notification_message":{"type":"boolean"},"use_config_min_qty":{"type":"boolean"},"min_qty":{"type":"number","description":"Minimal quantity available for item status in stock"},"use_config_min_sale_qty":{"type":"integer"},"min_sale_qty":{"type":"number","description":"Minimum Qty Allowed in Shopping Cart or NULL when there is no limitation"},"use_config_max_sale_qty":{"type":"boolean"},"max_sale_qty":{"type":"number","description":"Maximum Qty Allowed in Shopping Cart data wrapper"},"use_config_backorders":{"type":"boolean"},"backorders":{"type":"integer","description":"Backorders status"},"use_config_notify_stock_qty":{"type":"boolean"},"notify_stock_qty":{"type":"number","description":"Notify for Quantity Below data wrapper"},"use_config_qty_increments":{"type":"boolean"},"qty_increments":{"type":"number","description":"Quantity Increments data wrapper"},"use_config_enable_qty_inc":{"type":"boolean"},"enable_qty_increments":{"type":"boolean","description":"Whether Quantity Increments is enabled"},"use_config_manage_stock":{"type":"boolean"},"manage_stock":{"type":"boolean","description":"Can Manage Stock"},"low_stock_date":{"type":"string"},"is_decimal_divided":{"type":"boolean"},"stock_status_changed_auto":{"type":"integer"},"extension_attributes":{"$ref":"#/definitions/catalog-inventory-data-stock-item-extension-interface"}},"required":["qty","is_in_stock","is_qty_decimal","show_default_notification_message","use_config_min_qty","min_qty","use_config_min_sale_qty","min_sale_qty","use_config_max_sale_qty","max_sale_qty","use_config_backorders","backorders","use_config_notify_stock_qty","notify_stock_qty","use_config_qty_increments","qty_increments","use_config_enable_qty_inc","enable_qty_increments","use_config_manage_stock","manage_stock","low_stock_date","is_decimal_divided","stock_status_changed_auto"]},"catalog-inventory-data-stock-item-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\CatalogInventory\\Api\\Data\\StockItemInterface"},"bundle-data-option-interface":{"type":"object","description":"Interface OptionInterface","properties":{"option_id":{"type":"integer","description":"Option id"},"title":{"type":"string","description":"Option title"},"required":{"type":"boolean","description":"Is required option"},"type":{"type":"string","description":"Input type"},"position":{"type":"integer","description":"Option position"},"sku":{"type":"string","description":"Product sku"},"product_links":{"type":"array","description":"Product links","items":{"$ref":"#/definitions/bundle-data-link-interface"}},"extension_attributes":{"$ref":"#/definitions/bundle-data-option-extension-interface"}}},"bundle-data-link-interface":{"type":"object","description":"Interface LinkInterface","properties":{"id":{"type":"string","description":"The identifier"},"sku":{"type":"string","description":"Linked product sku"},"option_id":{"type":"integer","description":"Option id"},"qty":{"type":"number","description":"Qty"},"position":{"type":"integer","description":"Position"},"is_default":{"type":"boolean","description":"Is default"},"price":{"type":"number","description":"Price"},"price_type":{"type":"integer","description":"Price type"},"can_change_quantity":{"type":"integer","description":"Whether quantity could be changed"},"extension_attributes":{"$ref":"#/definitions/bundle-data-link-extension-interface"}},"required":["is_default","price","price_type"]},"bundle-data-link-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\Bundle\\Api\\Data\\LinkInterface"},"bundle-data-option-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\Bundle\\Api\\Data\\OptionInterface"},"downloadable-data-link-interface":{"type":"object","description":"","properties":{"id":{"type":"integer","description":"Sample(or link) id"},"title":{"type":"string"},"sort_order":{"type":"integer"},"is_shareable":{"type":"integer","description":"Shareable status"},"price":{"type":"number","description":"Price"},"number_of_downloads":{"type":"integer","description":"Of downloads per user"},"link_type":{"type":"string"},"link_file":{"type":"string","description":"relative file path"},"link_file_content":{"$ref":"#/definitions/downloadable-data-file-content-interface"},"link_url":{"type":"string","description":"Link url or null when type is 'file'"},"sample_type":{"type":"string"},"sample_file":{"type":"string","description":"relative file path"},"sample_file_content":{"$ref":"#/definitions/downloadable-data-file-content-interface"},"sample_url":{"type":"string","description":"file URL"},"extension_attributes":{"$ref":"#/definitions/downloadable-data-link-extension-interface"}},"required":["sort_order","is_shareable","price","link_type","sample_type"]},"downloadable-data-file-content-interface":{"type":"object","description":"","properties":{"file_data":{"type":"string","description":"Data (base64 encoded content)"},"name":{"type":"string","description":"File name"},"extension_attributes":{"$ref":"#/definitions/downloadable-data-file-content-extension-interface"}},"required":["file_data","name"]},"downloadable-data-file-content-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\Downloadable\\Api\\Data\\File\\ContentInterface"},"downloadable-data-link-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\Downloadable\\Api\\Data\\LinkInterface"},"downloadable-data-sample-interface":{"type":"object","description":"","properties":{"id":{"type":"integer","description":"Sample(or link) id"},"title":{"type":"string","description":"Title"},"sort_order":{"type":"integer","description":"Order index for sample"},"sample_type":{"type":"string"},"sample_file":{"type":"string","description":"relative file path"},"sample_file_content":{"$ref":"#/definitions/downloadable-data-file-content-interface"},"sample_url":{"type":"string","description":"file URL"},"extension_attributes":{"$ref":"#/definitions/downloadable-data-sample-extension-interface"}},"required":["title","sort_order","sample_type"]},"downloadable-data-sample-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\Downloadable\\Api\\Data\\SampleInterface"},"gift-card-data-giftcard-amount-interface":{"type":"object","description":"Interface GiftcardAmountInterface: this interface is used to serialize and deserialize EAV attribute giftcard_amounts","properties":{"attribute_id":{"type":"integer"},"website_id":{"type":"integer"},"value":{"type":"number"},"website_value":{"type":"number"},"extension_attributes":{"$ref":"#/definitions/gift-card-data-giftcard-amount-extension-interface"}},"required":["attribute_id","website_id","value","website_value"]},"gift-card-data-giftcard-amount-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\GiftCard\\Api\\Data\\GiftcardAmountInterface"},"configurable-product-data-option-interface":{"type":"object","description":"Interface OptionInterface","properties":{"id":{"type":"integer"},"attribute_id":{"type":"string"},"label":{"type":"string"},"position":{"type":"integer"},"is_use_default":{"type":"boolean"},"values":{"type":"array","items":{"$ref":"#/definitions/configurable-product-data-option-value-interface"}},"extension_attributes":{"$ref":"#/definitions/configurable-product-data-option-extension-interface"},"product_id":{"type":"integer"}}},"configurable-product-data-option-value-interface":{"type":"object","description":"Interface OptionValueInterface","properties":{"value_index":{"type":"integer"},"extension_attributes":{"$ref":"#/definitions/configurable-product-data-option-value-extension-interface"}},"required":["value_index"]},"configurable-product-data-option-value-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\ConfigurableProduct\\Api\\Data\\OptionValueInterface"},"configurable-product-data-option-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\ConfigurableProduct\\Api\\Data\\OptionInterface"},"catalog-data-product-link-interface":{"type":"object","description":"","properties":{"sku":{"type":"string","description":"SKU"},"link_type":{"type":"string","description":"Link type"},"linked_product_sku":{"type":"string","description":"Linked product sku"},"linked_product_type":{"type":"string","description":"Linked product type (simple, virtual, etc)"},"position":{"type":"integer","description":"Linked item position"},"extension_attributes":{"$ref":"#/definitions/catalog-data-product-link-extension-interface"}},"required":["sku","link_type","linked_product_sku","linked_product_type","position"]},"catalog-data-product-link-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\Catalog\\Api\\Data\\ProductLinkInterface","properties":{"qty":{"type":"number"}}},"catalog-data-product-custom-option-interface":{"type":"object","description":"","properties":{"product_sku":{"type":"string","description":"Product SKU"},"option_id":{"type":"integer","description":"Option id"},"title":{"type":"string","description":"Option title"},"type":{"type":"string","description":"Option type"},"sort_order":{"type":"integer","description":"Sort order"},"is_require":{"type":"boolean","description":"Is require"},"price":{"type":"number","description":"Price"},"price_type":{"type":"string","description":"Price type"},"sku":{"type":"string","description":"Sku"},"file_extension":{"type":"string"},"max_characters":{"type":"integer"},"image_size_x":{"type":"integer"},"image_size_y":{"type":"integer"},"values":{"type":"array","items":{"$ref":"#/definitions/catalog-data-product-custom-option-values-interface"}},"extension_attributes":{"$ref":"#/definitions/catalog-data-product-custom-option-extension-interface"}},"required":["product_sku","title","type","sort_order","is_require"]},"catalog-data-product-custom-option-values-interface":{"type":"object","description":"","properties":{"title":{"type":"string","description":"Option title"},"sort_order":{"type":"integer","description":"Sort order"},"price":{"type":"number","description":"Price"},"price_type":{"type":"string","description":"Price type"},"sku":{"type":"string","description":"Sku"},"option_type_id":{"type":"integer","description":"Option type id"}},"required":["title","sort_order","price","price_type"]},"catalog-data-product-custom-option-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\Catalog\\Api\\Data\\ProductCustomOptionInterface"},"catalog-data-product-attribute-media-gallery-entry-interface":{"type":"object","description":"","properties":{"id":{"type":"integer","description":"Gallery entry ID"},"media_type":{"type":"string","description":"Media type"},"label":{"type":"string","description":"Gallery entry alternative text"},"position":{"type":"integer","description":"Gallery entry position (sort order)"},"disabled":{"type":"boolean","description":"If gallery entry is hidden from product page"},"types":{"type":"array","description":"Gallery entry image types (thumbnail, image, small_image etc)","items":{"type":"string"}},"file":{"type":"string","description":"File path"},"content":{"$ref":"#/definitions/framework-data-image-content-interface"},"extension_attributes":{"$ref":"#/definitions/catalog-data-product-attribute-media-gallery-entry-extension-interface"}},"required":["media_type","label","position","disabled","types"]},"framework-data-image-content-interface":{"type":"object","description":"Image Content data interface","properties":{"base64_encoded_data":{"type":"string","description":"Media data (base64 encoded content)"},"type":{"type":"string","description":"MIME type"},"name":{"type":"string","description":"Image name"}},"required":["base64_encoded_data","type","name"]},"catalog-data-product-attribute-media-gallery-entry-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\Catalog\\Api\\Data\\ProductAttributeMediaGalleryEntryInterface","properties":{"video_content":{"$ref":"#/definitions/framework-data-video-content-interface"}}},"framework-data-video-content-interface":{"type":"object","description":"Video Content data interface","properties":{"media_type":{"type":"string","description":"MIME type"},"video_provider":{"type":"string","description":"Provider"},"video_url":{"type":"string","description":"Video URL"},"video_title":{"type":"string","description":"Title"},"video_description":{"type":"string","description":"Video Description"},"video_metadata":{"type":"string","description":"Metadata"}},"required":["media_type","video_provider","video_url","video_title","video_description","video_metadata"]},"catalog-data-product-tier-price-interface":{"type":"object","description":"","properties":{"customer_group_id":{"type":"integer","description":"Customer group id"},"qty":{"type":"number","description":"Tier qty"},"value":{"type":"number","description":"Price value"},"extension_attributes":{"$ref":"#/definitions/catalog-data-product-tier-price-extension-interface"}},"required":["customer_group_id","qty","value"]},"catalog-data-product-tier-price-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\Catalog\\Api\\Data\\ProductTierPriceInterface","properties":{"percentage_value":{"type":"number"},"website_id":{"type":"integer"}}},"catalog-data-product-search-results-interface":{"type":"object","description":"","properties":{"items":{"type":"array","description":"Attributes list.","items":{"$ref":"#/definitions/catalog-data-product-interface"}},"search_criteria":{"$ref":"#/definitions/framework-search-criteria-interface"},"total_count":{"type":"integer","description":"Total count."}},"required":["items","search_criteria","total_count"]},"catalog-data-product-attribute-type-interface":{"type":"object","description":"","properties":{"value":{"type":"string","description":"Value"},"label":{"type":"string","description":"Type label"},"extension_attributes":{"$ref":"#/definitions/catalog-data-product-attribute-type-extension-interface"}},"required":["value","label"]},"catalog-data-product-attribute-type-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\Catalog\\Api\\Data\\ProductAttributeTypeInterface"},"catalog-data-product-attribute-interface":{"type":"object","description":"","properties":{"is_wysiwyg_enabled":{"type":"boolean","description":"WYSIWYG flag"},"is_html_allowed_on_front":{"type":"boolean","description":"The HTML tags are allowed on the frontend"},"used_for_sort_by":{"type":"boolean","description":"It is used for sorting in product listing"},"is_filterable":{"type":"boolean","description":"It used in layered navigation"},"is_filterable_in_search":{"type":"boolean","description":"It is used in search results layered navigation"},"is_used_in_grid":{"type":"boolean","description":"It is used in catalog product grid"},"is_visible_in_grid":{"type":"boolean","description":"It is visible in catalog product grid"},"is_filterable_in_grid":{"type":"boolean","description":"It is filterable in catalog product grid"},"position":{"type":"integer","description":"Position"},"apply_to":{"type":"array","description":"Apply to value for the element","items":{"type":"string"}},"is_searchable":{"type":"string","description":"The attribute can be used in Quick Search"},"is_visible_in_advanced_search":{"type":"string","description":"The attribute can be used in Advanced Search"},"is_comparable":{"type":"string","description":"The attribute can be compared on the frontend"},"is_used_for_promo_rules":{"type":"string","description":"The attribute can be used for promo rules"},"is_visible_on_front":{"type":"string","description":"The attribute is visible on the frontend"},"used_in_product_listing":{"type":"string","description":"The attribute can be used in product listing"},"is_visible":{"type":"boolean","description":"Attribute is visible on frontend."},"scope":{"type":"string","description":"Attribute scope"},"extension_attributes":{"$ref":"#/definitions/catalog-data-eav-attribute-extension-interface"},"attribute_id":{"type":"integer","description":"Id of the attribute."},"attribute_code":{"type":"string","description":"Code of the attribute."},"frontend_input":{"type":"string","description":"HTML for input element."},"entity_type_id":{"type":"string","description":"Entity type id"},"is_required":{"type":"boolean","description":"Attribute is required."},"options":{"type":"array","description":"Options of the attribute (key => value pairs for select)","items":{"$ref":"#/definitions/eav-data-attribute-option-interface"}},"is_user_defined":{"type":"boolean","description":"Current attribute has been defined by a user."},"default_frontend_label":{"type":"string","description":"Frontend label for default store"},"frontend_labels":{"type":"array","description":"Frontend label for each store","items":{"$ref":"#/definitions/eav-data-attribute-frontend-label-interface"}},"note":{"type":"string","description":"The note attribute for the element."},"backend_type":{"type":"string","description":"Backend type."},"backend_model":{"type":"string","description":"Backend model"},"source_model":{"type":"string","description":"Source model"},"default_value":{"type":"string","description":"Default value for the element."},"is_unique":{"type":"string","description":"This is a unique attribute"},"frontend_class":{"type":"string","description":"Frontend class of attribute"},"validation_rules":{"type":"array","description":"Validation rules.","items":{"$ref":"#/definitions/eav-data-attribute-validation-rule-interface"}},"custom_attributes":{"type":"array","description":"Custom attributes values.","items":{"$ref":"#/definitions/framework-attribute-interface"}}},"required":["attribute_code","frontend_input","entity_type_id","is_required","frontend_labels"]},"catalog-data-eav-attribute-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\Catalog\\Api\\Data\\EavAttributeInterface"},"eav-data-attribute-option-interface":{"type":"object","description":"Created from:","properties":{"label":{"type":"string","description":"Option label"},"value":{"type":"string","description":"Option value"},"sort_order":{"type":"integer","description":"Option order"},"is_default":{"type":"boolean","description":"Default"},"store_labels":{"type":"array","description":"Option label for store scopes","items":{"$ref":"#/definitions/eav-data-attribute-option-label-interface"}}},"required":["label","value"]},"eav-data-attribute-option-label-interface":{"type":"object","description":"Interface AttributeOptionLabelInterface","properties":{"store_id":{"type":"integer","description":"Store id"},"label":{"type":"string","description":"Option label"}}},"eav-data-attribute-frontend-label-interface":{"type":"object","description":"Interface AttributeFrontendLabelInterface","properties":{"store_id":{"type":"integer","description":"Store id"},"label":{"type":"string","description":"Option label"}}},"eav-data-attribute-validation-rule-interface":{"type":"object","description":"Interface AttributeValidationRuleInterface","properties":{"key":{"type":"string","description":"Object key"},"value":{"type":"string","description":"Object value"}},"required":["key","value"]},"catalog-data-product-attribute-search-results-interface":{"type":"object","description":"","properties":{"items":{"type":"array","description":"Attributes list.","items":{"$ref":"#/definitions/catalog-data-product-attribute-interface"}},"search_criteria":{"$ref":"#/definitions/framework-search-criteria-interface"},"total_count":{"type":"integer","description":"Total count."}},"required":["items","search_criteria","total_count"]},"catalog-data-category-attribute-interface":{"type":"object","description":"","properties":{"is_wysiwyg_enabled":{"type":"boolean","description":"WYSIWYG flag"},"is_html_allowed_on_front":{"type":"boolean","description":"The HTML tags are allowed on the frontend"},"used_for_sort_by":{"type":"boolean","description":"It is used for sorting in product listing"},"is_filterable":{"type":"boolean","description":"It used in layered navigation"},"is_filterable_in_search":{"type":"boolean","description":"It is used in search results layered navigation"},"is_used_in_grid":{"type":"boolean","description":"It is used in catalog product grid"},"is_visible_in_grid":{"type":"boolean","description":"It is visible in catalog product grid"},"is_filterable_in_grid":{"type":"boolean","description":"It is filterable in catalog product grid"},"position":{"type":"integer","description":"Position"},"apply_to":{"type":"array","description":"Apply to value for the element","items":{"type":"string"}},"is_searchable":{"type":"string","description":"The attribute can be used in Quick Search"},"is_visible_in_advanced_search":{"type":"string","description":"The attribute can be used in Advanced Search"},"is_comparable":{"type":"string","description":"The attribute can be compared on the frontend"},"is_used_for_promo_rules":{"type":"string","description":"The attribute can be used for promo rules"},"is_visible_on_front":{"type":"string","description":"The attribute is visible on the frontend"},"used_in_product_listing":{"type":"string","description":"The attribute can be used in product listing"},"is_visible":{"type":"boolean","description":"Attribute is visible on frontend."},"scope":{"type":"string","description":"Attribute scope"},"extension_attributes":{"$ref":"#/definitions/catalog-data-eav-attribute-extension-interface"},"attribute_id":{"type":"integer","description":"Id of the attribute."},"attribute_code":{"type":"string","description":"Code of the attribute."},"frontend_input":{"type":"string","description":"HTML for input element."},"entity_type_id":{"type":"string","description":"Entity type id"},"is_required":{"type":"boolean","description":"Attribute is required."},"options":{"type":"array","description":"Options of the attribute (key => value pairs for select)","items":{"$ref":"#/definitions/eav-data-attribute-option-interface"}},"is_user_defined":{"type":"boolean","description":"Current attribute has been defined by a user."},"default_frontend_label":{"type":"string","description":"Frontend label for default store"},"frontend_labels":{"type":"array","description":"Frontend label for each store","items":{"$ref":"#/definitions/eav-data-attribute-frontend-label-interface"}},"note":{"type":"string","description":"The note attribute for the element."},"backend_type":{"type":"string","description":"Backend type."},"backend_model":{"type":"string","description":"Backend model"},"source_model":{"type":"string","description":"Source model"},"default_value":{"type":"string","description":"Default value for the element."},"is_unique":{"type":"string","description":"This is a unique attribute"},"frontend_class":{"type":"string","description":"Frontend class of attribute"},"validation_rules":{"type":"array","description":"Validation rules.","items":{"$ref":"#/definitions/eav-data-attribute-validation-rule-interface"}},"custom_attributes":{"type":"array","description":"Custom attributes values.","items":{"$ref":"#/definitions/framework-attribute-interface"}}},"required":["attribute_code","frontend_input","entity_type_id","is_required","frontend_labels"]},"catalog-data-category-attribute-search-results-interface":{"type":"object","description":"","properties":{"items":{"type":"array","description":"Attributes list.","items":{"$ref":"#/definitions/catalog-data-category-attribute-interface"}},"search_criteria":{"$ref":"#/definitions/framework-search-criteria-interface"},"total_count":{"type":"integer","description":"Total count."}},"required":["items","search_criteria","total_count"]},"catalog-data-product-type-interface":{"type":"object","description":"Product type details","properties":{"name":{"type":"string","description":"Product type code"},"label":{"type":"string","description":"Product type label"},"extension_attributes":{"$ref":"#/definitions/catalog-data-product-type-extension-interface"}},"required":["name","label"]},"catalog-data-product-type-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\Catalog\\Api\\Data\\ProductTypeInterface"},"eav-data-attribute-group-search-results-interface":{"type":"object","description":"Interface AttributeGroupSearchResultsInterface","properties":{"items":{"type":"array","description":"Attribute sets list.","items":{"$ref":"#/definitions/eav-data-attribute-group-interface"}},"search_criteria":{"$ref":"#/definitions/framework-search-criteria-interface"},"total_count":{"type":"integer","description":"Total count."}},"required":["items","search_criteria","total_count"]},"eav-data-attribute-group-interface":{"type":"object","description":"Interface AttributeGroupInterface","properties":{"attribute_group_id":{"type":"string","description":"Id"},"attribute_group_name":{"type":"string","description":"Name"},"attribute_set_id":{"type":"integer","description":"Attribute set id"},"extension_attributes":{"$ref":"#/definitions/eav-data-attribute-group-extension-interface"}}},"eav-data-attribute-group-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\Eav\\Api\\Data\\AttributeGroupInterface","properties":{"attribute_group_code":{"type":"string"},"sort_order":{"type":"string"}}},"catalog-data-tier-price-interface":{"type":"object","description":"Tier price interface.","properties":{"price":{"type":"number","description":"Tier price."},"price_type":{"type":"string","description":"Tier price type."},"website_id":{"type":"integer","description":"Website id."},"sku":{"type":"string","description":"SKU."},"customer_group":{"type":"string","description":"Customer group."},"quantity":{"type":"number","description":"Quantity."},"extension_attributes":{"$ref":"#/definitions/catalog-data-tier-price-extension-interface"}},"required":["price","price_type","website_id","sku","customer_group","quantity"]},"catalog-data-tier-price-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\Catalog\\Api\\Data\\TierPriceInterface"},"catalog-data-price-update-result-interface":{"type":"object","description":"Interface returned in case of incorrect price passed to efficient price API.","properties":{"message":{"type":"string","description":"Error message, that contains description of error occurred during price update."},"parameters":{"type":"array","description":"Parameters, that could be displayed in error message placeholders.","items":{"type":"string"}},"extension_attributes":{"$ref":"#/definitions/catalog-data-price-update-result-extension-interface"}},"required":["message","parameters"]},"catalog-data-price-update-result-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\Catalog\\Api\\Data\\PriceUpdateResultInterface"},"catalog-data-base-price-interface":{"type":"object","description":"Price interface.","properties":{"price":{"type":"number","description":"Price."},"store_id":{"type":"integer","description":"Store id."},"sku":{"type":"string","description":"SKU."},"extension_attributes":{"$ref":"#/definitions/catalog-data-base-price-extension-interface"}},"required":["price","store_id","sku"]},"catalog-data-base-price-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\Catalog\\Api\\Data\\BasePriceInterface"},"catalog-data-cost-interface":{"type":"object","description":"Cost interface.","properties":{"cost":{"type":"number","description":"Cost value."},"store_id":{"type":"integer","description":"Store id."},"sku":{"type":"string","description":"SKU."},"extension_attributes":{"$ref":"#/definitions/catalog-data-cost-extension-interface"}},"required":["cost","store_id","sku"]},"catalog-data-cost-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\Catalog\\Api\\Data\\CostInterface"},"catalog-data-special-price-interface":{"type":"object","description":"Product Special Price Interface is used to encapsulate data that can be processed by efficient price API.","properties":{"price":{"type":"number","description":"Product special price value."},"store_id":{"type":"integer","description":"ID of store, that contains special price value."},"sku":{"type":"string","description":"SKU of product, that contains special price value."},"price_from":{"type":"string","description":"Start date for special price in Y-m-d H:i:s format."},"price_to":{"type":"string","description":"End date for special price in Y-m-d H:i:s format."},"extension_attributes":{"$ref":"#/definitions/catalog-data-special-price-extension-interface"}},"required":["price","store_id","sku","price_from","price_to"]},"catalog-data-special-price-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\Catalog\\Api\\Data\\SpecialPriceInterface"},"catalog-data-category-interface":{"type":"object","description":"","properties":{"id":{"type":"integer"},"parent_id":{"type":"integer","description":"Parent category ID"},"name":{"type":"string","description":"Category name"},"is_active":{"type":"boolean","description":"Whether category is active"},"position":{"type":"integer","description":"Category position"},"level":{"type":"integer","description":"Category level"},"children":{"type":"string"},"created_at":{"type":"string"},"updated_at":{"type":"string"},"path":{"type":"string"},"available_sort_by":{"type":"array","items":{"type":"string"}},"include_in_menu":{"type":"boolean"},"extension_attributes":{"$ref":"#/definitions/catalog-data-category-extension-interface"},"custom_attributes":{"type":"array","description":"Custom attributes values.","items":{"$ref":"#/definitions/framework-attribute-interface"}}},"required":["name"]},"catalog-data-category-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\Catalog\\Api\\Data\\CategoryInterface"},"catalog-data-category-tree-interface":{"type":"object","description":"","properties":{"id":{"type":"integer"},"parent_id":{"type":"integer","description":"Parent category ID"},"name":{"type":"string","description":"Category name"},"is_active":{"type":"boolean","description":"Whether category is active"},"position":{"type":"integer","description":"Category position"},"level":{"type":"integer","description":"Category level"},"product_count":{"type":"integer","description":"Product count"},"children_data":{"type":"array","items":{"$ref":"#/definitions/catalog-data-category-tree-interface"}}},"required":["parent_id","name","is_active","position","level","product_count","children_data"]},"catalog-data-category-search-results-interface":{"type":"object","description":"","properties":{"items":{"type":"array","description":"Categories","items":{"$ref":"#/definitions/catalog-data-category-interface"}},"search_criteria":{"$ref":"#/definitions/framework-search-criteria-interface"},"total_count":{"type":"integer","description":"Total count."}},"required":["items","search_criteria","total_count"]},"catalog-data-product-custom-option-type-interface":{"type":"object","description":"","properties":{"label":{"type":"string","description":"Option type label"},"code":{"type":"string","description":"Option type code"},"group":{"type":"string","description":"Option type group"},"extension_attributes":{"$ref":"#/definitions/catalog-data-product-custom-option-type-extension-interface"}},"required":["label","code","group"]},"catalog-data-product-custom-option-type-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\Catalog\\Api\\Data\\ProductCustomOptionTypeInterface"},"catalog-data-product-link-type-interface":{"type":"object","description":"","properties":{"code":{"type":"integer","description":"Link type code"},"name":{"type":"string","description":"Link type name"},"extension_attributes":{"$ref":"#/definitions/catalog-data-product-link-type-extension-interface"}},"required":["code","name"]},"catalog-data-product-link-type-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\Catalog\\Api\\Data\\ProductLinkTypeInterface"},"catalog-data-product-link-attribute-interface":{"type":"object","description":"","properties":{"code":{"type":"string","description":"Attribute code"},"type":{"type":"string","description":"Attribute type"},"extension_attributes":{"$ref":"#/definitions/catalog-data-product-link-attribute-extension-interface"}},"required":["code","type"]},"catalog-data-product-link-attribute-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\Catalog\\Api\\Data\\ProductLinkAttributeInterface"},"catalog-data-category-product-link-interface":{"type":"object","description":"","properties":{"sku":{"type":"string"},"position":{"type":"integer"},"category_id":{"type":"string","description":"Category id"},"extension_attributes":{"$ref":"#/definitions/catalog-data-category-product-link-extension-interface"}},"required":["category_id"]},"catalog-data-category-product-link-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\Catalog\\Api\\Data\\CategoryProductLinkInterface"},"catalog-data-product-website-link-interface":{"type":"object","description":"","properties":{"sku":{"type":"string"},"website_id":{"type":"integer","description":"Website ids"}},"required":["sku","website_id"]},"catalog-data-product-render-search-results-interface":{"type":"object","description":"Dto that holds render information about products","properties":{"items":{"type":"array","description":"List of products rendered information","items":{"$ref":"#/definitions/catalog-data-product-render-interface"}}},"required":["items"]},"catalog-data-product-render-interface":{"type":"object","description":"Represents Data Object which holds enough information to render product This information is put into part as Add To Cart or Add to Compare Data or Price Data","properties":{"add_to_cart_button":{"$ref":"#/definitions/catalog-data-product-render-button-interface"},"add_to_compare_button":{"$ref":"#/definitions/catalog-data-product-render-button-interface"},"price_info":{"$ref":"#/definitions/catalog-data-product-render-price-info-interface"},"images":{"type":"array","description":"Enough information, that needed to render image on front","items":{"$ref":"#/definitions/catalog-data-product-render-image-interface"}},"url":{"type":"string","description":"Product url"},"id":{"type":"integer","description":"Product identifier"},"name":{"type":"string","description":"Product name"},"type":{"type":"string","description":"Product type. Such as bundle, grouped, simple, etc..."},"is_salable":{"type":"string","description":"Information about product saleability (In Stock)"},"store_id":{"type":"integer","description":"Information about current store id or requested store id"},"currency_code":{"type":"string","description":"Current or desired currency code to product"},"extension_attributes":{"$ref":"#/definitions/catalog-data-product-render-extension-interface"}},"required":["add_to_cart_button","add_to_compare_button","price_info","images","url","id","name","type","is_salable","store_id","currency_code","extension_attributes"]},"catalog-data-product-render-button-interface":{"type":"object","description":"Button interface. This interface represents all manner of product buttons: add to cart, add to compare, etc... The buttons describes by this interface should have interaction with backend","properties":{"post_data":{"type":"string","description":"Post data"},"url":{"type":"string","description":"Url, needed to add product to cart"},"required_options":{"type":"boolean","description":"Flag whether a product has options or not"},"extension_attributes":{"$ref":"#/definitions/catalog-data-product-render-button-extension-interface"}},"required":["post_data","url","required_options"]},"catalog-data-product-render-button-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\Catalog\\Api\\Data\\ProductRender\\ButtonInterface"},"catalog-data-product-render-price-info-interface":{"type":"object","description":"Price interface.","properties":{"final_price":{"type":"number","description":"Final price"},"max_price":{"type":"number","description":"Max price of a product"},"max_regular_price":{"type":"number","description":"Max regular price"},"minimal_regular_price":{"type":"number","description":"Minimal regular price"},"special_price":{"type":"number","description":"Special price"},"minimal_price":{"type":"number"},"regular_price":{"type":"number","description":"Regular price"},"formatted_prices":{"$ref":"#/definitions/catalog-data-product-render-formatted-price-info-interface"},"extension_attributes":{"$ref":"#/definitions/catalog-data-product-render-price-info-extension-interface"}},"required":["final_price","max_price","max_regular_price","minimal_regular_price","special_price","minimal_price","regular_price","formatted_prices"]},"catalog-data-product-render-formatted-price-info-interface":{"type":"object","description":"Formatted Price interface. Aggregate formatted html with price representations. E.g.: <span class=\"price\">$9.00</span> Consider currency, rounding and html","properties":{"final_price":{"type":"string","description":"Html with final price"},"max_price":{"type":"string","description":"Max price of a product"},"minimal_price":{"type":"string","description":"The minimal price of the product or variation"},"max_regular_price":{"type":"string","description":"Max regular price"},"minimal_regular_price":{"type":"string","description":"Minimal regular price"},"special_price":{"type":"string","description":"Special price"},"regular_price":{"type":"string","description":"Price - is price of product without discounts and special price with taxes and fixed product tax"},"extension_attributes":{"$ref":"#/definitions/catalog-data-product-render-formatted-price-info-extension-interface"}},"required":["final_price","max_price","minimal_price","max_regular_price","minimal_regular_price","special_price","regular_price"]},"catalog-data-product-render-formatted-price-info-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\Catalog\\Api\\Data\\ProductRender\\FormattedPriceInfoInterface"},"catalog-data-product-render-price-info-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\Catalog\\Api\\Data\\ProductRender\\PriceInfoInterface","properties":{"msrp":{"$ref":"#/definitions/msrp-data-product-render-msrp-price-info-interface"},"tax_adjustments":{"$ref":"#/definitions/catalog-data-product-render-price-info-interface"},"weee_attributes":{"type":"array","items":{"$ref":"#/definitions/weee-data-product-render-weee-adjustment-attribute-interface"}},"weee_adjustment":{"type":"string"}}},"msrp-data-product-render-msrp-price-info-interface":{"type":"object","description":"Price interface.","properties":{"msrp_price":{"type":"string"},"is_applicable":{"type":"string"},"is_shown_price_on_gesture":{"type":"string"},"msrp_message":{"type":"string"},"explanation_message":{"type":"string"},"extension_attributes":{"$ref":"#/definitions/msrp-data-product-render-msrp-price-info-extension-interface"}},"required":["msrp_price","is_applicable","is_shown_price_on_gesture","msrp_message","explanation_message"]},"msrp-data-product-render-msrp-price-info-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\Msrp\\Api\\Data\\ProductRender\\MsrpPriceInfoInterface"},"weee-data-product-render-weee-adjustment-attribute-interface":{"type":"object","description":"List of all weee attributes, their amounts, etc.., that product has","properties":{"amount":{"type":"string","description":"Weee attribute amount"},"tax_amount":{"type":"string","description":"Tax which is calculated to fixed product tax attribute"},"tax_amount_incl_tax":{"type":"string","description":"Tax amount of weee attribute"},"amount_excl_tax":{"type":"string","description":"Product amount exclude tax"},"attribute_code":{"type":"string","description":"Weee attribute code"},"extension_attributes":{"$ref":"#/definitions/weee-data-product-render-weee-adjustment-attribute-extension-interface"}},"required":["amount","tax_amount","tax_amount_incl_tax","amount_excl_tax","attribute_code","extension_attributes"]},"weee-data-product-render-weee-adjustment-attribute-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\Weee\\Api\\Data\\ProductRender\\WeeeAdjustmentAttributeInterface"},"catalog-data-product-render-image-interface":{"type":"object","description":"Product Render image interface. Represents physical characteristics of image, that can be used in product listing or product view","properties":{"url":{"type":"string","description":"Image url"},"code":{"type":"string","description":"Image code"},"height":{"type":"number","description":"Image height"},"width":{"type":"number","description":"Image width in px"},"label":{"type":"string","description":"Image label"},"resized_width":{"type":"number","description":"Resize width"},"resized_height":{"type":"number","description":"Resize height"},"extension_attributes":{"$ref":"#/definitions/catalog-data-product-render-image-extension-interface"}},"required":["url","code","height","width","label","resized_width","resized_height"]},"catalog-data-product-render-image-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\Catalog\\Api\\Data\\ProductRender\\ImageInterface"},"catalog-data-product-render-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\Catalog\\Api\\Data\\ProductRenderInterface","properties":{"wishlist_button":{"$ref":"#/definitions/catalog-data-product-render-button-interface"},"review_html":{"type":"string"}}},"catalog-inventory-data-stock-status-collection-interface":{"type":"object","description":"Stock Status collection interface","properties":{"items":{"type":"array","description":"Items","items":{"$ref":"#/definitions/catalog-inventory-data-stock-status-interface"}},"search_criteria":{"$ref":"#/definitions/catalog-inventory-stock-status-criteria-interface"},"total_count":{"type":"integer","description":"Total count."}},"required":["items","search_criteria","total_count"]},"catalog-inventory-data-stock-status-interface":{"type":"object","description":"Interface StockStatusInterface","properties":{"product_id":{"type":"integer"},"stock_id":{"type":"integer"},"qty":{"type":"integer"},"stock_status":{"type":"integer"},"stock_item":{"$ref":"#/definitions/catalog-inventory-data-stock-item-interface"},"extension_attributes":{"$ref":"#/definitions/catalog-inventory-data-stock-status-extension-interface"}},"required":["product_id","stock_id","qty","stock_status","stock_item"]},"catalog-inventory-data-stock-status-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\CatalogInventory\\Api\\Data\\StockStatusInterface"},"catalog-inventory-stock-status-criteria-interface":{"type":"object","description":"Interface StockStatusCriteriaInterface","properties":{"mapper_interface_name":{"type":"string","description":"Associated Mapper Interface name"},"criteria_list":{"type":"array","description":"Criteria objects added to current Composite Criteria","items":{"$ref":"#/definitions/framework-criteria-interface"}},"filters":{"type":"array","description":"List of filters","items":{"type":"string"}},"orders":{"type":"array","description":"Ordering criteria","items":{"type":"string"}},"limit":{"type":"array","description":"Limit","items":{"type":"string"}}},"required":["mapper_interface_name","criteria_list","filters","orders","limit"]},"framework-criteria-interface":{"type":"object","description":"Interface CriteriaInterface","properties":{"mapper_interface_name":{"type":"string","description":"Associated Mapper Interface name"},"criteria_list":{"type":"array","description":"Criteria objects added to current Composite Criteria","items":{"$ref":"#/definitions/framework-criteria-interface"}},"filters":{"type":"array","description":"List of filters","items":{"type":"string"}},"orders":{"type":"array","description":"Ordering criteria","items":{"type":"string"}},"limit":{"type":"array","description":"Limit","items":{"type":"string"}}},"required":["mapper_interface_name","criteria_list","filters","orders","limit"]},"bundle-data-option-type-interface":{"type":"object","description":"Interface OptionTypeInterface","properties":{"label":{"type":"string","description":"Type label"},"code":{"type":"string","description":"Type code"},"extension_attributes":{"$ref":"#/definitions/bundle-data-option-type-extension-interface"}},"required":["label","code"]},"bundle-data-option-type-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\Bundle\\Api\\Data\\OptionTypeInterface"},"quote-data-cart-interface":{"type":"object","description":"Interface CartInterface","properties":{"id":{"type":"integer","description":"Cart/quote ID."},"created_at":{"type":"string","description":"Cart creation date and time. Otherwise, null."},"updated_at":{"type":"string","description":"Cart last update date and time. Otherwise, null."},"converted_at":{"type":"string","description":"Cart conversion date and time. Otherwise, null."},"is_active":{"type":"boolean","description":"Active status flag value. Otherwise, null."},"is_virtual":{"type":"boolean","description":"Virtual flag value. Otherwise, null."},"items":{"type":"array","description":"Array of items. Otherwise, null.","items":{"$ref":"#/definitions/quote-data-cart-item-interface"}},"items_count":{"type":"integer","description":"Number of different items or products in the cart. Otherwise, null."},"items_qty":{"type":"number","description":"Total quantity of all cart items. Otherwise, null."},"customer":{"$ref":"#/definitions/customer-data-customer-interface"},"billing_address":{"$ref":"#/definitions/quote-data-address-interface"},"reserved_order_id":{"type":"integer","description":"Reserved order ID. Otherwise, null."},"orig_order_id":{"type":"integer","description":"Original order ID. Otherwise, null."},"currency":{"$ref":"#/definitions/quote-data-currency-interface"},"customer_is_guest":{"type":"boolean","description":"For guest customers, false for logged in customers"},"customer_note":{"type":"string","description":"Notice text"},"customer_note_notify":{"type":"boolean","description":"Customer notification flag"},"customer_tax_class_id":{"type":"integer","description":"Customer tax class ID."},"store_id":{"type":"integer","description":"Store identifier"},"extension_attributes":{"$ref":"#/definitions/quote-data-cart-extension-interface"}},"required":["id","customer","store_id"]},"quote-data-cart-item-interface":{"type":"object","description":"Interface CartItemInterface","properties":{"item_id":{"type":"integer","description":"Item ID. Otherwise, null."},"sku":{"type":"string","description":"Product SKU. Otherwise, null."},"qty":{"type":"number","description":"Product quantity."},"name":{"type":"string","description":"Product name. Otherwise, null."},"price":{"type":"number","description":"Product price. Otherwise, null."},"product_type":{"type":"string","description":"Product type. Otherwise, null."},"quote_id":{"type":"string","description":"Quote id."},"product_option":{"$ref":"#/definitions/quote-data-product-option-interface"},"extension_attributes":{"$ref":"#/definitions/quote-data-cart-item-extension-interface"}},"required":["qty","quote_id"]},"quote-data-product-option-interface":{"type":"object","description":"Product option interface","properties":{"extension_attributes":{"$ref":"#/definitions/quote-data-product-option-extension-interface"}}},"quote-data-product-option-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\Quote\\Api\\Data\\ProductOptionInterface","properties":{"custom_options":{"type":"array","items":{"$ref":"#/definitions/catalog-data-custom-option-interface"}},"bundle_options":{"type":"array","items":{"$ref":"#/definitions/bundle-data-bundle-option-interface"}},"downloadable_option":{"$ref":"#/definitions/downloadable-data-downloadable-option-interface"},"giftcard_item_option":{"$ref":"#/definitions/gift-card-data-gift-card-option-interface"},"configurable_item_options":{"type":"array","items":{"$ref":"#/definitions/configurable-product-data-configurable-item-option-value-interface"}}}},"catalog-data-custom-option-interface":{"type":"object","description":"Interface CustomOptionInterface","properties":{"option_id":{"type":"string","description":"Option id"},"option_value":{"type":"string","description":"Option value"},"extension_attributes":{"$ref":"#/definitions/catalog-data-custom-option-extension-interface"}},"required":["option_id","option_value"]},"catalog-data-custom-option-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\Catalog\\Api\\Data\\CustomOptionInterface","properties":{"file_info":{"$ref":"#/definitions/framework-data-image-content-interface"}}},"bundle-data-bundle-option-interface":{"type":"object","description":"Interface BundleOptionInterface","properties":{"option_id":{"type":"integer","description":"Bundle option id."},"option_qty":{"type":"integer","description":"Bundle option quantity."},"option_selections":{"type":"array","description":"Bundle option selection ids.","items":{"type":"integer"}},"extension_attributes":{"$ref":"#/definitions/bundle-data-bundle-option-extension-interface"}},"required":["option_id","option_qty","option_selections"]},"bundle-data-bundle-option-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\Bundle\\Api\\Data\\BundleOptionInterface"},"downloadable-data-downloadable-option-interface":{"type":"object","description":"Downloadable Option","properties":{"downloadable_links":{"type":"array","description":"The list of downloadable links","items":{"type":"integer"}}},"required":["downloadable_links"]},"gift-card-data-gift-card-option-interface":{"type":"object","description":"Interface GiftCardOptionInterface","properties":{"giftcard_amount":{"type":"string","description":"Gift card amount."},"custom_giftcard_amount":{"type":"number","description":"Gift card open amount value."},"giftcard_sender_name":{"type":"string","description":"Gift card sender name."},"giftcard_recipient_name":{"type":"string","description":"Gift card recipient name."},"giftcard_sender_email":{"type":"string","description":"Gift card sender email."},"giftcard_recipient_email":{"type":"string","description":"Gift card recipient email."},"giftcard_message":{"type":"string","description":"Giftcard message."},"extension_attributes":{"$ref":"#/definitions/gift-card-data-gift-card-option-extension-interface"}},"required":["giftcard_amount","giftcard_sender_name","giftcard_recipient_name","giftcard_sender_email","giftcard_recipient_email"]},"gift-card-data-gift-card-option-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\GiftCard\\Api\\Data\\GiftCardOptionInterface"},"configurable-product-data-configurable-item-option-value-interface":{"type":"object","description":"Interface ConfigurableItemOptionValueInterface","properties":{"option_id":{"type":"string","description":"Option SKU"},"option_value":{"type":"integer","description":"Item id"},"extension_attributes":{"$ref":"#/definitions/configurable-product-data-configurable-item-option-value-extension-interface"}},"required":["option_id"]},"configurable-product-data-configurable-item-option-value-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\ConfigurableProduct\\Api\\Data\\ConfigurableItemOptionValueInterface"},"quote-data-cart-item-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\Quote\\Api\\Data\\CartItemInterface"},"quote-data-address-interface":{"type":"object","description":"Interface AddressInterface","properties":{"id":{"type":"integer","description":"Id"},"region":{"type":"string","description":"Region name"},"region_id":{"type":"integer","description":"Region id"},"region_code":{"type":"string","description":"Region code"},"country_id":{"type":"string","description":"Country id"},"street":{"type":"array","description":"Street","items":{"type":"string"}},"company":{"type":"string","description":"Company"},"telephone":{"type":"string","description":"Telephone number"},"fax":{"type":"string","description":"Fax number"},"postcode":{"type":"string","description":"Postcode"},"city":{"type":"string","description":"City name"},"firstname":{"type":"string","description":"First name"},"lastname":{"type":"string","description":"Last name"},"middlename":{"type":"string","description":"Middle name"},"prefix":{"type":"string","description":"Prefix"},"suffix":{"type":"string","description":"Suffix"},"vat_id":{"type":"string","description":"Vat id"},"customer_id":{"type":"integer","description":"Customer id"},"email":{"type":"string","description":"Billing/shipping email"},"same_as_billing":{"type":"integer","description":"Same as billing flag"},"customer_address_id":{"type":"integer","description":"Customer address id"},"save_in_address_book":{"type":"integer","description":"Save in address book flag"},"extension_attributes":{"$ref":"#/definitions/quote-data-address-extension-interface"},"custom_attributes":{"type":"array","description":"Custom attributes values.","items":{"$ref":"#/definitions/framework-attribute-interface"}}},"required":["region","region_id","region_code","country_id","street","telephone","postcode","city","firstname","lastname","email"]},"quote-data-address-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\Quote\\Api\\Data\\AddressInterface","properties":{"gift_registry_id":{"type":"integer"}}},"quote-data-currency-interface":{"type":"object","description":"Interface CurrencyInterface","properties":{"global_currency_code":{"type":"string","description":"Global currency code"},"base_currency_code":{"type":"string","description":"Base currency code"},"store_currency_code":{"type":"string","description":"Store currency code"},"quote_currency_code":{"type":"string","description":"Quote currency code"},"store_to_base_rate":{"type":"number","description":"Store currency to base currency rate"},"store_to_quote_rate":{"type":"number","description":"Store currency to quote currency rate"},"base_to_global_rate":{"type":"number","description":"Base currency to global currency rate"},"base_to_quote_rate":{"type":"number","description":"Base currency to quote currency rate"},"extension_attributes":{"$ref":"#/definitions/quote-data-currency-extension-interface"}}},"quote-data-currency-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\Quote\\Api\\Data\\CurrencyInterface"},"quote-data-cart-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\Quote\\Api\\Data\\CartInterface","properties":{"shipping_assignments":{"type":"array","items":{"$ref":"#/definitions/quote-data-shipping-assignment-interface"}},"quote_api_test_attribute":{"$ref":"#/definitions/user-data-user-interface"}}},"quote-data-shipping-assignment-interface":{"type":"object","description":"Interface ShippingAssignmentInterface","properties":{"shipping":{"$ref":"#/definitions/quote-data-shipping-interface"},"items":{"type":"array","items":{"$ref":"#/definitions/quote-data-cart-item-interface"}},"extension_attributes":{"$ref":"#/definitions/quote-data-shipping-assignment-extension-interface"}},"required":["shipping","items"]},"quote-data-shipping-interface":{"type":"object","description":"Interface ShippingInterface","properties":{"address":{"$ref":"#/definitions/quote-data-address-interface"},"method":{"type":"string","description":"Shipping method"},"extension_attributes":{"$ref":"#/definitions/quote-data-shipping-extension-interface"}},"required":["address","method"]},"quote-data-shipping-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\Quote\\Api\\Data\\ShippingInterface"},"quote-data-shipping-assignment-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\Quote\\Api\\Data\\ShippingAssignmentInterface"},"user-data-user-interface":{"type":"object","description":"Admin user interface.","properties":{"id":{"type":"integer","description":"ID."},"first_name":{"type":"string","description":"First name."},"last_name":{"type":"string","description":"Last name."},"email":{"type":"string","description":"Email."},"user_name":{"type":"string","description":"User name."},"password":{"type":"string","description":"Password or password hash."},"created":{"type":"string","description":"User record creation date."},"modified":{"type":"string","description":"User record modification date."},"is_active":{"type":"integer","description":"If user is active."},"interface_locale":{"type":"string","description":"User interface locale."}},"required":["id","first_name","last_name","email","user_name","password","created","modified","is_active","interface_locale"]},"quote-data-cart-search-results-interface":{"type":"object","description":"Interface CartSearchResultsInterface","properties":{"items":{"type":"array","description":"Carts list.","items":{"$ref":"#/definitions/quote-data-cart-interface"}},"search_criteria":{"$ref":"#/definitions/framework-search-criteria-interface"},"total_count":{"type":"integer","description":"Total count."}},"required":["items","search_criteria","total_count"]},"quote-data-payment-interface":{"type":"object","description":"Interface PaymentInterface","properties":{"po_number":{"type":"string","description":"Purchase order number"},"method":{"type":"string","description":"Payment method code"},"additional_data":{"type":"array","description":"Payment additional details","items":{"type":"string"}},"extension_attributes":{"$ref":"#/definitions/quote-data-payment-extension-interface"}},"required":["method"]},"quote-data-payment-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\Quote\\Api\\Data\\PaymentInterface","properties":{"agreement_ids":{"type":"array","items":{"type":"string"}}}},"quote-data-shipping-method-interface":{"type":"object","description":"Interface ShippingMethodInterface","properties":{"carrier_code":{"type":"string","description":"Shipping carrier code."},"method_code":{"type":"string","description":"Shipping method code."},"carrier_title":{"type":"string","description":"Shipping carrier title. Otherwise, null."},"method_title":{"type":"string","description":"Shipping method title. Otherwise, null."},"amount":{"type":"number","description":"Shipping amount in store currency."},"base_amount":{"type":"number","description":"Shipping amount in base currency."},"available":{"type":"boolean","description":"The value of the availability flag for the current shipping method."},"extension_attributes":{"$ref":"#/definitions/quote-data-shipping-method-extension-interface"},"error_message":{"type":"string","description":"Shipping Error message."},"price_excl_tax":{"type":"number","description":"Shipping price excl tax."},"price_incl_tax":{"type":"number","description":"Shipping price incl tax."}},"required":["carrier_code","method_code","amount","base_amount","available","error_message","price_excl_tax","price_incl_tax"]},"quote-data-shipping-method-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\Quote\\Api\\Data\\ShippingMethodInterface"},"quote-data-payment-method-interface":{"type":"object","description":"Interface PaymentMethodInterface","properties":{"code":{"type":"string","description":"Payment method code"},"title":{"type":"string","description":"Payment method title"}},"required":["code","title"]},"quote-data-totals-interface":{"type":"object","description":"Interface TotalsInterface","properties":{"grand_total":{"type":"number","description":"Grand total in quote currency"},"base_grand_total":{"type":"number","description":"Grand total in base currency"},"subtotal":{"type":"number","description":"Subtotal in quote currency"},"base_subtotal":{"type":"number","description":"Subtotal in base currency"},"discount_amount":{"type":"number","description":"Discount amount in quote currency"},"base_discount_amount":{"type":"number","description":"Discount amount in base currency"},"subtotal_with_discount":{"type":"number","description":"Subtotal in quote currency with applied discount"},"base_subtotal_with_discount":{"type":"number","description":"Subtotal in base currency with applied discount"},"shipping_amount":{"type":"number","description":"Shipping amount in quote currency"},"base_shipping_amount":{"type":"number","description":"Shipping amount in base currency"},"shipping_discount_amount":{"type":"number","description":"Shipping discount amount in quote currency"},"base_shipping_discount_amount":{"type":"number","description":"Shipping discount amount in base currency"},"tax_amount":{"type":"number","description":"Tax amount in quote currency"},"base_tax_amount":{"type":"number","description":"Tax amount in base currency"},"weee_tax_applied_amount":{"type":"number","description":"Item weee tax applied amount in quote currency."},"shipping_tax_amount":{"type":"number","description":"Shipping tax amount in quote currency"},"base_shipping_tax_amount":{"type":"number","description":"Shipping tax amount in base currency"},"subtotal_incl_tax":{"type":"number","description":"Subtotal including tax in quote currency"},"base_subtotal_incl_tax":{"type":"number","description":"Subtotal including tax in base currency"},"shipping_incl_tax":{"type":"number","description":"Shipping including tax in quote currency"},"base_shipping_incl_tax":{"type":"number","description":"Shipping including tax in base currency"},"base_currency_code":{"type":"string","description":"Base currency code"},"quote_currency_code":{"type":"string","description":"Quote currency code"},"coupon_code":{"type":"string","description":"Applied coupon code"},"items_qty":{"type":"integer","description":"Items qty"},"items":{"type":"array","description":"Totals by items","items":{"$ref":"#/definitions/quote-data-totals-item-interface"}},"total_segments":{"type":"array","description":"Dynamically calculated totals","items":{"$ref":"#/definitions/quote-data-total-segment-interface"}},"extension_attributes":{"$ref":"#/definitions/quote-data-totals-extension-interface"}},"required":["weee_tax_applied_amount","total_segments"]},"quote-data-totals-item-interface":{"type":"object","description":"Interface TotalsItemInterface","properties":{"item_id":{"type":"integer","description":"Item id"},"price":{"type":"number","description":"Item price in quote currency."},"base_price":{"type":"number","description":"Item price in base currency."},"qty":{"type":"number","description":"Item quantity."},"row_total":{"type":"number","description":"Row total in quote currency."},"base_row_total":{"type":"number","description":"Row total in base currency."},"row_total_with_discount":{"type":"number","description":"Row total with discount in quote currency. Otherwise, null."},"tax_amount":{"type":"number","description":"Tax amount in quote currency. Otherwise, null."},"base_tax_amount":{"type":"number","description":"Tax amount in base currency. Otherwise, null."},"tax_percent":{"type":"number","description":"Tax percent. Otherwise, null."},"discount_amount":{"type":"number","description":"Discount amount in quote currency. Otherwise, null."},"base_discount_amount":{"type":"number","description":"Discount amount in base currency. Otherwise, null."},"discount_percent":{"type":"number","description":"Discount percent. Otherwise, null."},"price_incl_tax":{"type":"number","description":"Price including tax in quote currency. Otherwise, null."},"base_price_incl_tax":{"type":"number","description":"Price including tax in base currency. Otherwise, null."},"row_total_incl_tax":{"type":"number","description":"Row total including tax in quote currency. Otherwise, null."},"base_row_total_incl_tax":{"type":"number","description":"Row total including tax in base currency. Otherwise, null."},"options":{"type":"string","description":"Item price in quote currency."},"weee_tax_applied_amount":{"type":"number","description":"Item weee tax applied amount in quote currency."},"weee_tax_applied":{"type":"string","description":"Item weee tax applied in quote currency."},"extension_attributes":{"$ref":"#/definitions/quote-data-totals-item-extension-interface"},"name":{"type":"string","description":"Product name. Otherwise, null."}},"required":["item_id","price","base_price","qty","row_total","base_row_total","options","weee_tax_applied_amount","weee_tax_applied"]},"quote-data-totals-item-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\Quote\\Api\\Data\\TotalsItemInterface"},"quote-data-total-segment-interface":{"type":"object","description":"Interface TotalsInterface","properties":{"code":{"type":"string","description":"Code"},"title":{"type":"string","description":"Total title"},"value":{"type":"number","description":"Total value"},"area":{"type":"string","description":"Display area code."},"extension_attributes":{"$ref":"#/definitions/quote-data-total-segment-extension-interface"}},"required":["code","value"]},"quote-data-total-segment-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\Quote\\Api\\Data\\TotalSegmentInterface","properties":{"gift_cards":{"type":"string"},"tax_grandtotal_details":{"type":"array","items":{"$ref":"#/definitions/tax-data-grand-total-details-interface"}},"gw_order_id":{"type":"string"},"gw_item_ids":{"type":"array","items":{"type":"string"}},"gw_allow_gift_receipt":{"type":"string"},"gw_add_card":{"type":"string"},"gw_price":{"type":"string"},"gw_base_price":{"type":"string"},"gw_items_price":{"type":"string"},"gw_items_base_price":{"type":"string"},"gw_card_price":{"type":"string"},"gw_card_base_price":{"type":"string"},"gw_base_tax_amount":{"type":"string"},"gw_tax_amount":{"type":"string"},"gw_items_base_tax_amount":{"type":"string"},"gw_items_tax_amount":{"type":"string"},"gw_card_base_tax_amount":{"type":"string"},"gw_card_tax_amount":{"type":"string"},"gw_price_incl_tax":{"type":"string"},"gw_base_price_incl_tax":{"type":"string"},"gw_card_price_incl_tax":{"type":"string"},"gw_card_base_price_incl_tax":{"type":"string"},"gw_items_price_incl_tax":{"type":"string"},"gw_items_base_price_incl_tax":{"type":"string"}}},"tax-data-grand-total-details-interface":{"type":"object","description":"Interface GrandTotalDetailsInterface","properties":{"amount":{"type":"number","description":"Tax amount value"},"rates":{"type":"array","description":"Tax rates info","items":{"$ref":"#/definitions/tax-data-grand-total-rates-interface"}},"group_id":{"type":"integer","description":"Group identifier"}},"required":["amount","rates","group_id"]},"tax-data-grand-total-rates-interface":{"type":"object","description":"Interface GrandTotalRatesInterface","properties":{"percent":{"type":"string","description":"Tax percentage value"},"title":{"type":"string","description":"Rate title"}},"required":["percent","title"]},"quote-data-totals-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\Quote\\Api\\Data\\TotalsInterface","properties":{"coupon_label":{"type":"string"},"base_customer_balance_amount":{"type":"number"},"customer_balance_amount":{"type":"number"},"reward_points_balance":{"type":"number"},"reward_currency_amount":{"type":"number"},"base_reward_currency_amount":{"type":"number"}}},"quote-data-totals-additional-data-interface":{"type":"object","description":"Additional data for totals collection.","properties":{"extension_attributes":{"$ref":"#/definitions/quote-data-totals-additional-data-extension-interface"},"custom_attributes":{"type":"array","description":"Custom attributes values.","items":{"$ref":"#/definitions/framework-attribute-interface"}}}},"quote-data-totals-additional-data-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\Quote\\Api\\Data\\TotalsAdditionalDataInterface","properties":{"gift_messages":{"type":"array","items":{"$ref":"#/definitions/gift-message-data-message-interface"}}}},"gift-message-data-message-interface":{"type":"object","description":"Interface MessageInterface","properties":{"gift_message_id":{"type":"integer","description":"Gift message ID. Otherwise, null."},"customer_id":{"type":"integer","description":"Customer ID. Otherwise, null."},"sender":{"type":"string","description":"Sender name."},"recipient":{"type":"string","description":"Recipient name."},"message":{"type":"string","description":"Message text."},"extension_attributes":{"$ref":"#/definitions/gift-message-data-message-extension-interface"}},"required":["sender","recipient","message"]},"gift-message-data-message-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\GiftMessage\\Api\\Data\\MessageInterface","properties":{"entity_id":{"type":"string"},"entity_type":{"type":"string"},"wrapping_id":{"type":"integer"},"wrapping_allow_gift_receipt":{"type":"boolean"},"wrapping_add_printed_card":{"type":"boolean"}}},"framework-search-search-result-interface":{"type":"object","description":"Interface SearchResultInterface","properties":{"items":{"type":"array","items":{"$ref":"#/definitions/framework-search-document-interface"}},"aggregations":{"$ref":"#/definitions/framework-search-aggregation-interface"},"search_criteria":{"$ref":"#/definitions/framework-search-search-criteria-interface"},"total_count":{"type":"integer","description":"Total count."}},"required":["items","aggregations","search_criteria","total_count"]},"framework-search-document-interface":{"type":"object","description":"Interface \\Magento\\Framework\\Api\\Search\\DocumentInterface","properties":{"id":{"type":"integer"},"custom_attributes":{"type":"array","description":"Custom attributes values.","items":{"$ref":"#/definitions/framework-attribute-interface"}}},"required":["id"]},"framework-search-aggregation-interface":{"type":"object","description":"Faceted data","properties":{"buckets":{"type":"array","description":"All Document fields","items":{"$ref":"#/definitions/framework-search-bucket-interface"}},"bucket_names":{"type":"array","description":"Document field names","items":{"type":"string"}}},"required":["buckets","bucket_names"]},"framework-search-bucket-interface":{"type":"object","description":"Facet Bucket","properties":{"name":{"type":"string","description":"Field name"},"values":{"type":"array","description":"Field values","items":{"$ref":"#/definitions/framework-search-aggregation-value-interface"}}},"required":["name","values"]},"framework-search-aggregation-value-interface":{"type":"object","description":"Interface \\Magento\\Framework\\Api\\Search\\AggregationValueInterface","properties":{"value":{"type":"string","description":"Aggregation"},"metrics":{"type":"array","description":"Metrics","items":{"type":"string"}}},"required":["value","metrics"]},"framework-search-search-criteria-interface":{"type":"object","description":"Interface SearchCriteriaInterface","properties":{"request_name":{"type":"string"},"filter_groups":{"type":"array","description":"A list of filter groups.","items":{"$ref":"#/definitions/framework-search-filter-group"}},"sort_orders":{"type":"array","description":"Sort order.","items":{"$ref":"#/definitions/framework-sort-order"}},"page_size":{"type":"integer","description":"Page size."},"current_page":{"type":"integer","description":"Current page."}},"required":["request_name","filter_groups"]},"sales-data-order-interface":{"type":"object","description":"Order interface. An order is a document that a web store issues to a customer. Magento generates a sales order that lists the product items, billing and shipping addresses, and shipping and payment methods. A corresponding external document, known as a purchase order, is emailed to the customer.","properties":{"adjustment_negative":{"type":"number","description":"Negative adjustment value."},"adjustment_positive":{"type":"number","description":"Positive adjustment value."},"applied_rule_ids":{"type":"string","description":"Applied rule IDs."},"base_adjustment_negative":{"type":"number","description":"Base negative adjustment value."},"base_adjustment_positive":{"type":"number","description":"Base positive adjustment value."},"base_currency_code":{"type":"string","description":"Base currency code."},"base_discount_amount":{"type":"number","description":"Base discount amount."},"base_discount_canceled":{"type":"number","description":"Base discount canceled."},"base_discount_invoiced":{"type":"number","description":"Base discount invoiced."},"base_discount_refunded":{"type":"number","description":"Base discount refunded."},"base_grand_total":{"type":"number","description":"Base grand total."},"base_discount_tax_compensation_amount":{"type":"number","description":"Base discount tax compensation amount."},"base_discount_tax_compensation_invoiced":{"type":"number","description":"Base discount tax compensation invoiced."},"base_discount_tax_compensation_refunded":{"type":"number","description":"Base discount tax compensation refunded."},"base_shipping_amount":{"type":"number","description":"Base shipping amount."},"base_shipping_canceled":{"type":"number","description":"Base shipping canceled."},"base_shipping_discount_amount":{"type":"number","description":"Base shipping discount amount."},"base_shipping_discount_tax_compensation_amnt":{"type":"number","description":"Base shipping discount tax compensation amount."},"base_shipping_incl_tax":{"type":"number","description":"Base shipping including tax."},"base_shipping_invoiced":{"type":"number","description":"Base shipping invoiced."},"base_shipping_refunded":{"type":"number","description":"Base shipping refunded."},"base_shipping_tax_amount":{"type":"number","description":"Base shipping tax amount."},"base_shipping_tax_refunded":{"type":"number","description":"Base shipping tax refunded."},"base_subtotal":{"type":"number","description":"Base subtotal."},"base_subtotal_canceled":{"type":"number","description":"Base subtotal canceled."},"base_subtotal_incl_tax":{"type":"number","description":"Base subtotal including tax."},"base_subtotal_invoiced":{"type":"number","description":"Base subtotal invoiced."},"base_subtotal_refunded":{"type":"number","description":"Base subtotal refunded."},"base_tax_amount":{"type":"number","description":"Base tax amount."},"base_tax_canceled":{"type":"number","description":"Base tax canceled."},"base_tax_invoiced":{"type":"number","description":"Base tax invoiced."},"base_tax_refunded":{"type":"number","description":"Base tax refunded."},"base_total_canceled":{"type":"number","description":"Base total canceled."},"base_total_due":{"type":"number","description":"Base total due."},"base_total_invoiced":{"type":"number","description":"Base total invoiced."},"base_total_invoiced_cost":{"type":"number","description":"Base total invoiced cost."},"base_total_offline_refunded":{"type":"number","description":"Base total offline refunded."},"base_total_online_refunded":{"type":"number","description":"Base total online refunded."},"base_total_paid":{"type":"number","description":"Base total paid."},"base_total_qty_ordered":{"type":"number","description":"Base total quantity ordered."},"base_total_refunded":{"type":"number","description":"Base total refunded."},"base_to_global_rate":{"type":"number","description":"Base-to-global rate."},"base_to_order_rate":{"type":"number","description":"Base-to-order rate."},"billing_address_id":{"type":"integer","description":"Billing address ID."},"can_ship_partially":{"type":"integer","description":"Can-ship-partially flag value."},"can_ship_partially_item":{"type":"integer","description":"Can-ship-partially-item flag value."},"coupon_code":{"type":"string","description":"Coupon code."},"created_at":{"type":"string","description":"Created-at timestamp."},"customer_dob":{"type":"string","description":"Customer date-of-birth (DOB)."},"customer_email":{"type":"string","description":"Customer email address."},"customer_firstname":{"type":"string","description":"Customer first name."},"customer_gender":{"type":"integer","description":"Customer gender."},"customer_group_id":{"type":"integer","description":"Customer group ID."},"customer_id":{"type":"integer","description":"Customer ID."},"customer_is_guest":{"type":"integer","description":"Customer-is-guest flag value."},"customer_lastname":{"type":"string","description":"Customer last name."},"customer_middlename":{"type":"string","description":"Customer middle name."},"customer_note":{"type":"string","description":"Customer note."},"customer_note_notify":{"type":"integer","description":"Customer-note-notify flag value."},"customer_prefix":{"type":"string","description":"Customer prefix."},"customer_suffix":{"type":"string","description":"Customer suffix."},"customer_taxvat":{"type":"string","description":"Customer value-added tax (VAT)."},"discount_amount":{"type":"number","description":"Discount amount."},"discount_canceled":{"type":"number","description":"Discount canceled."},"discount_description":{"type":"string","description":"Discount description."},"discount_invoiced":{"type":"number","description":"Discount invoiced."},"discount_refunded":{"type":"number","description":"Discount refunded amount."},"edit_increment":{"type":"integer","description":"Edit increment value."},"email_sent":{"type":"integer","description":"Email-sent flag value."},"entity_id":{"type":"integer","description":"Order ID."},"ext_customer_id":{"type":"string","description":"External customer ID."},"ext_order_id":{"type":"string","description":"External order ID."},"forced_shipment_with_invoice":{"type":"integer","description":"Forced-shipment-with-invoice flag value."},"global_currency_code":{"type":"string","description":"Global currency code."},"grand_total":{"type":"number","description":"Grand total."},"discount_tax_compensation_amount":{"type":"number","description":"Discount tax compensation amount."},"discount_tax_compensation_invoiced":{"type":"number","description":"Discount tax compensation invoiced amount."},"discount_tax_compensation_refunded":{"type":"number","description":"Discount tax compensation refunded amount."},"hold_before_state":{"type":"string","description":"Hold before state."},"hold_before_status":{"type":"string","description":"Hold before status."},"increment_id":{"type":"string","description":"Increment ID."},"is_virtual":{"type":"integer","description":"Is-virtual flag value."},"order_currency_code":{"type":"string","description":"Order currency code."},"original_increment_id":{"type":"string","description":"Original increment ID."},"payment_authorization_amount":{"type":"number","description":"Payment authorization amount."},"payment_auth_expiration":{"type":"integer","description":"Payment authorization expiration date."},"protect_code":{"type":"string","description":"Protect code."},"quote_address_id":{"type":"integer","description":"Quote address ID."},"quote_id":{"type":"integer","description":"Quote ID."},"relation_child_id":{"type":"string","description":"Relation child ID."},"relation_child_real_id":{"type":"string","description":"Relation child real ID."},"relation_parent_id":{"type":"string","description":"Relation parent ID."},"relation_parent_real_id":{"type":"string","description":"Relation parent real ID."},"remote_ip":{"type":"string","description":"Remote IP address."},"shipping_amount":{"type":"number","description":"Shipping amount."},"shipping_canceled":{"type":"number","description":"Shipping canceled amount."},"shipping_description":{"type":"string","description":"Shipping description."},"shipping_discount_amount":{"type":"number","description":"Shipping discount amount."},"shipping_discount_tax_compensation_amount":{"type":"number","description":"Shipping discount tax compensation amount."},"shipping_incl_tax":{"type":"number","description":"Shipping including tax amount."},"shipping_invoiced":{"type":"number","description":"Shipping invoiced amount."},"shipping_refunded":{"type":"number","description":"Shipping refunded amount."},"shipping_tax_amount":{"type":"number","description":"Shipping tax amount."},"shipping_tax_refunded":{"type":"number","description":"Shipping tax refunded amount."},"state":{"type":"string","description":"State."},"status":{"type":"string","description":"Status."},"store_currency_code":{"type":"string","description":"Store currency code."},"store_id":{"type":"integer","description":"Store ID."},"store_name":{"type":"string","description":"Store name."},"store_to_base_rate":{"type":"number","description":"Store-to-base rate."},"store_to_order_rate":{"type":"number","description":"Store-to-order rate."},"subtotal":{"type":"number","description":"Subtotal."},"subtotal_canceled":{"type":"number","description":"Subtotal canceled amount."},"subtotal_incl_tax":{"type":"number","description":"Subtotal including tax amount."},"subtotal_invoiced":{"type":"number","description":"Subtotal invoiced amount."},"subtotal_refunded":{"type":"number","description":"Subtotal refunded amount."},"tax_amount":{"type":"number","description":"Tax amount."},"tax_canceled":{"type":"number","description":"Tax canceled amount."},"tax_invoiced":{"type":"number","description":"Tax invoiced amount."},"tax_refunded":{"type":"number","description":"Tax refunded amount."},"total_canceled":{"type":"number","description":"Total canceled."},"total_due":{"type":"number","description":"Total due."},"total_invoiced":{"type":"number","description":"Total invoiced amount."},"total_item_count":{"type":"integer","description":"Total item count."},"total_offline_refunded":{"type":"number","description":"Total offline refunded amount."},"total_online_refunded":{"type":"number","description":"Total online refunded amount."},"total_paid":{"type":"number","description":"Total paid."},"total_qty_ordered":{"type":"number","description":"Total quantity ordered."},"total_refunded":{"type":"number","description":"Total amount refunded."},"updated_at":{"type":"string","description":"Updated-at timestamp."},"weight":{"type":"number","description":"Weight."},"x_forwarded_for":{"type":"string","description":"X-Forwarded-For field value."},"items":{"type":"array","description":"Array of items.","items":{"$ref":"#/definitions/sales-data-order-item-interface"}},"billing_address":{"$ref":"#/definitions/sales-data-order-address-interface"},"payment":{"$ref":"#/definitions/sales-data-order-payment-interface"},"status_histories":{"type":"array","description":"Array of status histories.","items":{"$ref":"#/definitions/sales-data-order-status-history-interface"}},"extension_attributes":{"$ref":"#/definitions/sales-data-order-extension-interface"}},"required":["base_grand_total","customer_email","grand_total","items"]},"sales-data-order-item-interface":{"type":"object","description":"Order item interface. An order is a document that a web store issues to a customer. Magento generates a sales order that lists the product items, billing and shipping addresses, and shipping and payment methods. A corresponding external document, known as a purchase order, is emailed to the customer.","properties":{"additional_data":{"type":"string","description":"Additional data."},"amount_refunded":{"type":"number","description":"Amount refunded."},"applied_rule_ids":{"type":"string","description":"Applied rule IDs."},"base_amount_refunded":{"type":"number","description":"Base amount refunded."},"base_cost":{"type":"number","description":"Base cost."},"base_discount_amount":{"type":"number","description":"Base discount amount."},"base_discount_invoiced":{"type":"number","description":"Base discount invoiced."},"base_discount_refunded":{"type":"number","description":"Base discount refunded."},"base_discount_tax_compensation_amount":{"type":"number","description":"Base discount tax compensation amount."},"base_discount_tax_compensation_invoiced":{"type":"number","description":"Base discount tax compensation invoiced."},"base_discount_tax_compensation_refunded":{"type":"number","description":"Base discount tax compensation refunded."},"base_original_price":{"type":"number","description":"Base original price."},"base_price":{"type":"number","description":"Base price."},"base_price_incl_tax":{"type":"number","description":"Base price including tax."},"base_row_invoiced":{"type":"number","description":"Base row invoiced."},"base_row_total":{"type":"number","description":"Base row total."},"base_row_total_incl_tax":{"type":"number","description":"Base row total including tax."},"base_tax_amount":{"type":"number","description":"Base tax amount."},"base_tax_before_discount":{"type":"number","description":"Base tax before discount."},"base_tax_invoiced":{"type":"number","description":"Base tax invoiced."},"base_tax_refunded":{"type":"number","description":"Base tax refunded."},"base_weee_tax_applied_amount":{"type":"number","description":"Base WEEE tax applied amount."},"base_weee_tax_applied_row_amnt":{"type":"number","description":"Base WEEE tax applied row amount."},"base_weee_tax_disposition":{"type":"number","description":"Base WEEE tax disposition."},"base_weee_tax_row_disposition":{"type":"number","description":"Base WEEE tax row disposition."},"created_at":{"type":"string","description":"Created-at timestamp."},"description":{"type":"string","description":"Description."},"discount_amount":{"type":"number","description":"Discount amount."},"discount_invoiced":{"type":"number","description":"Discount invoiced."},"discount_percent":{"type":"number","description":"Discount percent."},"discount_refunded":{"type":"number","description":"Discount refunded."},"event_id":{"type":"integer","description":"Event ID."},"ext_order_item_id":{"type":"string","description":"External order item ID."},"free_shipping":{"type":"integer","description":"Free-shipping flag value."},"gw_base_price":{"type":"number","description":"GW base price."},"gw_base_price_invoiced":{"type":"number","description":"GW base price invoiced."},"gw_base_price_refunded":{"type":"number","description":"GW base price refunded."},"gw_base_tax_amount":{"type":"number","description":"GW base tax amount."},"gw_base_tax_amount_invoiced":{"type":"number","description":"GW base tax amount invoiced."},"gw_base_tax_amount_refunded":{"type":"number","description":"GW base tax amount refunded."},"gw_id":{"type":"integer","description":"GW ID."},"gw_price":{"type":"number","description":"GW price."},"gw_price_invoiced":{"type":"number","description":"GW price invoiced."},"gw_price_refunded":{"type":"number","description":"GW price refunded."},"gw_tax_amount":{"type":"number","description":"GW tax amount."},"gw_tax_amount_invoiced":{"type":"number","description":"GW tax amount invoiced."},"gw_tax_amount_refunded":{"type":"number","description":"GW tax amount refunded."},"discount_tax_compensation_amount":{"type":"number","description":"Discount tax compensation amount."},"discount_tax_compensation_canceled":{"type":"number","description":"Discount tax compensation canceled."},"discount_tax_compensation_invoiced":{"type":"number","description":"Discount tax compensation invoiced."},"discount_tax_compensation_refunded":{"type":"number","description":"Discount tax compensation refunded."},"is_qty_decimal":{"type":"integer","description":"Is-quantity-decimal flag value."},"is_virtual":{"type":"integer","description":"Is-virtual flag value."},"item_id":{"type":"integer","description":"Item ID."},"locked_do_invoice":{"type":"integer","description":"Locked DO invoice flag value."},"locked_do_ship":{"type":"integer","description":"Locked DO ship flag value."},"name":{"type":"string","description":"Name."},"no_discount":{"type":"integer","description":"No-discount flag value."},"order_id":{"type":"integer","description":"Order ID."},"original_price":{"type":"number","description":"Original price."},"parent_item_id":{"type":"integer","description":"Parent item ID."},"price":{"type":"number","description":"Price."},"price_incl_tax":{"type":"number","description":"Price including tax."},"product_id":{"type":"integer","description":"Product ID."},"product_type":{"type":"string","description":"Product type."},"qty_backordered":{"type":"number","description":"Quantity backordered."},"qty_canceled":{"type":"number","description":"Quantity canceled."},"qty_invoiced":{"type":"number","description":"Quantity invoiced."},"qty_ordered":{"type":"number","description":"Quantity ordered."},"qty_refunded":{"type":"number","description":"Quantity refunded."},"qty_returned":{"type":"number","description":"Quantity returned."},"qty_shipped":{"type":"number","description":"Quantity shipped."},"quote_item_id":{"type":"integer","description":"Quote item ID."},"row_invoiced":{"type":"number","description":"Row invoiced."},"row_total":{"type":"number","description":"Row total."},"row_total_incl_tax":{"type":"number","description":"Row total including tax."},"row_weight":{"type":"number","description":"Row weight."},"sku":{"type":"string","description":"SKU."},"store_id":{"type":"integer","description":"Store ID."},"tax_amount":{"type":"number","description":"Tax amount."},"tax_before_discount":{"type":"number","description":"Tax before discount."},"tax_canceled":{"type":"number","description":"Tax canceled."},"tax_invoiced":{"type":"number","description":"Tax invoiced."},"tax_percent":{"type":"number","description":"Tax percent."},"tax_refunded":{"type":"number","description":"Tax refunded."},"updated_at":{"type":"string","description":"Updated-at timestamp."},"weee_tax_applied":{"type":"string","description":"WEEE tax applied."},"weee_tax_applied_amount":{"type":"number","description":"WEEE tax applied amount."},"weee_tax_applied_row_amount":{"type":"number","description":"WEEE tax applied row amount."},"weee_tax_disposition":{"type":"number","description":"WEEE tax disposition."},"weee_tax_row_disposition":{"type":"number","description":"WEEE tax row disposition."},"weight":{"type":"number","description":"Weight."},"parent_item":{"$ref":"#/definitions/sales-data-order-item-interface"},"product_option":{"$ref":"#/definitions/catalog-data-product-option-interface"},"extension_attributes":{"$ref":"#/definitions/sales-data-order-item-extension-interface"}},"required":["sku"]},"catalog-data-product-option-interface":{"type":"object","description":"Product option interface","properties":{"extension_attributes":{"$ref":"#/definitions/catalog-data-product-option-extension-interface"}}},"catalog-data-product-option-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\Catalog\\Api\\Data\\ProductOptionInterface","properties":{"custom_options":{"type":"array","items":{"$ref":"#/definitions/catalog-data-custom-option-interface"}},"bundle_options":{"type":"array","items":{"$ref":"#/definitions/bundle-data-bundle-option-interface"}},"downloadable_option":{"$ref":"#/definitions/downloadable-data-downloadable-option-interface"},"giftcard_item_option":{"$ref":"#/definitions/gift-card-data-gift-card-option-interface"},"configurable_item_options":{"type":"array","items":{"$ref":"#/definitions/configurable-product-data-configurable-item-option-value-interface"}}}},"sales-data-order-item-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\Sales\\Api\\Data\\OrderItemInterface","properties":{"gift_message":{"$ref":"#/definitions/gift-message-data-message-interface"},"gw_id":{"type":"string"},"gw_base_price":{"type":"string"},"gw_price":{"type":"string"},"gw_base_tax_amount":{"type":"string"},"gw_tax_amount":{"type":"string"},"gw_base_price_invoiced":{"type":"string"},"gw_price_invoiced":{"type":"string"},"gw_base_tax_amount_invoiced":{"type":"string"},"gw_tax_amount_invoiced":{"type":"string"},"gw_base_price_refunded":{"type":"string"},"gw_price_refunded":{"type":"string"},"gw_base_tax_amount_refunded":{"type":"string"},"gw_tax_amount_refunded":{"type":"string"}}},"sales-data-order-address-interface":{"type":"object","description":"Order address interface. An order is a document that a web store issues to a customer. Magento generates a sales order that lists the product items, billing and shipping addresses, and shipping and payment methods. A corresponding external document, known as a purchase order, is emailed to the customer.","properties":{"address_type":{"type":"string","description":"Address type."},"city":{"type":"string","description":"City."},"company":{"type":"string","description":"Company."},"country_id":{"type":"string","description":"Country ID."},"customer_address_id":{"type":"integer","description":"Country address ID."},"customer_id":{"type":"integer","description":"Customer ID."},"email":{"type":"string","description":"Email address."},"entity_id":{"type":"integer","description":"Order address ID."},"fax":{"type":"string","description":"Fax number."},"firstname":{"type":"string","description":"First name."},"lastname":{"type":"string","description":"Last name."},"middlename":{"type":"string","description":"Middle name."},"parent_id":{"type":"integer","description":"Parent ID."},"postcode":{"type":"string","description":"Postal code."},"prefix":{"type":"string","description":"Prefix."},"region":{"type":"string","description":"Region."},"region_code":{"type":"string","description":"Region code."},"region_id":{"type":"integer","description":"Region ID."},"street":{"type":"array","description":"Array of any street values. Otherwise, null.","items":{"type":"string"}},"suffix":{"type":"string","description":"Suffix."},"telephone":{"type":"string","description":"Telephone number."},"vat_id":{"type":"string","description":"VAT ID."},"vat_is_valid":{"type":"integer","description":"VAT-is-valid flag value."},"vat_request_date":{"type":"string","description":"VAT request date."},"vat_request_id":{"type":"string","description":"VAT request ID."},"vat_request_success":{"type":"integer","description":"VAT-request-success flag value."},"extension_attributes":{"$ref":"#/definitions/sales-data-order-address-extension-interface"}},"required":["address_type","city","country_id","firstname","lastname","postcode","telephone"]},"sales-data-order-address-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\Sales\\Api\\Data\\OrderAddressInterface"},"sales-data-order-payment-interface":{"type":"object","description":"Order payment interface. An order is a document that a web store issues to a customer. Magento generates a sales order that lists the product items, billing and shipping addresses, and shipping and payment methods. A corresponding external document, known as a purchase order, is emailed to the customer.","properties":{"account_status":{"type":"string","description":"Account status."},"additional_data":{"type":"string","description":"Additional data."},"additional_information":{"type":"array","description":"Array of additional information.","items":{"type":"string"}},"address_status":{"type":"string","description":"Address status."},"amount_authorized":{"type":"number","description":"Amount authorized."},"amount_canceled":{"type":"number","description":"Amount canceled."},"amount_ordered":{"type":"number","description":"Amount ordered."},"amount_paid":{"type":"number","description":"Amount paid."},"amount_refunded":{"type":"number","description":"Amount refunded."},"anet_trans_method":{"type":"string","description":"Anet transaction method."},"base_amount_authorized":{"type":"number","description":"Base amount authorized."},"base_amount_canceled":{"type":"number","description":"Base amount canceled."},"base_amount_ordered":{"type":"number","description":"Base amount ordered."},"base_amount_paid":{"type":"number","description":"Base amount paid."},"base_amount_paid_online":{"type":"number","description":"Base amount paid online."},"base_amount_refunded":{"type":"number","description":"Base amount refunded."},"base_amount_refunded_online":{"type":"number","description":"Base amount refunded online."},"base_shipping_amount":{"type":"number","description":"Base shipping amount."},"base_shipping_captured":{"type":"number","description":"Base shipping captured amount."},"base_shipping_refunded":{"type":"number","description":"Base shipping refunded amount."},"cc_approval":{"type":"string","description":"Credit card approval."},"cc_avs_status":{"type":"string","description":"Credit card avs status."},"cc_cid_status":{"type":"string","description":"Credit card CID status."},"cc_debug_request_body":{"type":"string","description":"Credit card debug request body."},"cc_debug_response_body":{"type":"string","description":"Credit card debug response body."},"cc_debug_response_serialized":{"type":"string","description":"Credit card debug response serialized."},"cc_exp_month":{"type":"string","description":"Credit card expiration month."},"cc_exp_year":{"type":"string","description":"Credit card expiration year."},"cc_last4":{"type":"string","description":"Last four digits of the credit card."},"cc_number_enc":{"type":"string","description":"Encrypted credit card number."},"cc_owner":{"type":"string","description":"Credit card number."},"cc_secure_verify":{"type":"string","description":"Credit card secure verify."},"cc_ss_issue":{"type":"string","description":"Credit card SS issue."},"cc_ss_start_month":{"type":"string","description":"Credit card SS start month."},"cc_ss_start_year":{"type":"string","description":"Credit card SS start year."},"cc_status":{"type":"string","description":"Credit card status."},"cc_status_description":{"type":"string","description":"Credit card status description."},"cc_trans_id":{"type":"string","description":"Credit card transaction ID."},"cc_type":{"type":"string","description":"Credit card type."},"echeck_account_name":{"type":"string","description":"eCheck account name."},"echeck_account_type":{"type":"string","description":"eCheck account type."},"echeck_bank_name":{"type":"string","description":"eCheck bank name."},"echeck_routing_number":{"type":"string","description":"eCheck routing number."},"echeck_type":{"type":"string","description":"eCheck type."},"entity_id":{"type":"integer","description":"Entity ID."},"last_trans_id":{"type":"string","description":"Last transaction ID."},"method":{"type":"string","description":"Method."},"parent_id":{"type":"integer","description":"Parent ID."},"po_number":{"type":"string","description":"PO number."},"protection_eligibility":{"type":"string","description":"Protection eligibility."},"quote_payment_id":{"type":"integer","description":"Quote payment ID."},"shipping_amount":{"type":"number","description":"Shipping amount."},"shipping_captured":{"type":"number","description":"Shipping captured."},"shipping_refunded":{"type":"number","description":"Shipping refunded."},"extension_attributes":{"$ref":"#/definitions/sales-data-order-payment-extension-interface"}},"required":["account_status","additional_information","cc_last4","method"]},"sales-data-order-payment-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\Sales\\Api\\Data\\OrderPaymentInterface","properties":{"vault_payment_token":{"$ref":"#/definitions/vault-data-payment-token-interface"}}},"vault-data-payment-token-interface":{"type":"object","description":"Gateway vault payment token interface.","properties":{"entity_id":{"type":"integer","description":"Entity ID."},"customer_id":{"type":"integer","description":"Customer ID."},"public_hash":{"type":"string","description":"Public hash"},"payment_method_code":{"type":"string","description":"Payment method code"},"type":{"type":"string","description":"Type"},"created_at":{"type":"string","description":"Token creation timestamp"},"expires_at":{"type":"string","description":"Token expiration timestamp"},"gateway_token":{"type":"string","description":"Gateway token ID"},"token_details":{"type":"string","description":"Token details"},"is_active":{"type":"boolean","description":"Is active."},"is_visible":{"type":"boolean","description":"Is visible."}},"required":["public_hash","payment_method_code","type","gateway_token","token_details","is_active","is_visible"]},"sales-data-order-status-history-interface":{"type":"object","description":"Order status history interface. An order is a document that a web store issues to a customer. Magento generates a sales order that lists the product items, billing and shipping addresses, and shipping and payment methods. A corresponding external document, known as a purchase order, is emailed to the customer.","properties":{"comment":{"type":"string","description":"Comment."},"created_at":{"type":"string","description":"Created-at timestamp."},"entity_id":{"type":"integer","description":"Order status history ID."},"entity_name":{"type":"string","description":"Entity name."},"is_customer_notified":{"type":"integer","description":"Is-customer-notified flag value."},"is_visible_on_front":{"type":"integer","description":"Is-visible-on-storefront flag value."},"parent_id":{"type":"integer","description":"Parent ID."},"status":{"type":"string","description":"Status."},"extension_attributes":{"$ref":"#/definitions/sales-data-order-status-history-extension-interface"}},"required":["comment","is_customer_notified","is_visible_on_front","parent_id"]},"sales-data-order-status-history-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\Sales\\Api\\Data\\OrderStatusHistoryInterface"},"sales-data-order-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\Sales\\Api\\Data\\OrderInterface","properties":{"shipping_assignments":{"type":"array","items":{"$ref":"#/definitions/sales-data-shipping-assignment-interface"}},"base_customer_balance_amount":{"type":"number"},"customer_balance_amount":{"type":"number"},"base_customer_balance_invoiced":{"type":"number"},"customer_balance_invoiced":{"type":"number"},"base_customer_balance_refunded":{"type":"number"},"customer_balance_refunded":{"type":"number"},"base_customer_balance_total_refunded":{"type":"number"},"customer_balance_total_refunded":{"type":"number"},"gift_cards":{"type":"array","items":{"$ref":"#/definitions/gift-card-account-data-gift-card-interface"}},"base_gift_cards_amount":{"type":"number"},"gift_cards_amount":{"type":"number"},"base_gift_cards_invoiced":{"type":"number"},"gift_cards_invoiced":{"type":"number"},"base_gift_cards_refunded":{"type":"number"},"gift_cards_refunded":{"type":"number"},"applied_taxes":{"type":"array","items":{"$ref":"#/definitions/tax-data-order-tax-details-applied-tax-interface"}},"item_applied_taxes":{"type":"array","items":{"$ref":"#/definitions/tax-data-order-tax-details-item-interface"}},"converting_from_quote":{"type":"boolean"},"gift_message":{"$ref":"#/definitions/gift-message-data-message-interface"},"gw_id":{"type":"string"},"gw_allow_gift_receipt":{"type":"string"},"gw_add_card":{"type":"string"},"gw_base_price":{"type":"string"},"gw_price":{"type":"string"},"gw_items_base_price":{"type":"string"},"gw_items_price":{"type":"string"},"gw_card_base_price":{"type":"string"},"gw_card_price":{"type":"string"},"gw_base_tax_amount":{"type":"string"},"gw_tax_amount":{"type":"string"},"gw_items_base_tax_amount":{"type":"string"},"gw_items_tax_amount":{"type":"string"},"gw_card_base_tax_amount":{"type":"string"},"gw_card_tax_amount":{"type":"string"},"gw_base_price_incl_tax":{"type":"string"},"gw_price_incl_tax":{"type":"string"},"gw_items_base_price_incl_tax":{"type":"string"},"gw_items_price_incl_tax":{"type":"string"},"gw_card_base_price_incl_tax":{"type":"string"},"gw_card_price_incl_tax":{"type":"string"},"gw_base_price_invoiced":{"type":"string"},"gw_price_invoiced":{"type":"string"},"gw_items_base_price_invoiced":{"type":"string"},"gw_items_price_invoiced":{"type":"string"},"gw_card_base_price_invoiced":{"type":"string"},"gw_card_price_invoiced":{"type":"string"},"gw_base_tax_amount_invoiced":{"type":"string"},"gw_tax_amount_invoiced":{"type":"string"},"gw_items_base_tax_invoiced":{"type":"string"},"gw_items_tax_invoiced":{"type":"string"},"gw_card_base_tax_invoiced":{"type":"string"},"gw_card_tax_invoiced":{"type":"string"},"gw_base_price_refunded":{"type":"string"},"gw_price_refunded":{"type":"string"},"gw_items_base_price_refunded":{"type":"string"},"gw_items_price_refunded":{"type":"string"},"gw_card_base_price_refunded":{"type":"string"},"gw_card_price_refunded":{"type":"string"},"gw_base_tax_amount_refunded":{"type":"string"},"gw_tax_amount_refunded":{"type":"string"},"gw_items_base_tax_refunded":{"type":"string"},"gw_items_tax_refunded":{"type":"string"},"gw_card_base_tax_refunded":{"type":"string"},"gw_card_tax_refunded":{"type":"string"}}},"sales-data-shipping-assignment-interface":{"type":"object","description":"Interface ShippingAssignmentInterface","properties":{"shipping":{"$ref":"#/definitions/sales-data-shipping-interface"},"items":{"type":"array","description":"Order items of shipping assignment","items":{"$ref":"#/definitions/sales-data-order-item-interface"}},"stock_id":{"type":"integer","description":"Stock id"},"extension_attributes":{"$ref":"#/definitions/sales-data-shipping-assignment-extension-interface"}},"required":["shipping","items"]},"sales-data-shipping-interface":{"type":"object","description":"Interface ShippingInterface","properties":{"address":{"$ref":"#/definitions/sales-data-order-address-interface"},"method":{"type":"string","description":"Shipping method"},"total":{"$ref":"#/definitions/sales-data-total-interface"},"extension_attributes":{"$ref":"#/definitions/sales-data-shipping-extension-interface"}}},"sales-data-total-interface":{"type":"object","description":"Interface TotalInterface","properties":{"base_shipping_amount":{"type":"number","description":"Base shipping amount."},"base_shipping_canceled":{"type":"number","description":"Base shipping canceled."},"base_shipping_discount_amount":{"type":"number","description":"Base shipping discount amount."},"base_shipping_discount_tax_compensation_amnt":{"type":"number","description":"Base shipping discount tax compensation amount."},"base_shipping_incl_tax":{"type":"number","description":"Base shipping including tax."},"base_shipping_invoiced":{"type":"number","description":"Base shipping invoiced."},"base_shipping_refunded":{"type":"number","description":"Base shipping refunded."},"base_shipping_tax_amount":{"type":"number","description":"Base shipping tax amount."},"base_shipping_tax_refunded":{"type":"number","description":"Base shipping tax refunded."},"shipping_amount":{"type":"number","description":"Shipping amount."},"shipping_canceled":{"type":"number","description":"Shipping canceled amount."},"shipping_discount_amount":{"type":"number","description":"Shipping discount amount."},"shipping_discount_tax_compensation_amount":{"type":"number","description":"Shipping discount tax compensation amount."},"shipping_incl_tax":{"type":"number","description":"Shipping including tax amount."},"shipping_invoiced":{"type":"number","description":"Shipping invoiced amount."},"shipping_refunded":{"type":"number","description":"Shipping refunded amount."},"shipping_tax_amount":{"type":"number","description":"Shipping tax amount."},"shipping_tax_refunded":{"type":"number","description":"Shipping tax refunded amount."},"extension_attributes":{"$ref":"#/definitions/sales-data-total-extension-interface"}}},"sales-data-total-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\Sales\\Api\\Data\\TotalInterface"},"sales-data-shipping-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\Sales\\Api\\Data\\ShippingInterface"},"sales-data-shipping-assignment-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\Sales\\Api\\Data\\ShippingAssignmentInterface"},"gift-card-account-data-gift-card-interface":{"type":"object","description":"Gift Card data","properties":{"id":{"type":"integer","description":"Id"},"code":{"type":"string","description":"Code"},"amount":{"type":"number","description":"Amount"},"base_amount":{"type":"number","description":"Base Amount"}},"required":["id","code","amount","base_amount"]},"tax-data-order-tax-details-applied-tax-interface":{"type":"object","description":"Interface OrderTaxDetailsAppliedTaxInterface","properties":{"code":{"type":"string","description":"Code"},"title":{"type":"string","description":"Title"},"percent":{"type":"number","description":"Tax Percent"},"amount":{"type":"number","description":"Tax amount"},"base_amount":{"type":"number","description":"Tax amount in base currency"},"extension_attributes":{"$ref":"#/definitions/tax-data-order-tax-details-applied-tax-extension-interface"}},"required":["amount","base_amount"]},"tax-data-order-tax-details-applied-tax-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\Tax\\Api\\Data\\OrderTaxDetailsAppliedTaxInterface","properties":{"rates":{"type":"array","items":{"$ref":"#/definitions/tax-data-applied-tax-rate-interface"}}}},"tax-data-applied-tax-rate-interface":{"type":"object","description":"Applied tax rate interface.","properties":{"code":{"type":"string","description":"Code"},"title":{"type":"string","description":"Title"},"percent":{"type":"number","description":"Tax Percent"},"extension_attributes":{"$ref":"#/definitions/tax-data-applied-tax-rate-extension-interface"}}},"tax-data-applied-tax-rate-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\Tax\\Api\\Data\\AppliedTaxRateInterface"},"tax-data-order-tax-details-item-interface":{"type":"object","description":"Interface OrderTaxDetailsItemInterface","properties":{"type":{"type":"string","description":"Type (shipping, product, weee, gift wrapping, etc)"},"item_id":{"type":"integer","description":"Item id if this item is a product"},"associated_item_id":{"type":"integer","description":"Associated item id if this item is associated with another item, null otherwise"},"applied_taxes":{"type":"array","description":"Applied taxes","items":{"$ref":"#/definitions/tax-data-order-tax-details-applied-tax-interface"}},"extension_attributes":{"$ref":"#/definitions/tax-data-order-tax-details-item-extension-interface"}}},"tax-data-order-tax-details-item-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\Tax\\Api\\Data\\OrderTaxDetailsItemInterface"},"sales-data-order-search-result-interface":{"type":"object","description":"Order search result interface. An order is a document that a web store issues to a customer. Magento generates a sales order that lists the product items, billing and shipping addresses, and shipping and payment methods. A corresponding external document, known as a purchase order, is emailed to the customer.","properties":{"items":{"type":"array","description":"Array of collection items.","items":{"$ref":"#/definitions/sales-data-order-interface"}},"search_criteria":{"$ref":"#/definitions/framework-search-criteria-interface"},"total_count":{"type":"integer","description":"Total count."}},"required":["items","search_criteria","total_count"]},"sales-data-order-status-history-search-result-interface":{"type":"object","description":"Order status history search result interface. An order is a document that a web store issues to a customer. Magento generates a sales order that lists the product items, billing and shipping addresses, and shipping and payment methods. A corresponding external document, known as a purchase order, is emailed to the customer.","properties":{"items":{"type":"array","description":"Array of collection items.","items":{"$ref":"#/definitions/sales-data-order-status-history-interface"}},"search_criteria":{"$ref":"#/definitions/framework-search-criteria-interface"},"total_count":{"type":"integer","description":"Total count."}},"required":["items","search_criteria","total_count"]},"sales-data-order-item-search-result-interface":{"type":"object","description":"Order item search result interface. An order is a document that a web store issues to a customer. Magento generates a sales order that lists the product items, billing and shipping addresses, and shipping and payment methods. A corresponding external document, known as a purchase order, is emailed to the customer.","properties":{"items":{"type":"array","description":"Array of collection items.","items":{"$ref":"#/definitions/sales-data-order-item-interface"}},"search_criteria":{"$ref":"#/definitions/framework-search-criteria-interface"},"total_count":{"type":"integer","description":"Total count."}},"required":["items","search_criteria","total_count"]},"sales-data-invoice-interface":{"type":"object","description":"Invoice interface. An invoice is a record of the receipt of payment for an order.","properties":{"base_currency_code":{"type":"string","description":"Base currency code."},"base_discount_amount":{"type":"number","description":"Base discount amount."},"base_grand_total":{"type":"number","description":"Base grand total."},"base_discount_tax_compensation_amount":{"type":"number","description":"Base discount tax compensation amount."},"base_shipping_amount":{"type":"number","description":"Base shipping amount."},"base_shipping_discount_tax_compensation_amnt":{"type":"number","description":"Base shipping discount tax compensation amount."},"base_shipping_incl_tax":{"type":"number","description":"Base shipping including tax."},"base_shipping_tax_amount":{"type":"number","description":"Base shipping tax amount."},"base_subtotal":{"type":"number","description":"Base subtotal."},"base_subtotal_incl_tax":{"type":"number","description":"Base subtotal including tax."},"base_tax_amount":{"type":"number","description":"Base tax amount."},"base_total_refunded":{"type":"number","description":"Base total refunded."},"base_to_global_rate":{"type":"number","description":"Base-to-global rate."},"base_to_order_rate":{"type":"number","description":"Base-to-order rate."},"billing_address_id":{"type":"integer","description":"Billing address ID."},"can_void_flag":{"type":"integer","description":"Can void flag value."},"created_at":{"type":"string","description":"Created-at timestamp."},"discount_amount":{"type":"number","description":"Discount amount."},"discount_description":{"type":"string","description":"Discount description."},"email_sent":{"type":"integer","description":"Email-sent flag value."},"entity_id":{"type":"integer","description":"Invoice ID."},"global_currency_code":{"type":"string","description":"Global currency code."},"grand_total":{"type":"number","description":"Grand total."},"discount_tax_compensation_amount":{"type":"number","description":"Discount tax compensation amount."},"increment_id":{"type":"string","description":"Increment ID."},"is_used_for_refund":{"type":"integer","description":"Is-used-for-refund flag value."},"order_currency_code":{"type":"string","description":"Order currency code."},"order_id":{"type":"integer","description":"Order ID."},"shipping_address_id":{"type":"integer","description":"Shipping address ID."},"shipping_amount":{"type":"number","description":"Shipping amount."},"shipping_discount_tax_compensation_amount":{"type":"number","description":"Shipping discount tax compensation amount."},"shipping_incl_tax":{"type":"number","description":"Shipping including tax."},"shipping_tax_amount":{"type":"number","description":"Shipping tax amount."},"state":{"type":"integer","description":"State."},"store_currency_code":{"type":"string","description":"Store currency code."},"store_id":{"type":"integer","description":"Store ID."},"store_to_base_rate":{"type":"number","description":"Store-to-base rate."},"store_to_order_rate":{"type":"number","description":"Store-to-order rate."},"subtotal":{"type":"number","description":"Subtotal."},"subtotal_incl_tax":{"type":"number","description":"Subtotal including tax."},"tax_amount":{"type":"number","description":"Tax amount."},"total_qty":{"type":"number","description":"Total quantity."},"transaction_id":{"type":"string","description":"Transaction ID."},"updated_at":{"type":"string","description":"Updated-at timestamp."},"items":{"type":"array","description":"Array of invoice items.","items":{"$ref":"#/definitions/sales-data-invoice-item-interface"}},"comments":{"type":"array","description":"Array of any invoice comments. Otherwise, null.","items":{"$ref":"#/definitions/sales-data-invoice-comment-interface"}},"extension_attributes":{"$ref":"#/definitions/sales-data-invoice-extension-interface"}},"required":["order_id","total_qty","items"]},"sales-data-invoice-item-interface":{"type":"object","description":"Invoice item interface. An invoice is a record of the receipt of payment for an order. An invoice item is a purchased item in an invoice.","properties":{"additional_data":{"type":"string","description":"Additional data."},"base_cost":{"type":"number","description":"Base cost."},"base_discount_amount":{"type":"number","description":"Base discount amount."},"base_discount_tax_compensation_amount":{"type":"number","description":"Base discount tax compensation amount."},"base_price":{"type":"number","description":"Base price."},"base_price_incl_tax":{"type":"number","description":"Base price including tax."},"base_row_total":{"type":"number","description":"Base row total."},"base_row_total_incl_tax":{"type":"number","description":"Base row total including tax."},"base_tax_amount":{"type":"number","description":"Base tax amount."},"description":{"type":"string","description":"Description."},"discount_amount":{"type":"number","description":"Discount amount."},"entity_id":{"type":"integer","description":"Invoice item ID."},"discount_tax_compensation_amount":{"type":"number","description":"Discount tax compensation amount."},"name":{"type":"string","description":"Name."},"parent_id":{"type":"integer","description":"Parent ID."},"price":{"type":"number","description":"Price."},"price_incl_tax":{"type":"number","description":"Price including tax."},"product_id":{"type":"integer","description":"Product ID."},"row_total":{"type":"number","description":"Row total."},"row_total_incl_tax":{"type":"number","description":"Row total including tax."},"sku":{"type":"string","description":"SKU."},"tax_amount":{"type":"number","description":"Tax amount."},"extension_attributes":{"$ref":"#/definitions/sales-data-invoice-item-extension-interface"},"order_item_id":{"type":"integer","description":"Order item ID."},"qty":{"type":"number","description":"Quantity."}},"required":["sku","order_item_id","qty"]},"sales-data-invoice-item-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\Sales\\Api\\Data\\InvoiceItemInterface"},"sales-data-invoice-comment-interface":{"type":"object","description":"Invoice comment interface. An invoice is a record of the receipt of payment for an order. An invoice can include comments that detail the invoice history.","properties":{"is_customer_notified":{"type":"integer","description":"Is-customer-notified flag value."},"parent_id":{"type":"integer","description":"Parent ID."},"extension_attributes":{"$ref":"#/definitions/sales-data-invoice-comment-extension-interface"},"comment":{"type":"string","description":"Comment."},"is_visible_on_front":{"type":"integer","description":"Is-visible-on-storefront flag value."},"created_at":{"type":"string","description":"Created-at timestamp."},"entity_id":{"type":"integer","description":"Invoice ID."}},"required":["is_customer_notified","parent_id","comment","is_visible_on_front"]},"sales-data-invoice-comment-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\Sales\\Api\\Data\\InvoiceCommentInterface"},"sales-data-invoice-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\Sales\\Api\\Data\\InvoiceInterface","properties":{"base_customer_balance_amount":{"type":"number"},"customer_balance_amount":{"type":"number"},"base_gift_cards_amount":{"type":"number"},"gift_cards_amount":{"type":"number"},"gw_base_price":{"type":"string"},"gw_price":{"type":"string"},"gw_items_base_price":{"type":"string"},"gw_items_price":{"type":"string"},"gw_card_base_price":{"type":"string"},"gw_card_price":{"type":"string"},"gw_base_tax_amount":{"type":"string"},"gw_tax_amount":{"type":"string"},"gw_items_base_tax_amount":{"type":"string"},"gw_items_tax_amount":{"type":"string"},"gw_card_base_tax_amount":{"type":"string"},"gw_card_tax_amount":{"type":"string"},"invoice_api_test_attribute":{"$ref":"#/definitions/user-data-user-interface"}}},"sales-data-invoice-search-result-interface":{"type":"object","description":"Invoice search result interface. An invoice is a record of the receipt of payment for an order.","properties":{"items":{"type":"array","description":"Array of collection items.","items":{"$ref":"#/definitions/sales-data-invoice-interface"}},"search_criteria":{"$ref":"#/definitions/framework-search-criteria-interface"},"total_count":{"type":"integer","description":"Total count."}},"required":["items","search_criteria","total_count"]},"sales-data-invoice-comment-search-result-interface":{"type":"object","description":"Invoice comment search result interface. An invoice is a record of the receipt of payment for an order. An invoice can include comments that detail the invoice history.","properties":{"items":{"type":"array","description":"Array of collection items.","items":{"$ref":"#/definitions/sales-data-invoice-comment-interface"}},"search_criteria":{"$ref":"#/definitions/framework-search-criteria-interface"},"total_count":{"type":"integer","description":"Total count."}},"required":["items","search_criteria","total_count"]},"sales-data-creditmemo-item-creation-interface":{"type":"object","description":"Interface CreditmemoItemCreationInterface","properties":{"extension_attributes":{"$ref":"#/definitions/sales-data-creditmemo-item-creation-extension-interface"},"order_item_id":{"type":"integer","description":"Order item ID."},"qty":{"type":"number","description":"Quantity."}},"required":["order_item_id","qty"]},"sales-data-creditmemo-item-creation-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\Sales\\Api\\Data\\CreditmemoItemCreationInterface"},"sales-data-creditmemo-comment-creation-interface":{"type":"object","description":"Interface CreditmemoCommentCreationInterface","properties":{"extension_attributes":{"$ref":"#/definitions/sales-data-creditmemo-comment-creation-extension-interface"},"comment":{"type":"string","description":"Comment."},"is_visible_on_front":{"type":"integer","description":"Is-visible-on-storefront flag value."}},"required":["comment","is_visible_on_front"]},"sales-data-creditmemo-comment-creation-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\Sales\\Api\\Data\\CreditmemoCommentCreationInterface"},"sales-data-creditmemo-creation-arguments-interface":{"type":"object","description":"Interface CreditmemoCreationArgumentsInterface","properties":{"shipping_amount":{"type":"number","description":"Credit memo shipping amount."},"adjustment_positive":{"type":"number","description":"Credit memo positive adjustment."},"adjustment_negative":{"type":"number","description":"Credit memo negative adjustment."},"extension_attributes":{"$ref":"#/definitions/sales-data-creditmemo-creation-arguments-extension-interface"}}},"sales-data-creditmemo-creation-arguments-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\Sales\\Api\\Data\\CreditmemoCreationArgumentsInterface","properties":{"return_to_stock_items":{"type":"array","items":{"type":"integer"}}}},"sales-data-creditmemo-comment-search-result-interface":{"type":"object","description":"Credit memo comment search result interface. After a customer places and pays for an order and an invoice has been issued, the merchant can create a credit memo to refund all or part of the amount paid for any returned or undelivered items. The memo restores funds to the customer account so that the customer can make future purchases. A credit memo usually includes comments that detail why the credit memo amount was credited to the customer.","properties":{"items":{"type":"array","description":"Array of collection items.","items":{"$ref":"#/definitions/sales-data-creditmemo-comment-interface"}},"search_criteria":{"$ref":"#/definitions/framework-search-criteria-interface"},"total_count":{"type":"integer","description":"Total count."}},"required":["items","search_criteria","total_count"]},"sales-data-creditmemo-comment-interface":{"type":"object","description":"Credit memo comment interface. After a customer places and pays for an order and an invoice has been issued, the merchant can create a credit memo to refund all or part of the amount paid for any returned or undelivered items. The memo restores funds to the customer account so that the customer can make future purchases. A credit memo usually includes comments that detail why the credit memo amount was credited to the customer.","properties":{"comment":{"type":"string","description":"Comment."},"created_at":{"type":"string","description":"Created-at timestamp."},"entity_id":{"type":"integer","description":"Credit memo ID."},"is_customer_notified":{"type":"integer","description":"Is-customer-notified flag value."},"is_visible_on_front":{"type":"integer","description":"Is-visible-on-storefront flag value."},"parent_id":{"type":"integer","description":"Parent ID."},"extension_attributes":{"$ref":"#/definitions/sales-data-creditmemo-comment-extension-interface"}},"required":["comment","is_customer_notified","is_visible_on_front","parent_id"]},"sales-data-creditmemo-comment-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\Sales\\Api\\Data\\CreditmemoCommentInterface"},"sales-data-creditmemo-interface":{"type":"object","description":"Credit memo interface. After a customer places and pays for an order and an invoice has been issued, the merchant can create a credit memo to refund all or part of the amount paid for any returned or undelivered items. The memo restores funds to the customer account so that the customer can make future purchases.","properties":{"adjustment":{"type":"number","description":"Credit memo adjustment."},"adjustment_negative":{"type":"number","description":"Credit memo negative adjustment."},"adjustment_positive":{"type":"number","description":"Credit memo positive adjustment."},"base_adjustment":{"type":"number","description":"Credit memo base adjustment."},"base_adjustment_negative":{"type":"number","description":"Credit memo negative base adjustment."},"base_adjustment_positive":{"type":"number","description":"Credit memo positive base adjustment."},"base_currency_code":{"type":"string","description":"Credit memo base currency code."},"base_discount_amount":{"type":"number","description":"Credit memo base discount amount."},"base_grand_total":{"type":"number","description":"Credit memo base grand total."},"base_discount_tax_compensation_amount":{"type":"number","description":"Credit memo base discount tax compensation amount."},"base_shipping_amount":{"type":"number","description":"Credit memo base shipping amount."},"base_shipping_discount_tax_compensation_amnt":{"type":"number","description":"Credit memo base shipping discount tax compensation amount."},"base_shipping_incl_tax":{"type":"number","description":"Credit memo base shipping including tax."},"base_shipping_tax_amount":{"type":"number","description":"Credit memo base shipping tax amount."},"base_subtotal":{"type":"number","description":"Credit memo base subtotal."},"base_subtotal_incl_tax":{"type":"number","description":"Credit memo base subtotal including tax."},"base_tax_amount":{"type":"number","description":"Credit memo base tax amount."},"base_to_global_rate":{"type":"number","description":"Credit memo base-to-global rate."},"base_to_order_rate":{"type":"number","description":"Credit memo base-to-order rate."},"billing_address_id":{"type":"integer","description":"Credit memo billing address ID."},"created_at":{"type":"string","description":"Credit memo created-at timestamp."},"creditmemo_status":{"type":"integer","description":"Credit memo status."},"discount_amount":{"type":"number","description":"Credit memo discount amount."},"discount_description":{"type":"string","description":"Credit memo discount description."},"email_sent":{"type":"integer","description":"Credit memo email sent flag value."},"entity_id":{"type":"integer","description":"Credit memo ID."},"global_currency_code":{"type":"string","description":"Credit memo global currency code."},"grand_total":{"type":"number","description":"Credit memo grand total."},"discount_tax_compensation_amount":{"type":"number","description":"Credit memo discount tax compensation amount."},"increment_id":{"type":"string","description":"Credit memo increment ID."},"invoice_id":{"type":"integer","description":"Credit memo invoice ID."},"order_currency_code":{"type":"string","description":"Credit memo order currency code."},"order_id":{"type":"integer","description":"Credit memo order ID."},"shipping_address_id":{"type":"integer","description":"Credit memo shipping address ID."},"shipping_amount":{"type":"number","description":"Credit memo shipping amount."},"shipping_discount_tax_compensation_amount":{"type":"number","description":"Credit memo shipping discount tax compensation amount."},"shipping_incl_tax":{"type":"number","description":"Credit memo shipping including tax."},"shipping_tax_amount":{"type":"number","description":"Credit memo shipping tax amount."},"state":{"type":"integer","description":"Credit memo state."},"store_currency_code":{"type":"string","description":"Credit memo store currency code."},"store_id":{"type":"integer","description":"Credit memo store ID."},"store_to_base_rate":{"type":"number","description":"Credit memo store-to-base rate."},"store_to_order_rate":{"type":"number","description":"Credit memo store-to-order rate."},"subtotal":{"type":"number","description":"Credit memo subtotal."},"subtotal_incl_tax":{"type":"number","description":"Credit memo subtotal including tax."},"tax_amount":{"type":"number","description":"Credit memo tax amount."},"transaction_id":{"type":"string","description":"Credit memo transaction ID."},"updated_at":{"type":"string","description":"Credit memo updated-at timestamp."},"items":{"type":"array","description":"Array of credit memo items.","items":{"$ref":"#/definitions/sales-data-creditmemo-item-interface"}},"comments":{"type":"array","description":"Array of any credit memo comments. Otherwise, null.","items":{"$ref":"#/definitions/sales-data-creditmemo-comment-interface"}},"extension_attributes":{"$ref":"#/definitions/sales-data-creditmemo-extension-interface"}},"required":["order_id","items"]},"sales-data-creditmemo-item-interface":{"type":"object","description":"Credit memo item interface. After a customer places and pays for an order and an invoice has been issued, the merchant can create a credit memo to refund all or part of the amount paid for any returned or undelivered items. The memo restores funds to the customer account so that the customer can make future purchases. A credit memo item is an invoiced item for which a merchant creates a credit memo.","properties":{"additional_data":{"type":"string","description":"Additional data."},"base_cost":{"type":"number","description":"The base cost for a credit memo item."},"base_discount_amount":{"type":"number","description":"The base discount amount for a credit memo item."},"base_discount_tax_compensation_amount":{"type":"number","description":"The base discount tax compensation amount for a credit memo item."},"base_price":{"type":"number","description":"The base price for a credit memo item."},"base_price_incl_tax":{"type":"number","description":"Base price including tax."},"base_row_total":{"type":"number","description":"Base row total."},"base_row_total_incl_tax":{"type":"number","description":"Base row total including tax."},"base_tax_amount":{"type":"number","description":"Base tax amount."},"base_weee_tax_applied_amount":{"type":"number","description":"Base WEEE tax applied amount."},"base_weee_tax_applied_row_amnt":{"type":"number","description":"Base WEEE tax applied row amount."},"base_weee_tax_disposition":{"type":"number","description":"Base WEEE tax disposition."},"base_weee_tax_row_disposition":{"type":"number","description":"Base WEEE tax row disposition."},"description":{"type":"string","description":"Description."},"discount_amount":{"type":"number","description":"Discount amount."},"entity_id":{"type":"integer","description":"Credit memo item ID."},"discount_tax_compensation_amount":{"type":"number","description":"Discount tax compensation amount."},"name":{"type":"string","description":"Name."},"order_item_id":{"type":"integer","description":"Order item ID."},"parent_id":{"type":"integer","description":"Parent ID."},"price":{"type":"number","description":"Price."},"price_incl_tax":{"type":"number","description":"Price including tax."},"product_id":{"type":"integer","description":"Product ID."},"qty":{"type":"number","description":"Quantity."},"row_total":{"type":"number","description":"Row total."},"row_total_incl_tax":{"type":"number","description":"Row total including tax."},"sku":{"type":"string","description":"SKU."},"tax_amount":{"type":"number","description":"Tax amount."},"weee_tax_applied":{"type":"string","description":"WEEE tax applied."},"weee_tax_applied_amount":{"type":"number","description":"WEEE tax applied amount."},"weee_tax_applied_row_amount":{"type":"number","description":"WEEE tax applied row amount."},"weee_tax_disposition":{"type":"number","description":"WEEE tax disposition."},"weee_tax_row_disposition":{"type":"number","description":"WEEE tax row disposition."},"extension_attributes":{"$ref":"#/definitions/sales-data-creditmemo-item-extension-interface"}},"required":["base_cost","base_price","entity_id","order_item_id","qty"]},"sales-data-creditmemo-item-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\Sales\\Api\\Data\\CreditmemoItemInterface"},"sales-data-creditmemo-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\Sales\\Api\\Data\\CreditmemoInterface","properties":{"base_customer_balance_amount":{"type":"number"},"customer_balance_amount":{"type":"number"},"base_gift_cards_amount":{"type":"number"},"gift_cards_amount":{"type":"number"},"gw_base_price":{"type":"string"},"gw_price":{"type":"string"},"gw_items_base_price":{"type":"string"},"gw_items_price":{"type":"string"},"gw_card_base_price":{"type":"string"},"gw_card_price":{"type":"string"},"gw_base_tax_amount":{"type":"string"},"gw_tax_amount":{"type":"string"},"gw_items_base_tax_amount":{"type":"string"},"gw_items_tax_amount":{"type":"string"},"gw_card_base_tax_amount":{"type":"string"},"gw_card_tax_amount":{"type":"string"}}},"sales-data-creditmemo-search-result-interface":{"type":"object","description":"Credit memo search result interface. After a customer places and pays for an order and an invoice has been issued, the merchant can create a credit memo to refund all or part of the amount paid for any returned or undelivered items. The memo restores funds to the customer account so that the customer can make future purchases.","properties":{"items":{"type":"array","description":"Array of collection items.","items":{"$ref":"#/definitions/sales-data-creditmemo-interface"}},"search_criteria":{"$ref":"#/definitions/framework-search-criteria-interface"},"total_count":{"type":"integer","description":"Total count."}},"required":["items","search_criteria","total_count"]},"sales-data-shipment-interface":{"type":"object","description":"Shipment interface. A shipment is a delivery package that contains products. A shipment document accompanies the shipment. This document lists the products and their quantities in the delivery package.","properties":{"billing_address_id":{"type":"integer","description":"Billing address ID."},"created_at":{"type":"string","description":"Created-at timestamp."},"customer_id":{"type":"integer","description":"Customer ID."},"email_sent":{"type":"integer","description":"Email-sent flag value."},"entity_id":{"type":"integer","description":"Shipment ID."},"increment_id":{"type":"string","description":"Increment ID."},"order_id":{"type":"integer","description":"Order ID."},"packages":{"type":"array","description":"Array of packages, if any. Otherwise, null.","items":{"$ref":"#/definitions/sales-data-shipment-package-interface"}},"shipment_status":{"type":"integer","description":"Shipment status."},"shipping_address_id":{"type":"integer","description":"Shipping address ID."},"shipping_label":{"type":"string","description":"Shipping label."},"store_id":{"type":"integer","description":"Store ID."},"total_qty":{"type":"number","description":"Total quantity."},"total_weight":{"type":"number","description":"Total weight."},"updated_at":{"type":"string","description":"Updated-at timestamp."},"items":{"type":"array","description":"Array of items.","items":{"$ref":"#/definitions/sales-data-shipment-item-interface"}},"tracks":{"type":"array","description":"Array of tracks.","items":{"$ref":"#/definitions/sales-data-shipment-track-interface"}},"comments":{"type":"array","description":"Array of comments.","items":{"$ref":"#/definitions/sales-data-shipment-comment-interface"}},"extension_attributes":{"$ref":"#/definitions/sales-data-shipment-extension-interface"}},"required":["order_id","items","tracks","comments"]},"sales-data-shipment-package-interface":{"type":"object","description":"Shipment package interface. A shipment is a delivery package that contains products. A shipment document accompanies the shipment. This document lists the products and their quantities in the delivery package.","properties":{"extension_attributes":{"$ref":"#/definitions/sales-data-shipment-package-extension-interface"}}},"sales-data-shipment-package-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\Sales\\Api\\Data\\ShipmentPackageInterface"},"sales-data-shipment-item-interface":{"type":"object","description":"Shipment item interface. A shipment is a delivery package that contains products. A shipment document accompanies the shipment. This document lists the products and their quantities in the delivery package. A product is an item in a shipment.","properties":{"additional_data":{"type":"string","description":"Additional data."},"description":{"type":"string","description":"Description."},"entity_id":{"type":"integer","description":"Shipment item ID."},"name":{"type":"string","description":"Name."},"parent_id":{"type":"integer","description":"Parent ID."},"price":{"type":"number","description":"Price."},"product_id":{"type":"integer","description":"Product ID."},"row_total":{"type":"number","description":"Row total."},"sku":{"type":"string","description":"SKU."},"weight":{"type":"number","description":"Weight."},"extension_attributes":{"$ref":"#/definitions/sales-data-shipment-item-extension-interface"},"order_item_id":{"type":"integer","description":"Order item ID."},"qty":{"type":"number","description":"Quantity."}},"required":["order_item_id","qty"]},"sales-data-shipment-item-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\Sales\\Api\\Data\\ShipmentItemInterface"},"sales-data-shipment-track-interface":{"type":"object","description":"Shipment track interface. A shipment is a delivery package that contains products. A shipment document accompanies the shipment. This document lists the products and their quantities in the delivery package. Merchants and customers can track shipments.","properties":{"order_id":{"type":"integer","description":"The order_id for the shipment package."},"created_at":{"type":"string","description":"Created-at timestamp."},"entity_id":{"type":"integer","description":"Shipment package ID."},"parent_id":{"type":"integer","description":"Parent ID."},"updated_at":{"type":"string","description":"Updated-at timestamp."},"weight":{"type":"number","description":"Weight."},"qty":{"type":"number","description":"Quantity."},"description":{"type":"string","description":"Description."},"extension_attributes":{"$ref":"#/definitions/sales-data-shipment-track-extension-interface"},"track_number":{"type":"string","description":"Track number."},"title":{"type":"string","description":"Title."},"carrier_code":{"type":"string","description":"Carrier code."}},"required":["order_id","parent_id","weight","qty","description","track_number","title","carrier_code"]},"sales-data-shipment-track-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\Sales\\Api\\Data\\ShipmentTrackInterface"},"sales-data-shipment-comment-interface":{"type":"object","description":"Shipment comment interface. A shipment is a delivery package that contains products. A shipment document accompanies the shipment. This document lists the products and their quantities in the delivery package. A shipment document can contain comments.","properties":{"is_customer_notified":{"type":"integer","description":"Is-customer-notified flag value."},"parent_id":{"type":"integer","description":"Parent ID."},"extension_attributes":{"$ref":"#/definitions/sales-data-shipment-comment-extension-interface"},"comment":{"type":"string","description":"Comment."},"is_visible_on_front":{"type":"integer","description":"Is-visible-on-storefront flag value."},"created_at":{"type":"string","description":"Created-at timestamp."},"entity_id":{"type":"integer","description":"Invoice ID."}},"required":["is_customer_notified","parent_id","comment","is_visible_on_front"]},"sales-data-shipment-comment-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\Sales\\Api\\Data\\ShipmentCommentInterface"},"sales-data-shipment-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\Sales\\Api\\Data\\ShipmentInterface"},"sales-data-shipment-search-result-interface":{"type":"object","description":"Shipment search result interface. A shipment is a delivery package that contains products. A shipment document accompanies the shipment. This document lists the products and their quantities in the delivery package.","properties":{"items":{"type":"array","description":"Array of collection items.","items":{"$ref":"#/definitions/sales-data-shipment-interface"}},"search_criteria":{"$ref":"#/definitions/framework-search-criteria-interface"},"total_count":{"type":"integer","description":"Total count."}},"required":["items","search_criteria","total_count"]},"sales-data-shipment-comment-search-result-interface":{"type":"object","description":"Shipment comment search result interface. A shipment is a delivery package that contains products. A shipment document accompanies the shipment. This document lists the products and their quantities in the delivery package. A shipment document can contain comments.","properties":{"items":{"type":"array","description":"Array of collection items.","items":{"$ref":"#/definitions/sales-data-shipment-comment-interface"}},"search_criteria":{"$ref":"#/definitions/framework-search-criteria-interface"},"total_count":{"type":"integer","description":"Total count."}},"required":["items","search_criteria","total_count"]},"sales-data-shipment-item-creation-interface":{"type":"object","description":"Input argument for shipment item creation Interface ShipmentItemCreationInterface","properties":{"extension_attributes":{"$ref":"#/definitions/sales-data-shipment-item-creation-extension-interface"},"order_item_id":{"type":"integer","description":"Order item ID."},"qty":{"type":"number","description":"Quantity."}},"required":["order_item_id","qty"]},"sales-data-shipment-item-creation-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\Sales\\Api\\Data\\ShipmentItemCreationInterface"},"sales-data-shipment-comment-creation-interface":{"type":"object","description":"Interface ShipmentCommentCreationInterface","properties":{"extension_attributes":{"$ref":"#/definitions/sales-data-shipment-comment-creation-extension-interface"},"comment":{"type":"string","description":"Comment."},"is_visible_on_front":{"type":"integer","description":"Is-visible-on-storefront flag value."}},"required":["comment","is_visible_on_front"]},"sales-data-shipment-comment-creation-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\Sales\\Api\\Data\\ShipmentCommentCreationInterface"},"sales-data-shipment-track-creation-interface":{"type":"object","description":"Shipment Track Creation interface.","properties":{"extension_attributes":{"$ref":"#/definitions/sales-data-shipment-track-creation-extension-interface"},"track_number":{"type":"string","description":"Track number."},"title":{"type":"string","description":"Title."},"carrier_code":{"type":"string","description":"Carrier code."}},"required":["track_number","title","carrier_code"]},"sales-data-shipment-track-creation-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\Sales\\Api\\Data\\ShipmentTrackCreationInterface"},"sales-data-shipment-package-creation-interface":{"type":"object","description":"Shipment package interface. A shipment is a delivery package that contains products. A shipment document accompanies the shipment. This document lists the products and their quantities in the delivery package.","properties":{"extension_attributes":{"$ref":"#/definitions/sales-data-shipment-package-creation-extension-interface"}}},"sales-data-shipment-package-creation-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\Sales\\Api\\Data\\ShipmentPackageCreationInterface"},"sales-data-shipment-creation-arguments-interface":{"type":"object","description":"Interface for creation arguments for Shipment.","properties":{"extension_attributes":{"$ref":"#/definitions/sales-data-shipment-creation-arguments-extension-interface"}}},"sales-data-shipment-creation-arguments-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\Sales\\Api\\Data\\ShipmentCreationArgumentsInterface"},"sales-data-transaction-interface":{"type":"object","description":"Transaction interface. A transaction is an interaction between a merchant and a customer such as a purchase, a credit, a refund, and so on.","properties":{"transaction_id":{"type":"integer","description":"Transaction ID."},"parent_id":{"type":"integer","description":"The parent ID for the transaction. Otherwise, null."},"order_id":{"type":"integer","description":"Order ID."},"payment_id":{"type":"integer","description":"Payment ID."},"txn_id":{"type":"string","description":"Transaction business ID."},"parent_txn_id":{"type":"string","description":"Parent transaction business ID."},"txn_type":{"type":"string","description":"Transaction type."},"is_closed":{"type":"integer","description":"Is-closed flag value."},"additional_information":{"type":"array","description":"Array of additional information. Otherwise, null.","items":{"type":"string"}},"created_at":{"type":"string","description":"Created-at timestamp."},"child_transactions":{"type":"array","description":"Array of child transactions.","items":{"$ref":"#/definitions/sales-data-transaction-interface"}},"extension_attributes":{"$ref":"#/definitions/sales-data-transaction-extension-interface"}},"required":["transaction_id","order_id","payment_id","txn_id","parent_txn_id","txn_type","is_closed","created_at","child_transactions"]},"sales-data-transaction-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\Sales\\Api\\Data\\TransactionInterface"},"sales-data-transaction-search-result-interface":{"type":"object","description":"Transaction search result interface. A transaction is an interaction between a merchant and a customer such as a purchase, a credit, a refund, and so on.","properties":{"items":{"type":"array","description":"Array of collection items.","items":{"$ref":"#/definitions/sales-data-transaction-interface"}},"search_criteria":{"$ref":"#/definitions/framework-search-criteria-interface"},"total_count":{"type":"integer","description":"Total count."}},"required":["items","search_criteria","total_count"]},"sales-data-invoice-item-creation-interface":{"type":"object","description":"Input argument for invoice creation Interface InvoiceItemCreationInterface","properties":{"extension_attributes":{"$ref":"#/definitions/sales-data-invoice-item-creation-extension-interface"},"order_item_id":{"type":"integer","description":"Order item ID."},"qty":{"type":"number","description":"Quantity."}},"required":["order_item_id","qty"]},"sales-data-invoice-item-creation-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\Sales\\Api\\Data\\InvoiceItemCreationInterface"},"sales-data-invoice-comment-creation-interface":{"type":"object","description":"Interface InvoiceCommentCreationInterface","properties":{"extension_attributes":{"$ref":"#/definitions/sales-data-invoice-comment-creation-extension-interface"},"comment":{"type":"string","description":"Comment."},"is_visible_on_front":{"type":"integer","description":"Is-visible-on-storefront flag value."}},"required":["comment","is_visible_on_front"]},"sales-data-invoice-comment-creation-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\Sales\\Api\\Data\\InvoiceCommentCreationInterface"},"sales-data-invoice-creation-arguments-interface":{"type":"object","description":"Interface for creation arguments for Invoice.","properties":{"extension_attributes":{"$ref":"#/definitions/sales-data-invoice-creation-arguments-extension-interface"}}},"sales-data-invoice-creation-arguments-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\Sales\\Api\\Data\\InvoiceCreationArgumentsInterface"},"checkout-data-shipping-information-interface":{"type":"object","description":"Interface ShippingInformationInterface","properties":{"shipping_address":{"$ref":"#/definitions/quote-data-address-interface"},"billing_address":{"$ref":"#/definitions/quote-data-address-interface"},"shipping_method_code":{"type":"string","description":"Shipping method code"},"shipping_carrier_code":{"type":"string","description":"Carrier code"},"extension_attributes":{"$ref":"#/definitions/checkout-data-shipping-information-extension-interface"},"custom_attributes":{"type":"array","description":"Custom attributes values.","items":{"$ref":"#/definitions/framework-attribute-interface"}}},"required":["shipping_address","shipping_method_code","shipping_carrier_code"]},"checkout-data-shipping-information-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\Checkout\\Api\\Data\\ShippingInformationInterface"},"checkout-data-payment-details-interface":{"type":"object","description":"Interface PaymentDetailsInterface","properties":{"payment_methods":{"type":"array","items":{"$ref":"#/definitions/quote-data-payment-method-interface"}},"totals":{"$ref":"#/definitions/quote-data-totals-interface"},"extension_attributes":{"$ref":"#/definitions/checkout-data-payment-details-extension-interface"}},"required":["payment_methods","totals"]},"checkout-data-payment-details-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\Checkout\\Api\\Data\\PaymentDetailsInterface"},"checkout-data-totals-information-interface":{"type":"object","description":"Interface TotalsInformationInterface","properties":{"address":{"$ref":"#/definitions/quote-data-address-interface"},"shipping_method_code":{"type":"string","description":"Shipping method code"},"shipping_carrier_code":{"type":"string","description":"Carrier code"},"extension_attributes":{"$ref":"#/definitions/checkout-data-totals-information-extension-interface"},"custom_attributes":{"type":"array","description":"Custom attributes values.","items":{"$ref":"#/definitions/framework-attribute-interface"}}},"required":["address"]},"checkout-data-totals-information-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\Checkout\\Api\\Data\\TotalsInformationInterface"},"checkout-agreements-data-agreement-interface":{"type":"object","description":"Interface AgreementInterface","properties":{"agreement_id":{"type":"integer","description":"Agreement ID."},"name":{"type":"string","description":"Agreement name."},"content":{"type":"string","description":"Agreement content."},"content_height":{"type":"string","description":"Agreement content height. Otherwise, null."},"checkbox_text":{"type":"string","description":"Agreement checkbox text."},"is_active":{"type":"boolean","description":"Agreement status."},"is_html":{"type":"boolean","description":"* true - HTML. * false - plain text."},"mode":{"type":"integer","description":"The agreement applied mode."},"extension_attributes":{"$ref":"#/definitions/checkout-agreements-data-agreement-extension-interface"}},"required":["agreement_id","name","content","checkbox_text","is_active","is_html","mode"]},"checkout-agreements-data-agreement-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\CheckoutAgreements\\Api\\Data\\AgreementInterface"},"gift-card-account-data-gift-card-account-interface":{"type":"object","description":"Gift Card Account data","properties":{"gift_cards":{"type":"array","description":"Cards codes","items":{"type":"string"}},"gift_cards_amount":{"type":"number","description":"Cards amount in quote currency"},"base_gift_cards_amount":{"type":"number","description":"Cards amount in base currency"},"gift_cards_amount_used":{"type":"number","description":"Cards amount used in quote currency"},"base_gift_cards_amount_used":{"type":"number","description":"Cards amount used in base currency"},"extension_attributes":{"$ref":"#/definitions/gift-card-account-data-gift-card-account-extension-interface"}},"required":["gift_cards","gift_cards_amount","base_gift_cards_amount","gift_cards_amount_used","base_gift_cards_amount_used"]},"gift-card-account-data-gift-card-account-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\GiftCardAccount\\Api\\Data\\GiftCardAccountInterface"},"tax-data-tax-rate-interface":{"type":"object","description":"Tax rate interface.","properties":{"id":{"type":"integer","description":"Id"},"tax_country_id":{"type":"string","description":"Country id"},"tax_region_id":{"type":"integer","description":"Region id"},"region_name":{"type":"string","description":"Region name"},"tax_postcode":{"type":"string","description":"Postcode"},"zip_is_range":{"type":"integer","description":"Zip is range"},"zip_from":{"type":"integer","description":"Zip range from"},"zip_to":{"type":"integer","description":"Zip range to"},"rate":{"type":"number","description":"Tax rate in percentage"},"code":{"type":"string","description":"Tax rate code"},"titles":{"type":"array","description":"Tax rate titles","items":{"$ref":"#/definitions/tax-data-tax-rate-title-interface"}},"extension_attributes":{"$ref":"#/definitions/tax-data-tax-rate-extension-interface"}},"required":["tax_country_id","rate","code"]},"tax-data-tax-rate-title-interface":{"type":"object","description":"Tax rate title interface.","properties":{"store_id":{"type":"string","description":"Store id"},"value":{"type":"string","description":"Title value"},"extension_attributes":{"$ref":"#/definitions/tax-data-tax-rate-title-extension-interface"}},"required":["store_id","value"]},"tax-data-tax-rate-title-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\Tax\\Api\\Data\\TaxRateTitleInterface"},"tax-data-tax-rate-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\Tax\\Api\\Data\\TaxRateInterface"},"tax-data-tax-rate-search-results-interface":{"type":"object","description":"Interface for tax rate search results.","properties":{"items":{"type":"array","description":"Items","items":{"$ref":"#/definitions/tax-data-tax-rate-interface"}},"search_criteria":{"$ref":"#/definitions/framework-search-criteria-interface"},"total_count":{"type":"integer","description":"Total count."}},"required":["items","search_criteria","total_count"]},"tax-data-tax-rule-interface":{"type":"object","description":"Tax rule interface.","properties":{"id":{"type":"integer","description":"Id"},"code":{"type":"string","description":"Tax rule code"},"priority":{"type":"integer","description":"Priority"},"position":{"type":"integer","description":"Sort order."},"customer_tax_class_ids":{"type":"array","description":"Customer tax class id","items":{"type":"integer"}},"product_tax_class_ids":{"type":"array","description":"Product tax class id","items":{"type":"integer"}},"tax_rate_ids":{"type":"array","description":"Tax rate ids","items":{"type":"integer"}},"calculate_subtotal":{"type":"boolean","description":"Calculate subtotal."},"extension_attributes":{"$ref":"#/definitions/tax-data-tax-rule-extension-interface"}},"required":["code","priority","position","customer_tax_class_ids","product_tax_class_ids","tax_rate_ids"]},"tax-data-tax-rule-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\Tax\\Api\\Data\\TaxRuleInterface"},"tax-data-tax-rule-search-results-interface":{"type":"object","description":"Interface for tax rule search results.","properties":{"items":{"type":"array","description":"Items","items":{"$ref":"#/definitions/tax-data-tax-rule-interface"}},"search_criteria":{"$ref":"#/definitions/framework-search-criteria-interface"},"total_count":{"type":"integer","description":"Total count."}},"required":["items","search_criteria","total_count"]},"tax-data-tax-class-interface":{"type":"object","description":"Tax class interface.","properties":{"class_id":{"type":"integer","description":"Tax class ID."},"class_name":{"type":"string","description":"Tax class name."},"class_type":{"type":"string","description":"Tax class type."},"extension_attributes":{"$ref":"#/definitions/tax-data-tax-class-extension-interface"}},"required":["class_name","class_type"]},"tax-data-tax-class-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\Tax\\Api\\Data\\TaxClassInterface"},"tax-data-tax-class-search-results-interface":{"type":"object","description":"Interface for tax class search results.","properties":{"items":{"type":"array","description":"Items","items":{"$ref":"#/definitions/tax-data-tax-class-interface"}},"search_criteria":{"$ref":"#/definitions/framework-search-criteria-interface"},"total_count":{"type":"integer","description":"Total count."}},"required":["items","search_criteria","total_count"]},"gift-wrapping-data-wrapping-interface":{"type":"object","description":"Interface WrappingInterface","properties":{"wrapping_id":{"type":"integer"},"design":{"type":"string"},"status":{"type":"integer"},"base_price":{"type":"number"},"image_name":{"type":"string"},"image_base64_content":{"type":"string"},"base_currency_code":{"type":"string"},"website_ids":{"type":"array","items":{"type":"integer"}},"image_url":{"type":"string","description":"Wrapping image URL."},"extension_attributes":{"$ref":"#/definitions/gift-wrapping-data-wrapping-extension-interface"}},"required":["design","status","base_price"]},"gift-wrapping-data-wrapping-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\GiftWrapping\\Api\\Data\\WrappingInterface"},"gift-wrapping-data-wrapping-search-results-interface":{"type":"object","description":"Interface WrappingSearchResultsInterface","properties":{"items":{"type":"array","description":"Items","items":{"$ref":"#/definitions/gift-wrapping-data-wrapping-interface"}},"search_criteria":{"$ref":"#/definitions/framework-search-criteria-interface"},"total_count":{"type":"integer","description":"Total count."}},"required":["items","search_criteria","total_count"]},"sales-rule-data-rule-interface":{"type":"object","description":"Interface RuleInterface","properties":{"rule_id":{"type":"integer","description":"Rule id"},"name":{"type":"string","description":"Rule name"},"store_labels":{"type":"array","description":"Display label","items":{"$ref":"#/definitions/sales-rule-data-rule-label-interface"}},"description":{"type":"string","description":"Description"},"website_ids":{"type":"array","description":"A list of websites the rule applies to","items":{"type":"integer"}},"customer_group_ids":{"type":"array","description":"Ids of customer groups that the rule applies to","items":{"type":"integer"}},"from_date":{"type":"string","description":"The start date when the coupon is active"},"to_date":{"type":"string","description":"The end date when the coupon is active"},"uses_per_customer":{"type":"integer","description":"Number of uses per customer"},"is_active":{"type":"boolean","description":"The coupon is active"},"condition":{"$ref":"#/definitions/sales-rule-data-condition-interface"},"action_condition":{"$ref":"#/definitions/sales-rule-data-condition-interface"},"stop_rules_processing":{"type":"boolean","description":"To stop rule processing"},"is_advanced":{"type":"boolean","description":"Is this field needed"},"product_ids":{"type":"array","description":"Product ids","items":{"type":"integer"}},"sort_order":{"type":"integer","description":"Sort order"},"simple_action":{"type":"string","description":"Simple action of the rule"},"discount_amount":{"type":"number","description":"Discount amount"},"discount_qty":{"type":"number","description":"Maximum qty discount is applied"},"discount_step":{"type":"integer","description":"Discount step"},"apply_to_shipping":{"type":"boolean","description":"The rule applies to shipping"},"times_used":{"type":"integer","description":"How many times the rule has been used"},"is_rss":{"type":"boolean","description":"Whether the rule is in RSS"},"coupon_type":{"type":"string","description":"Coupon type"},"use_auto_generation":{"type":"boolean","description":"To auto generate coupon"},"uses_per_coupon":{"type":"integer","description":"Limit of uses per coupon"},"simple_free_shipping":{"type":"string","description":"To grant free shipping"},"extension_attributes":{"$ref":"#/definitions/sales-rule-data-rule-extension-interface"}},"required":["website_ids","customer_group_ids","uses_per_customer","is_active","stop_rules_processing","is_advanced","sort_order","discount_amount","discount_step","apply_to_shipping","times_used","is_rss","coupon_type","use_auto_generation","uses_per_coupon"]},"sales-rule-data-rule-label-interface":{"type":"object","description":"Interface RuleLabelInterface","properties":{"store_id":{"type":"integer","description":"StoreId"},"store_label":{"type":"string","description":"The label for the store"},"extension_attributes":{"$ref":"#/definitions/sales-rule-data-rule-label-extension-interface"}},"required":["store_id","store_label"]},"sales-rule-data-rule-label-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\SalesRule\\Api\\Data\\RuleLabelInterface"},"sales-rule-data-condition-interface":{"type":"object","description":"Interface ConditionInterface","properties":{"condition_type":{"type":"string","description":"Condition type"},"conditions":{"type":"array","description":"List of conditions","items":{"$ref":"#/definitions/sales-rule-data-condition-interface"}},"aggregator_type":{"type":"string","description":"The aggregator type"},"operator":{"type":"string","description":"The operator of the condition"},"attribute_name":{"type":"string","description":"The attribute name of the condition"},"value":{"type":"string","description":"The value of the condition"},"extension_attributes":{"$ref":"#/definitions/sales-rule-data-condition-extension-interface"}},"required":["condition_type","operator","value"]},"sales-rule-data-condition-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\SalesRule\\Api\\Data\\ConditionInterface"},"sales-rule-data-rule-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\SalesRule\\Api\\Data\\RuleInterface","properties":{"reward_points_delta":{"type":"integer"}}},"sales-rule-data-rule-search-result-interface":{"type":"object","description":"","properties":{"items":{"type":"array","description":"Rules.","items":{"$ref":"#/definitions/sales-rule-data-rule-interface"}},"search_criteria":{"$ref":"#/definitions/framework-search-criteria-interface"},"total_count":{"type":"integer","description":"Total count."}},"required":["items","search_criteria","total_count"]},"sales-rule-data-coupon-interface":{"type":"object","description":"Interface CouponInterface","properties":{"coupon_id":{"type":"integer","description":"Coupon id"},"rule_id":{"type":"integer","description":"The id of the rule associated with the coupon"},"code":{"type":"string","description":"Coupon code"},"usage_limit":{"type":"integer","description":"Usage limit"},"usage_per_customer":{"type":"integer","description":"Usage limit per customer"},"times_used":{"type":"integer","description":"The number of times the coupon has been used"},"expiration_date":{"type":"string","description":"Expiration date"},"is_primary":{"type":"boolean","description":"The coupon is primary coupon for the rule that it's associated with"},"created_at":{"type":"string","description":"When the coupon is created"},"type":{"type":"integer","description":"Of coupon"},"extension_attributes":{"$ref":"#/definitions/sales-rule-data-coupon-extension-interface"}},"required":["rule_id","times_used","is_primary"]},"sales-rule-data-coupon-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\SalesRule\\Api\\Data\\CouponInterface"},"sales-rule-data-coupon-search-result-interface":{"type":"object","description":"","properties":{"items":{"type":"array","description":"Rules.","items":{"$ref":"#/definitions/sales-rule-data-coupon-interface"}},"search_criteria":{"$ref":"#/definitions/framework-search-criteria-interface"},"total_count":{"type":"integer","description":"Total count."}},"required":["items","search_criteria","total_count"]},"sales-rule-data-coupon-generation-spec-interface":{"type":"object","description":"CouponGenerationSpecInterface","properties":{"rule_id":{"type":"integer","description":"The id of the rule associated with the coupon"},"format":{"type":"string","description":"Format of generated coupon code"},"quantity":{"type":"integer","description":"Of coupons to generate"},"length":{"type":"integer","description":"Length of coupon code"},"prefix":{"type":"string","description":"The prefix"},"suffix":{"type":"string","description":"The suffix"},"delimiter_at_every":{"type":"integer","description":"The spacing where the delimiter should exist"},"delimiter":{"type":"string","description":"The delimiter"},"extension_attributes":{"$ref":"#/definitions/sales-rule-data-coupon-generation-spec-extension-interface"}},"required":["rule_id","format","quantity","length"]},"sales-rule-data-coupon-generation-spec-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\SalesRule\\Api\\Data\\CouponGenerationSpecInterface"},"sales-rule-data-coupon-mass-delete-result-interface":{"type":"object","description":"Coupon mass delete results interface.","properties":{"failed_items":{"type":"array","description":"List of failed items.","items":{"type":"string"}},"missing_items":{"type":"array","description":"List of missing items.","items":{"type":"string"}}},"required":["failed_items","missing_items"]},"rma-data-track-interface":{"type":"object","description":"Interface TrackInterface","properties":{"entity_id":{"type":"integer","description":"Entity id"},"rma_entity_id":{"type":"integer","description":"Rma entity id"},"track_number":{"type":"string","description":"Track number"},"carrier_title":{"type":"string","description":"Carrier title"},"carrier_code":{"type":"string","description":"Carrier code"},"extension_attributes":{"$ref":"#/definitions/rma-data-track-extension-interface"}},"required":["entity_id","rma_entity_id","track_number","carrier_title","carrier_code"]},"rma-data-track-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\Rma\\Api\\Data\\TrackInterface"},"rma-data-track-search-result-interface":{"type":"object","description":"Interface TrackSearchResultInterface","properties":{"items":{"type":"array","description":"Rma list","items":{"$ref":"#/definitions/rma-data-track-interface"}},"search_criteria":{"$ref":"#/definitions/framework-search-criteria-interface"},"total_count":{"type":"integer","description":"Total count."}},"required":["items","search_criteria","total_count"]},"rma-data-rma-interface":{"type":"object","description":"Interface RmaInterface","properties":{"increment_id":{"type":"string","description":"Entity_id"},"entity_id":{"type":"integer","description":"Entity_id"},"order_id":{"type":"integer","description":"Order_id"},"order_increment_id":{"type":"string","description":"Order_increment_id"},"store_id":{"type":"integer","description":"Store_id"},"customer_id":{"type":"integer","description":"Customer_id"},"date_requested":{"type":"string","description":"Date_requested"},"customer_custom_email":{"type":"string","description":"Customer_custom_email"},"items":{"type":"array","description":"Items","items":{"$ref":"#/definitions/rma-data-item-interface"}},"status":{"type":"string","description":"Status"},"comments":{"type":"array","description":"Comments list","items":{"$ref":"#/definitions/rma-data-comment-interface"}},"tracks":{"type":"array","description":"Tracks list","items":{"$ref":"#/definitions/rma-data-track-interface"}},"extension_attributes":{"$ref":"#/definitions/rma-data-rma-extension-interface"},"custom_attributes":{"type":"array","description":"Custom attributes values.","items":{"$ref":"#/definitions/framework-attribute-interface"}}},"required":["increment_id","entity_id","order_id","order_increment_id","store_id","customer_id","date_requested","customer_custom_email","items","status","comments","tracks"]},"rma-data-item-interface":{"type":"object","description":"Interface CategoryInterface","properties":{"entity_id":{"type":"integer","description":"Id"},"rma_entity_id":{"type":"integer","description":"RMA id"},"order_item_id":{"type":"integer","description":"Order_item_id"},"qty_requested":{"type":"integer","description":"Qty_requested"},"qty_authorized":{"type":"integer","description":"Qty_authorized"},"qty_approved":{"type":"integer","description":"Qty_approved"},"qty_returned":{"type":"integer","description":"Qty_returned"},"reason":{"type":"string","description":"Reason"},"condition":{"type":"string","description":"Condition"},"resolution":{"type":"string","description":"Resolution"},"status":{"type":"string","description":"Status"},"extension_attributes":{"$ref":"#/definitions/rma-data-item-extension-interface"}},"required":["entity_id","rma_entity_id","order_item_id","qty_requested","qty_authorized","qty_approved","qty_returned","reason","condition","resolution","status"]},"rma-data-item-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\Rma\\Api\\Data\\ItemInterface"},"rma-data-comment-interface":{"type":"object","description":"Interface CommentInterface","properties":{"comment":{"type":"string","description":"Comment"},"rma_entity_id":{"type":"integer","description":"Rma Id"},"created_at":{"type":"string","description":"Created_at"},"entity_id":{"type":"integer","description":"Entity_id"},"customer_notified":{"type":"boolean","description":"Is_customer_notified"},"visible_on_front":{"type":"boolean","description":"Is_visible_on_front"},"status":{"type":"string","description":"Status"},"admin":{"type":"boolean","description":"Is_admin"},"extension_attributes":{"$ref":"#/definitions/rma-data-comment-extension-interface"},"custom_attributes":{"type":"array","description":"Custom attributes values.","items":{"$ref":"#/definitions/framework-attribute-interface"}}},"required":["comment","rma_entity_id","created_at","entity_id","customer_notified","visible_on_front","status","admin"]},"rma-data-comment-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\Rma\\Api\\Data\\CommentInterface"},"rma-data-rma-extension-interface":{"type":"object","description":"ExtensionInterface class for @see \\Magento\\Rma\\Api\\Data\\RmaInterface"},"rma-data-comment-search-result-interface":{"type":"object","description":"Interface CommentSearchResultInterface","properties":{"items":{"type":"array","description":"Rma Status History list","items":{"$ref":"#/definitions/rma-data-comment-interface"}},"search_criteria":{"$ref":"#/definitions/framework-search-criteria-interface"},"total_count":{"type":"integer","description":"Total count."}},"required":["items","search_criteria","total_count"]},"rma-data-rma-search-result-interface":{"type":"object","description":"Interface RmaSearchResultInterface","properties":{"items":{"type":"array","description":"Rma list","items":{"$ref":"#/definitions/rma-data-rma-interface"}},"search_criteria":{"$ref":"#/definitions/framework-search-criteria-interface"},"total_count":{"type":"integer","description":"Total count."}},"required":["items","search_criteria","total_count"]},"framework-metadata-object-interface":{"type":"object","description":"Provides metadata about an attribute.","properties":{"attribute_code":{"type":"string","description":"Code of the attribute."}},"required":["attribute_code"]},"test-module1-v1-entity-item":{"type":"object","description":"","properties":{"item_id":{"type":"integer"},"name":{"type":"string"},"custom_attributes":{"type":"array","description":"Custom attributes values.","items":{"$ref":"#/definitions/framework-attribute-interface"}}},"required":["item_id","name"]},"test-module1-v2-entity-item":{"type":"object","description":"","properties":{"id":{"type":"integer"},"name":{"type":"string"},"price":{"type":"string"},"custom_attributes":{"type":"array","description":"Custom attributes values.","items":{"$ref":"#/definitions/framework-attribute-interface"}}},"required":["id","name","price"]},"test-module2-v1-entity-item":{"type":"object","description":"","properties":{"id":{"type":"integer"},"name":{"type":"string"},"custom_attributes":{"type":"array","description":"Custom attributes values.","items":{"$ref":"#/definitions/framework-attribute-interface"}}},"required":["id","name"]},"test-module3-v1-entity-parameter":{"type":"object","description":"","properties":{"name":{"type":"string","description":"$name"},"value":{"type":"string","description":"$value"},"custom_attributes":{"type":"array","description":"Custom attributes values.","items":{"$ref":"#/definitions/framework-attribute-interface"}}},"required":["name","value"]},"test-module3-v1-entity-wrapped-error-parameter":{"type":"object","description":"","properties":{"field_name":{"type":"string","description":"$name"},"value":{"type":"string","description":"$value"},"custom_attributes":{"type":"array","description":"Custom attributes values.","items":{"$ref":"#/definitions/framework-attribute-interface"}}},"required":["field_name","value"]},"test-module4-v1-entity-data-object-response":{"type":"object","description":"","properties":{"entity_id":{"type":"integer"},"name":{"type":"string"},"custom_attributes":{"type":"array","description":"Custom attributes values.","items":{"$ref":"#/definitions/framework-attribute-interface"}}},"required":["entity_id","name"]},"test-module4-v1-entity-data-object-request":{"type":"object","description":"","properties":{"name":{"type":"string"},"entity_id":{"type":"integer"},"custom_attributes":{"type":"array","description":"Custom attributes values.","items":{"$ref":"#/definitions/framework-attribute-interface"}}},"required":["name"]},"test-module4-v1-entity-nested-data-object-request":{"type":"object","description":"","properties":{"details":{"$ref":"#/definitions/test-module4-v1-entity-data-object-request"},"custom_attributes":{"type":"array","description":"Custom attributes values.","items":{"$ref":"#/definitions/framework-attribute-interface"}}},"required":["details"]},"test-module4-v1-entity-extensible-request-interface":{"type":"object","description":"","properties":{"name":{"type":"string"},"entity_id":{"type":"integer"}},"required":["name"]},"test-module5-v1-entity-all-soap-and-rest":{"type":"object","description":"Some Data Object short description. Data Object long multi line description.","properties":{"entity_id":{"type":"integer","description":"Item ID"},"name":{"type":"string","description":"Item name"},"enabled":{"type":"boolean","description":"If entity is enabled"},"orders":{"type":"boolean","description":"If current entity has a property defined"},"custom_attributes":{"type":"array","description":"Custom attributes values.","items":{"$ref":"#/definitions/framework-attribute-interface"}}},"required":["entity_id","enabled","orders"]},"test-module5-v2-entity-all-soap-and-rest":{"type":"object","description":"Some Data Object short description. Data Object long multi line description.","properties":{"price":{"type":"integer"}},"required":["price"]},"test-module-ms-cdata-item-interface":{"type":"object","description":"","properties":{"item_id":{"type":"integer"},"name":{"type":"string"}},"required":["item_id","name"]}}} \ No newline at end of file diff --git a/src/Magento/FunctionalTestingFramework/Util/MetadataGenerator/Swagger/views/definition.mustache b/src/Magento/FunctionalTestingFramework/Util/MetadataGenerator/Swagger/views/definition.mustache deleted file mode 100644 index e61212219..000000000 --- a/src/Magento/FunctionalTestingFramework/Util/MetadataGenerator/Swagger/views/definition.mustache +++ /dev/null @@ -1,14 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ ---> - -<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../DataGenerator/etc/dataOperation.xsd"> - <operation name="{{ operationName }}" dataType="{{ operationDataType }}" type="{{ operationType }}"> - {{> field2 }} - {{> array1 }} - </operation> -</config> \ No newline at end of file diff --git a/src/Magento/FunctionalTestingFramework/Util/MetadataGenerator/Swagger/views/operation.mustache b/src/Magento/FunctionalTestingFramework/Util/MetadataGenerator/Swagger/views/operation.mustache deleted file mode 100644 index 5114f233a..000000000 --- a/src/Magento/FunctionalTestingFramework/Util/MetadataGenerator/Swagger/views/operation.mustache +++ /dev/null @@ -1,16 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ ---> - -<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../DataGenerator/etc/dataOperation.xsd"> - <operation name="{{ operationName }}" dataType="{{ operationDataType }}" type="{{ operationType }}" auth="{{ auth }}" url="{{ operationUrl }}" method="{{ method }}"> - <contentType>application/json</contentType> - {{> field }} - {{> param }} - {{> array }} - </operation> -</config> \ No newline at end of file diff --git a/src/Magento/FunctionalTestingFramework/Util/MetadataGenerator/Swagger/views/partials/array1.mustache b/src/Magento/FunctionalTestingFramework/Util/MetadataGenerator/Swagger/views/partials/array1.mustache deleted file mode 100644 index 3669c78d0..000000000 --- a/src/Magento/FunctionalTestingFramework/Util/MetadataGenerator/Swagger/views/partials/array1.mustache +++ /dev/null @@ -1,12 +0,0 @@ -{{! -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -}} -{{# arrays1 }} -<array key="{{ arrayKey }}"> - {{> value }} - {{> arrays2 }} -</array> -{{/ arrays1 }} diff --git a/src/Magento/FunctionalTestingFramework/Util/MetadataGenerator/Swagger/views/partials/array2.mustache b/src/Magento/FunctionalTestingFramework/Util/MetadataGenerator/Swagger/views/partials/array2.mustache deleted file mode 100644 index 072b430fb..000000000 --- a/src/Magento/FunctionalTestingFramework/Util/MetadataGenerator/Swagger/views/partials/array2.mustache +++ /dev/null @@ -1,12 +0,0 @@ -{{! -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -}} -{{# arrays2 }} -<array key="{{ arrayKey }}"> - {{> value }} - {{> arrays3 }} -</array> -{{/ arrays2 }} diff --git a/src/Magento/FunctionalTestingFramework/Util/MetadataGenerator/Swagger/views/partials/array3.mustache b/src/Magento/FunctionalTestingFramework/Util/MetadataGenerator/Swagger/views/partials/array3.mustache deleted file mode 100644 index 401dbbcbd..000000000 --- a/src/Magento/FunctionalTestingFramework/Util/MetadataGenerator/Swagger/views/partials/array3.mustache +++ /dev/null @@ -1,12 +0,0 @@ -{{! -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -}} -{{! No array is nested more than 2 level in Magento2 swagger spec. }} -{{# arrays3 }} -<array key="{{ arrayKey }}"> - {{> value }} -</array> -{{/ arrays3 }} diff --git a/src/Magento/FunctionalTestingFramework/Util/MetadataGenerator/Swagger/views/partials/field.mustache b/src/Magento/FunctionalTestingFramework/Util/MetadataGenerator/Swagger/views/partials/field.mustache deleted file mode 100644 index 29a822d05..000000000 --- a/src/Magento/FunctionalTestingFramework/Util/MetadataGenerator/Swagger/views/partials/field.mustache +++ /dev/null @@ -1,9 +0,0 @@ -{{! -/** - * Copyright ? Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -}} -{{# fields }} -<field key="{{ fieldName }}" required="{{ isRequired }}">{{ fieldType }}</field> -{{/ fields }} diff --git a/src/Magento/FunctionalTestingFramework/Util/MetadataGenerator/Swagger/views/partials/field2.mustache b/src/Magento/FunctionalTestingFramework/Util/MetadataGenerator/Swagger/views/partials/field2.mustache deleted file mode 100644 index bf4f6c2a5..000000000 --- a/src/Magento/FunctionalTestingFramework/Util/MetadataGenerator/Swagger/views/partials/field2.mustache +++ /dev/null @@ -1,9 +0,0 @@ -{{! -/** - * Copyright ? Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -}} -{{# fields }} -<field key="{{ fieldName }}">{{ fieldType }}</field> -{{/ fields }} diff --git a/src/Magento/FunctionalTestingFramework/Util/MetadataGenerator/Swagger/views/partials/param.mustache b/src/Magento/FunctionalTestingFramework/Util/MetadataGenerator/Swagger/views/partials/param.mustache deleted file mode 100644 index be5f4a3ef..000000000 --- a/src/Magento/FunctionalTestingFramework/Util/MetadataGenerator/Swagger/views/partials/param.mustache +++ /dev/null @@ -1,9 +0,0 @@ -{{! -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -}} -{{# params }} -<param key="{{ paramName }}">{{ paramType }}</param> -{{/ params }} \ No newline at end of file diff --git a/src/Magento/FunctionalTestingFramework/Util/MetadataGenerator/Swagger/views/partials/value.mustache b/src/Magento/FunctionalTestingFramework/Util/MetadataGenerator/Swagger/views/partials/value.mustache deleted file mode 100644 index def0c03ff..000000000 --- a/src/Magento/FunctionalTestingFramework/Util/MetadataGenerator/Swagger/views/partials/value.mustache +++ /dev/null @@ -1,9 +0,0 @@ -{{! -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -}} -{{# values }} -<value>{{ value }}</value> -{{/ values }} diff --git a/src/Magento/FunctionalTestingFramework/Util/MftfGlobals.php b/src/Magento/FunctionalTestingFramework/Util/MftfGlobals.php new file mode 100644 index 000000000..cb0f4f5ae --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Util/MftfGlobals.php @@ -0,0 +1,163 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\FunctionalTestingFramework\Util; + +use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; +use Magento\FunctionalTestingFramework\Util\Path\UrlFormatter; + +/** + * MFTF Globals + */ +class MftfGlobals +{ + /** + * Magento Base URL + * + * @var string null + */ + private static $baseUrl = null; + + /** + * Magento Backend Base URL + * + * @var string null + */ + private static $backendBaseUrl = null; + + /** + * Magento Web API Base URL + * + * @var string null + */ + private static $webApiBaseUrl = null; + + /** + * Returns Magento Base URL + * + * @param boolean $withTrailingSeparator + * @return string + * @throws TestFrameworkException + */ + public static function getBaseUrl($withTrailingSeparator = true) + { + if (!self::$baseUrl) { + try { + $url = getenv('MAGENTO_BASE_URL'); + if ($url) { + self::$baseUrl = UrlFormatter::format($url, false); + } + } catch (TestFrameworkException $e) { + } + } + + if (self::$baseUrl) { + return UrlFormatter::format(self::$baseUrl, $withTrailingSeparator); + } + + throw new TestFrameworkException( + 'Unable to retrieve Magento Base URL. Please check .env and set:' + . PHP_EOL + . '"MAGENTO_BASE_URL"' + ); + } + + /** + * Return Magento Backend Base URL + * + * @param boolean $withTrailingSeparator + * @return string + * @throws TestFrameworkException + */ + public static function getBackendBaseUrl($withTrailingSeparator = true) + { + if (!self::$backendBaseUrl) { + try { + $backendName = getenv('MAGENTO_BACKEND_NAME'); + $bUrl = getenv('MAGENTO_BACKEND_BASE_URL'); + if ($bUrl && $backendName) { + self::$backendBaseUrl = UrlFormatter::format( + UrlFormatter::format($bUrl) . $backendName, + false + ); + } else { + $baseUrl = getenv('MAGENTO_BASE_URL'); + if ($baseUrl && $backendName) { + self::$backendBaseUrl = UrlFormatter::format( + UrlFormatter::format($baseUrl) . $backendName, + false + ); + } + } + } catch (TestFrameworkException $e) { + } + } + + if (self::$backendBaseUrl) { + return UrlFormatter::format(self::$backendBaseUrl, $withTrailingSeparator); + } + + throw new TestFrameworkException( + 'Unable to retrieve Magento Backend Base URL. Please check .env and set either:' + . PHP_EOL + . '"MAGENTO_BASE_URL" and "MAGENTO_BACKEND_NAME"' + . PHP_EOL + . 'or' + . PHP_EOL + . '"MAGENTO_BACKEND_BASE_URL"' + ); + } + + /** + * Return Web API Base URL + * + * @param boolean $withTrailingSeparator + * @return string + * @throws TestFrameworkException + */ + public static function getWebApiBaseUrl($withTrailingSeparator = true) + { + if (!self::$webApiBaseUrl) { + try { + $webapiHost = getenv('MAGENTO_RESTAPI_SERVER_HOST'); + $webapiPort = getenv("MAGENTO_RESTAPI_SERVER_PORT"); + $webapiProtocol = getenv("MAGENTO_RESTAPI_SERVER_PROTOCOL"); + + if ($webapiHost && $webapiProtocol) { + $baseUrl = UrlFormatter::format( + sprintf('%s://%s', $webapiProtocol, $webapiHost), + false + ); + } elseif ($webapiHost) { + $baseUrl = UrlFormatter::format($webapiHost, false); + } + + if (!isset($baseUrl)) { + $baseUrl = MftfGlobals::getBaseUrl(false); + } + + if ($webapiPort) { + $baseUrl .= ':' . $webapiPort; + } + + self::$webApiBaseUrl = $baseUrl . '/rest'; + } catch (TestFrameworkException $e) { + } + } + if (self::$webApiBaseUrl) { + return UrlFormatter::format(self::$webApiBaseUrl, $withTrailingSeparator); + } + throw new TestFrameworkException( + 'Unable to retrieve Magento Web API Base URL. Please check .env and set either:' + . PHP_EOL + . '"MAGENTO_BASE_URL"' + . PHP_EOL + . 'or' + . PHP_EOL + . '"MAGENTO_RESTAPI_SERVER_HOST"' + ); + } +} diff --git a/src/Magento/FunctionalTestingFramework/Util/ModulePathExtractor.php b/src/Magento/FunctionalTestingFramework/Util/ModulePathExtractor.php index 80b556f9a..a9f51d226 100644 --- a/src/Magento/FunctionalTestingFramework/Util/ModulePathExtractor.php +++ b/src/Magento/FunctionalTestingFramework/Util/ModulePathExtractor.php @@ -72,7 +72,7 @@ public function getExtensionPath($path) private function splitKeyForParts($key) { $parts = explode(self::SPLIT_DELIMITER, $key); - return count($parts) == 2 ? $parts : []; + return count($parts) === 2 ? $parts : []; } /** @@ -90,7 +90,7 @@ private function extractKeyByPath($path) } foreach ($this->testModulePaths as $key => $value) { - if ($value == $shortenedPath) { + if (substr($path, 0, strlen($value)) === $value) { return $key; } } diff --git a/src/Magento/FunctionalTestingFramework/Util/ModuleResolver.php b/src/Magento/FunctionalTestingFramework/Util/ModuleResolver.php index d776603d1..b330ac84b 100644 --- a/src/Magento/FunctionalTestingFramework/Util/ModuleResolver.php +++ b/src/Magento/FunctionalTestingFramework/Util/ModuleResolver.php @@ -7,9 +7,14 @@ namespace Magento\FunctionalTestingFramework\Util; use Magento\FunctionalTestingFramework\Config\MftfApplicationConfig; +use Magento\FunctionalTestingFramework\Exceptions\FastFailException; use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; +use Magento\FunctionalTestingFramework\Util\ModuleResolver\ModuleResolverService; use Magento\FunctionalTestingFramework\Util\Logger\LoggingUtil; -use Symfony\Component\HttpFoundation\Response; +use Magento\FunctionalTestingFramework\Util\Path\FilePathFormatter; +use Magento\FunctionalTestingFramework\Util\Path\UrlFormatter; +use \Magento\FunctionalTestingFramework\Util\ModuleResolver\AlphabeticSequenceSorter; +use \Magento\FunctionalTestingFramework\Util\ModuleResolver\SequenceSorterInterface; /** * Class ModuleResolver, resolve module path based on enabled modules of target Magento instance. @@ -21,9 +26,9 @@ class ModuleResolver { /** - * Environment field name for module whitelist. + * Environment field name for module allowlist. */ - const MODULE_WHITELIST = 'MODULE_WHITELIST'; + const MODULE_ALLOWLIST = 'MODULE_ALLOWLIST'; /** * Environment field name for custom module paths. @@ -52,12 +57,6 @@ class ModuleResolver . 'tests' . DIRECTORY_SEPARATOR . 'functional'; - const DEPRECATED_DEV_TESTS = DIRECTORY_SEPARATOR - . self:: DEV_TESTS - . DIRECTORY_SEPARATOR - . "Magento" - . DIRECTORY_SEPARATOR - . "FunctionalTest"; /** * Enabled modules. @@ -134,31 +133,10 @@ class ModuleResolver * * @var array */ - protected $moduleBlacklist = [ + protected $moduleBlocklist = [ 'SampleTests', 'SampleTemplates' ]; - /** - * Registered module list in magento system under test - * - * @var array - */ - private $registeredModuleList = []; - - /** - * Composer json based test module paths - * - * @var array - */ - private $composerJsonModulePaths = null; - - /** - * Composer installed test module paths - * - * @var array - */ - private $composerInstalledModulePaths = null; - /** * Get ModuleResolver instance. * @@ -178,9 +156,12 @@ public static function getInstance() private function __construct() { $objectManager = \Magento\FunctionalTestingFramework\ObjectManagerFactory::getObjectManager(); - $this->sequenceSorter = $objectManager->get( - \Magento\FunctionalTestingFramework\Util\ModuleResolver\SequenceSorterInterface::class - ); + + if (MftfApplicationConfig::getConfig()->getPhase() === MftfApplicationConfig::UNIT_TEST_PHASE) { + $this->sequenceSorter = $objectManager->get(AlphabeticSequenceSorter::class); + } else { + $this->sequenceSorter = $objectManager->get(SequenceSorterInterface::class); + } } /** @@ -188,6 +169,7 @@ private function __construct() * * @return array * @throws TestFrameworkException + * @throws FastFailException */ public function getEnabledModules() { @@ -195,13 +177,13 @@ public function getEnabledModules() return $this->enabledModules; } - if (MftfApplicationConfig::getConfig()->getPhase() == MftfApplicationConfig::GENERATION_PHASE) { + if (MftfApplicationConfig::getConfig()->getPhase() === MftfApplicationConfig::GENERATION_PHASE) { $this->printMagentoVersionInfo(); } - $token = $this->getAdminToken(); + $token = ModuleResolverService::getInstance()->getAdminToken(); - $url = ConfigSanitizerUtil::sanitizeUrl(getenv('MAGENTO_BASE_URL')) . $this->moduleUrl; + $url = UrlFormatter::format(getenv('MAGENTO_BASE_URL')) . $this->moduleUrl; $headers = [ 'Authorization: Bearer ' . $token, @@ -221,7 +203,7 @@ public function getEnabledModules() "MAGENTO_ADMIN_USERNAME" => getenv("MAGENTO_ADMIN_USERNAME"), "MAGENTO_ADMIN_PASSWORD" => getenv("MAGENTO_ADMIN_PASSWORD"), ]; - throw new TestFrameworkException($message, $context); + throw new FastFailException($message, $context); } $this->enabledModules = json_decode($response); @@ -234,6 +216,8 @@ public function getEnabledModules() * * @param boolean $verbosePath * @return array + * @throws TestFrameworkException + * @throws FastFailException */ public function getModulesPath($verbosePath = false) { @@ -246,7 +230,7 @@ public function getModulesPath($verbosePath = false) } // Find test modules paths by searching patterns (Test/Mftf, etc) - $allModulePaths = $this->aggregateTestModulePaths(); + $allModulePaths = ModuleResolverService::getInstance()->aggregateTestModulePaths(); // Find test modules paths by searching test composer.json files $composerBasedModulePaths = $this->aggregateTestModulePathsFromComposerJson(); @@ -269,7 +253,7 @@ public function getModulesPath($verbosePath = false) return $this->enabledModulePaths; } - $enabledModules = array_merge($this->getEnabledModules(), $this->getModuleWhitelist()); + $enabledModules = array_merge($this->getEnabledModules(), $this->getModuleAllowlist()); $enabledDirectoryPaths = $this->flipAndFilterModulePathsArray($allModulePaths, $enabledModules); $this->enabledModulePaths = $this->applyCustomModuleMethods($enabledDirectoryPaths); @@ -288,142 +272,34 @@ public function sortFilesByModuleSequence(array $files) } /** - * Return an array of module whitelist that not exist in target Magento instance. + * Return an array of module allowlist that not exist in target Magento instance. * * @return array */ - protected function getModuleWhitelist() + protected function getModuleAllowlist() { - $moduleWhitelist = getenv(self::MODULE_WHITELIST); + $moduleAllowlist = getenv(self::MODULE_ALLOWLIST); - if (empty($moduleWhitelist)) { + if (empty($moduleAllowlist)) { return []; } - return array_map('trim', explode(',', $moduleWhitelist)); - } - - /** - * Retrieves all module directories which might contain pertinent test code. - * - * @return array - * @throws TestFrameworkException - */ - private function aggregateTestModulePaths() - { - $allModulePaths = []; - - // Define the Module paths from magento bp - $magentoBaseCodePath = MAGENTO_BP; - - // Define the Module paths from default TESTS_MODULE_PATH - $modulePath = defined('TESTS_MODULE_PATH') ? TESTS_MODULE_PATH : TESTS_BP; - $modulePath = rtrim($modulePath, DIRECTORY_SEPARATOR); - - $vendorCodePath = DIRECTORY_SEPARATOR . self::VENDOR; - $appCodePath = DIRECTORY_SEPARATOR . self::APP_CODE; - - $codePathsToPattern = [ - $modulePath => '', - $magentoBaseCodePath . $vendorCodePath => self::TEST_MFTF_PATTERN, - $magentoBaseCodePath . $appCodePath => self::TEST_MFTF_PATTERN, - $magentoBaseCodePath . self::DEPRECATED_DEV_TESTS => '' - ]; - - foreach ($codePathsToPattern as $codePath => $pattern) { - $allModulePaths = array_merge_recursive($allModulePaths, $this->globRelevantPaths($codePath, $pattern)); - } - - return $allModulePaths; - } - - /** - * Function which takes a code path and a pattern and determines if there are any matching subdir paths. Matches - * are returned as an associative array keyed by basename (the last dir excluding pattern) to an array containing - * the matching path. - * - * @param string $testPath - * @param string $pattern - * @return array - */ - private function globRelevantPaths($testPath, $pattern) - { - $modulePaths = []; - $relevantPaths = []; - - if (file_exists($testPath)) { - $relevantPaths = $this->globRelevantWrapper($testPath, $pattern); - } - - foreach ($relevantPaths as $codePath) { - // Reduce magento/app/code/Magento/AdminGws/<pattern> to magento/app/code/Magento/AdminGws to read symlink - // Symlinks must be resolved otherwise they will not match Magento's filepath to the module - $potentialSymlink = str_replace(DIRECTORY_SEPARATOR . $pattern, "", $codePath); - if (is_link($potentialSymlink)) { - $codePath = realpath($potentialSymlink) . DIRECTORY_SEPARATOR . $pattern; - } - $mainModName = basename(str_replace($pattern, '', $codePath)); - $modulePaths[$codePath] = [$mainModName]; - - if (MftfApplicationConfig::getConfig()->verboseEnabled()) { - LoggingUtil::getInstance()->getLogger(ModuleResolver::class)->debug( - "including module", - ['module' => $mainModName, 'path' => $codePath] - ); - } - } - - /* TODO uncomment this to show deprecation warning when we ready to fully deliver test packaging feature - if (strpos($testPath, self::DEPRECATED_DEV_TESTS) !== false && !empty($modulePaths)) { - $deprecatedPath = ltrim(self::DEPRECATED_DEV_TESTS, DIRECTORY_SEPARATOR); - $suggestedPath = self::DEV_TESTS . DIRECTORY_SEPARATOR . 'Magento'; - $message = "DEPRECATION: Found MFTF test modules in the deprecated path: $deprecatedPath." - . " Move these test modules to $suggestedPath."; - - if (MftfApplicationConfig::getConfig()->verboseEnabled()) { - LoggingUtil::getInstance()->getLogger(ModuleResolver::class)->warning($message); - } - // Suppress print during unit testing - if (MftfApplicationConfig::getConfig()->getPhase() !== MftfApplicationConfig::UNIT_TEST_PHASE) { - print ("\n$message\n\n"); - } - } - */ - return $modulePaths; - } - - /** - * Glob wrapper for globRelevantPaths function - * - * @param string $testPath - * @param string $pattern - * @return array - */ - private static function globRelevantWrapper($testPath, $pattern) - { - if ($pattern == "") { - return glob($testPath . '*' . DIRECTORY_SEPARATOR . '*' . $pattern); - } - $subDirectory = "*" . DIRECTORY_SEPARATOR; - $directories = glob($testPath . $subDirectory . $pattern, GLOB_ONLYDIR); - foreach (glob($testPath . $subDirectory, GLOB_ONLYDIR) as $dir) { - $directories = array_merge_recursive($directories, self::globRelevantWrapper($dir, $pattern)); - } - return $directories; + return array_map('trim', explode(',', $moduleAllowlist)); } /** * Aggregate all code paths with test module composer json files * * @return array + * @throws TestFrameworkException */ private function aggregateTestModulePathsFromComposerJson() { // Define the module paths - $magentoBaseCodePath = MAGENTO_BP; + $magentoBaseCodePath = FilePathFormatter::format(MAGENTO_BP, false); // Define the module paths from default TESTS_MODULE_PATH $modulePath = defined('TESTS_MODULE_PATH') ? TESTS_MODULE_PATH : TESTS_BP; - $modulePath = rtrim($modulePath, DIRECTORY_SEPARATOR); + $modulePath = FilePathFormatter::format($modulePath, false); $searchCodePaths = [ $magentoBaseCodePath . DIRECTORY_SEPARATOR . self::DEV_TESTS, @@ -434,30 +310,9 @@ private function aggregateTestModulePathsFromComposerJson() $searchCodePaths[] = $modulePath; } - return $this->getComposerJsonTestModulePaths($searchCodePaths); + return ModuleResolverService::getInstance()->getComposerJsonTestModulePaths($searchCodePaths); } - /** - * Retrieve all module code paths that have test module composer json files - * - * @param array $codePaths - * @return array - */ - private function getComposerJsonTestModulePaths($codePaths) - { - if (null !== $this->composerJsonModulePaths) { - return $this->composerJsonModulePaths; - } - try { - $this->composerJsonModulePaths = []; - $resolver = new ComposerModuleResolver(); - $this->composerJsonModulePaths = $resolver->getTestModulesFromPaths($codePaths); - } catch (TestFrameworkException $e) { - } - - return $this->composerJsonModulePaths; - } - /** * Aggregate all code paths with composer installed test modules * @@ -469,28 +324,7 @@ private function aggregateTestModulePathsFromComposerInstaller() $magentoBaseCodePath = MAGENTO_BP; $composerFile = $magentoBaseCodePath . DIRECTORY_SEPARATOR . 'composer.json'; - return $this->getComposerInstalledTestModulePaths($composerFile); - } - - /** - * Retrieve composer installed test module code paths - * - * @params string $composerFile - * @return array - */ - private function getComposerInstalledTestModulePaths($composerFile) - { - if (null !== $this->composerInstalledModulePaths) { - return $this->composerInstalledModulePaths; - } - try { - $this->composerInstalledModulePaths = []; - $resolver = new ComposerModuleResolver(); - $this->composerInstalledModulePaths = $resolver->getComposerInstalledTestModules($composerFile); - } catch (TestFrameworkException $e) { - } - - return $this->composerInstalledModulePaths; + return ModuleResolverService::getInstance()->getComposerInstalledTestModulePaths($composerFile); } /** @@ -507,8 +341,8 @@ private function flipAndFilterModulePathsArray($objectArray, $filterArray) // Filter array by enabled modules foreach ($objectArray as $path => $modules) { if (!array_diff($modules, $filterArray) - || (count($modules) == 1 && isset($this->knownDirectories[$modules[0]]))) { - if (count($modules) == 1) { + || (count($modules) === 1 && isset($this->knownDirectories[$modules[0]]))) { + if (count($modules) === 1) { $oneToOneArray[$path] = $modules[0]; } else { $oneToManyArray[$path] = $modules; @@ -553,10 +387,10 @@ private function flipAndSortModulePathsArray($objectArray, $sort, $inFlippedArra foreach ($objectArray as $path => $modules) { if (is_array($modules) && count($modules) > 1) { // The "one path => many module names" case is designed to be strictly used when it's - // impossible to write tests in dedicated modules. Due to performance consideration and there - // is no real usage of this currently, we will use the first module name for the path. - // TODO: consider saving all module names if this information is needed in the future. - $module = $modules[0]; + // impossible to write tests in dedicated modules. + // For now we will set module name based on path. + // TODO: Consider saving all module names if this information is needed in the future. + $module = $this->findVendorAndModuleNameFromPath($path); } elseif (is_array($modules)) { if (strpos($modules[0], '_') === false) { $module = $this->findVendorNameFromPath($path) . '_' . $modules[0]; @@ -598,7 +432,7 @@ private function setArrayValueWithLogging($inArray, $index, $value) } else { $warnMsg = 'Path: ' . $value . ' is ignored by ModuleResolver. ' . PHP_EOL . 'Path: '; $warnMsg .= $inArray[$index] . ' is set for Module: ' . $index . PHP_EOL; - LoggingUtil::getInstance()->getLogger(ModuleResolver::class)->warn($warnMsg); + LoggingUtil::getInstance()->getLogger(ModuleResolver::class)->warning($warnMsg); } return $outArray; } @@ -631,7 +465,7 @@ private function mergeModulePaths($oneToOneArray, $oneToManyArray) */ private function normalizeModuleNames($codePaths) { - $allComponents = $this->getRegisteredModuleList(); + $allComponents = ModuleResolverService::getInstance()->getRegisteredModuleList(); if (empty($allComponents)) { return $codePaths; } @@ -677,7 +511,7 @@ private function printMagentoVersionInfo() if (MftfApplicationConfig::getConfig()->forceGenerateEnabled()) { return; } - $url = ConfigSanitizerUtil::sanitizeUrl(getenv('MAGENTO_BASE_URL')) . $this->versionUrl; + $url = UrlFormatter::format(getenv('MAGENTO_BASE_URL')) . $this->versionUrl; LoggingUtil::getInstance()->getLogger(ModuleResolver::class)->info( "Fetching version information.", ['url' => $url] @@ -698,66 +532,6 @@ private function printMagentoVersionInfo() ); } - /** - * Get the API token for admin. - * - * @return string|boolean - */ - protected function getAdminToken() - { - $login = $_ENV['MAGENTO_ADMIN_USERNAME'] ?? null; - $password = $_ENV['MAGENTO_ADMIN_PASSWORD'] ?? null; - if (!$login || !$password || !$this->getBackendUrl()) { - $message = "Cannot retrieve API token without credentials and base url, please fill out .env."; - $context = [ - "MAGENTO_BASE_URL" => getenv("MAGENTO_BASE_URL"), - "MAGENTO_BACKEND_BASE_URL" => getenv("MAGENTO_BACKEND_BASE_URL"), - "MAGENTO_ADMIN_USERNAME" => getenv("MAGENTO_ADMIN_USERNAME"), - "MAGENTO_ADMIN_PASSWORD" => getenv("MAGENTO_ADMIN_PASSWORD"), - ]; - throw new TestFrameworkException($message, $context); - } - - $url = ConfigSanitizerUtil::sanitizeUrl($this->getBackendUrl()) . $this->adminTokenUrl; - $data = [ - 'username' => $login, - 'password' => $password - ]; - $headers = [ - 'Content-Type: application/json', - ]; - - $ch = curl_init($url); - curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST"); - curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data)); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); - curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); - curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); - - $response = curl_exec($ch); - $responseCode = curl_getinfo($ch)['http_code']; - - if ($responseCode !== 200) { - if ($responseCode == 0) { - $details = "Could not find Magento Backend Instance at MAGENTO_BACKEND_BASE_URL or MAGENTO_BASE_URL"; - } else { - $details = $responseCode . " " . Response::$statusTexts[$responseCode]; - } - - $message = "Could not retrieve API token from Magento Instance ({$details})"; - $context = [ - "tokenUrl" => $url, - "responseCode" => $responseCode, - "MAGENTO_ADMIN_USERNAME" => getenv("MAGENTO_ADMIN_USERNAME"), - "MAGENTO_ADMIN_PASSWORD" => getenv("MAGENTO_ADMIN_PASSWORD"), - ]; - throw new TestFrameworkException($message, $context); - } - - return json_decode($response); - } - /** * A wrapping method for any custom logic which needs to be applied to the module list * @@ -766,8 +540,8 @@ protected function getAdminToken() */ protected function applyCustomModuleMethods($modulesPath) { - $modulePathsResult = $this->removeBlacklistModules($modulesPath); - $customModulePaths = $this->getCustomModulePaths(); + $modulePathsResult = $this->removeBlocklistModules($modulesPath); + $customModulePaths = ModuleResolverService::getInstance()->getCustomModulePaths(); array_map(function ($key, $value) { LoggingUtil::getInstance()->getLogger(ModuleResolver::class)->info( @@ -783,17 +557,17 @@ protected function applyCustomModuleMethods($modulesPath) } /** - * Remove blacklist modules from input module paths. + * Remove blocklist modules from input module paths. * * @param array $modulePaths * @return string[] */ - private function removeBlacklistModules($modulePaths) + private function removeBlocklistModules($modulePaths) { $modulePathsResult = $modulePaths; foreach ($modulePathsResult as $moduleName => $modulePath) { - // Remove module if it is in blacklist - if (in_array($moduleName, $this->getModuleBlacklist())) { + // Remove module if it is in blocklist + if (in_array($moduleName, $this->getModuleBlocklist())) { unset($modulePathsResult[$moduleName]); LoggingUtil::getInstance()->getLogger(ModuleResolver::class)->info( "excluding module", @@ -806,87 +580,13 @@ private function removeBlacklistModules($modulePaths) } /** - * Returns an array of custom module paths defined by the user - * - * @return string[] - */ - private function getCustomModulePaths() - { - $customModulePaths = []; - $paths = getenv(self::CUSTOM_MODULE_PATHS); - - if (!$paths) { - return $customModulePaths; - } - - foreach (explode(',', $paths) as $path) { - $customModulePaths = [$this->findVendorAndModuleNameFromPath(trim($path)) => $path]; - } - - return $customModulePaths; - } - - /** - * Getter for moduleBlacklist. + * Getter for moduleBlocklist. * * @return string[] */ - private function getModuleBlacklist() - { - return $this->moduleBlacklist; - } - - /** - * Calls Magento method for determining registered modules. - * - * @return string[] - */ - private function getRegisteredModuleList() - { - if (!empty($this->registeredModuleList)) { - return $this->registeredModuleList; - } - - if (array_key_exists('MAGENTO_BP', $_ENV)) { - $autoloadPath = realpath(MAGENTO_BP . "/app/autoload.php"); - if ($autoloadPath) { - require_once($autoloadPath); - } else { - throw new TestFrameworkException("Magento app/autoload.php not found with given MAGENTO_BP:" - . MAGENTO_BP); - } - } - - try { - $allComponents = []; - if (!class_exists(self::REGISTRAR_CLASS)) { - throw new TestFrameworkException("Magento Installation not found when loading registered modules.\n"); - } - $components = new \Magento\Framework\Component\ComponentRegistrar(); - foreach (self::PATHS as $componentType) { - $allComponents = array_merge($allComponents, $components->getPaths($componentType)); - } - array_walk($allComponents, function (&$value) { - // Magento stores component paths with unix DIRECTORY_SEPARATOR, need to stay uniform and convert - $value = realpath($value); - $value .= DIRECTORY_SEPARATOR . self::TEST_MFTF_PATTERN; - }); - return $allComponents; - } catch (TestFrameworkException $e) { - LoggingUtil::getInstance()->getLogger(ModuleResolver::class)->warning( - "$e" - ); - } - return []; - } - - /** - * Returns custom Backend URL if set, fallback to Magento Base URL - * @return string|null - */ - private function getBackendUrl() + private function getModuleBlocklist() { - return getenv('MAGENTO_BACKEND_BASE_URL') ?: getenv('MAGENTO_BASE_URL'); + return $this->moduleBlocklist; } /** diff --git a/src/Magento/FunctionalTestingFramework/Util/ModuleResolver/AlphabeticSequenceSorter.php b/src/Magento/FunctionalTestingFramework/Util/ModuleResolver/AlphabeticSequenceSorter.php new file mode 100644 index 000000000..67e909062 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Util/ModuleResolver/AlphabeticSequenceSorter.php @@ -0,0 +1,25 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\FunctionalTestingFramework\Util\ModuleResolver; + +/** + * Alphabetic sequence sorter. + */ +class AlphabeticSequenceSorter implements SequenceSorterInterface +{ + /** + * Sort files alphabetically. + * + * @param array $paths + * @return array + */ + public function sort(array $paths) + { + asort($paths); + return $paths; + } +} diff --git a/src/Magento/FunctionalTestingFramework/Util/ModuleResolver/ModuleResolverService.php b/src/Magento/FunctionalTestingFramework/Util/ModuleResolver/ModuleResolverService.php new file mode 100644 index 000000000..16d3e7d2b --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Util/ModuleResolver/ModuleResolverService.php @@ -0,0 +1,335 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\FunctionalTestingFramework\Util\ModuleResolver; + +use Magento\Framework\Component\ComponentRegistrar; +use Magento\FunctionalTestingFramework\Config\MftfApplicationConfig; +use Magento\FunctionalTestingFramework\DataTransport\Auth\WebApiAuth; +use Magento\FunctionalTestingFramework\Exceptions\FastFailException; +use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; +use Magento\FunctionalTestingFramework\Util\ComposerModuleResolver; +use Magento\FunctionalTestingFramework\Util\Logger\LoggingUtil; +use Magento\FunctionalTestingFramework\Util\ModuleResolver; +use Magento\FunctionalTestingFramework\Util\Path\FilePathFormatter; + +class ModuleResolverService +{ + /** + * Singleton ModuleResolverCreator Instance. + * + * @var ModuleResolverService + */ + private static $INSTANCE; + + /** + * Composer json based test module paths. + * + * @var array + */ + private $composerJsonModulePaths = null; + + /** + * Composer installed test module paths. + * + * @var array + */ + private $composerInstalledModulePaths = null; + + /** + * ModuleResolverService constructor. + */ + private function __construct() + { + } + + /** + * Get ModuleResolverCreator instance. + * + * @return ModuleResolverService + */ + public static function getInstance() + { + if (self::$INSTANCE === null) { + self::$INSTANCE = new ModuleResolverService(); + } + + return self::$INSTANCE; + } + + /** + * Calls Magento method for determining registered modules. + * + * @return string[] + * @throws TestFrameworkException + */ + public function getRegisteredModuleList(): array + { + if (!empty($this->registeredModuleList)) { + return $this->registeredModuleList; + } + + if (array_key_exists('MAGENTO_BP', $_ENV)) { + $autoloadPath = realpath(MAGENTO_BP . "/app/autoload.php"); + + if ($autoloadPath) { + require_once($autoloadPath); + } else { + throw new TestFrameworkException( + "Magento app/autoload.php not found with given MAGENTO_BP:" . MAGENTO_BP + ); + } + } + + try { + $allComponents = []; + + if (!class_exists(ModuleResolver::REGISTRAR_CLASS)) { + throw new TestFrameworkException("Magento Installation not found when loading registered modules.\n"); + } + + $components = new ComponentRegistrar(); + + foreach (ModuleResolver::PATHS as $componentType) { + $allComponents = array_merge($allComponents, $components->getPaths($componentType)); + } + + array_walk($allComponents, function (&$value) { + // Magento stores component paths with unix DIRECTORY_SEPARATOR, need to stay uniform and convert + $value = realpath($value); + $value .= DIRECTORY_SEPARATOR . ModuleResolver::TEST_MFTF_PATTERN; + }); + + return $allComponents; + } catch (TestFrameworkException $exception) { + LoggingUtil::getInstance()->getLogger(ModuleResolver::class)->warning("$exception"); + } + return []; + } + + /** + * Function which takes a code path and a pattern and determines if there are any matching subdir paths. Matches + * are returned as an associative array keyed by basename (the last dir excluding pattern) to an array containing + * the matching path. + * + * @param string $testPath + * @param string $pattern + * + * @return array + * @throws TestFrameworkException + */ + public function globRelevantPaths(string $testPath, string $pattern): array + { + $modulePaths = []; + $relevantPaths = []; + + if (file_exists($testPath)) { + $relevantPaths = $this->globRelevantWrapper($testPath, $pattern); + } + + foreach ($relevantPaths as $codePath) { + $potentialSymlink = str_replace(DIRECTORY_SEPARATOR . $pattern, "", $codePath); + + if (is_link($potentialSymlink)) { + $codePath = realpath($potentialSymlink) . DIRECTORY_SEPARATOR . $pattern; + } + + $mainModName = basename(str_replace($pattern, '', $codePath)); + $modulePaths[$codePath] = [$mainModName]; + + if (MftfApplicationConfig::getConfig()->verboseEnabled()) { + LoggingUtil::getInstance()->getLogger(ModuleResolver::class)->debug( + "including module", + ['module' => $mainModName, 'path' => $codePath] + ); + } + } + + return $modulePaths; + } + + /** + * Glob wrapper for globRelevantPaths function. + * + * @param string $testPath + * @param string $pattern + * + * @return array + */ + private static function globRelevantWrapper(string $testPath, string $pattern): array + { + if ($pattern === '') { + return glob($testPath . '*' . DIRECTORY_SEPARATOR . '*' . $pattern); + } + + $subDirectory = '*' . DIRECTORY_SEPARATOR; + $directories = glob($testPath . $subDirectory . $pattern, GLOB_ONLYDIR); + + foreach (glob($testPath . $subDirectory, GLOB_ONLYDIR) as $dir) { + $directories = array_merge_recursive($directories, self::globRelevantWrapper($dir, $pattern)); + } + + return $directories; + } + + /** + * Retrieve all module code paths that have test module composer json files. + * + * @param array $codePaths + * + * @return array + */ + public function getComposerJsonTestModulePaths(array $codePaths): array + { + if (null !== $this->composerJsonModulePaths) { + return $this->composerJsonModulePaths; + } + + try { + $this->composerJsonModulePaths = []; + $resolver = new ComposerModuleResolver(); + $this->composerJsonModulePaths = $resolver->getTestModulesFromPaths($codePaths); + } catch (TestFrameworkException $e) { + } + + return $this->composerJsonModulePaths; + } + + /** + * Retrieve composer installed test module code paths. + * + * @param string $composerFile + * + * @return array + */ + public function getComposerInstalledTestModulePaths(string $composerFile): array + { + if (null !== $this->composerInstalledModulePaths) { + return $this->composerInstalledModulePaths; + } + + try { + $this->composerInstalledModulePaths = []; + $resolver = new ComposerModuleResolver(); + $this->composerInstalledModulePaths = $resolver->getComposerInstalledTestModules($composerFile); + } catch (TestFrameworkException $e) { + } + + return $this->composerInstalledModulePaths; + } + + /** + * Retrieves all module directories which might contain pertinent test code. + * + * @return array + * @throws TestFrameworkException + */ + public function aggregateTestModulePaths(): array + { + $allModulePaths = []; + + // Define the Module paths from magento bp + $magentoBaseCodePath = FilePathFormatter::format(MAGENTO_BP, false); + + // Define the Module paths from default TESTS_MODULE_PATH + $modulePath = defined('TESTS_MODULE_PATH') ? TESTS_MODULE_PATH : TESTS_BP; + $modulePath = FilePathFormatter::format($modulePath, false); + + // If $modulePath is DEV_TESTS path, we don't need to search by pattern + if (strpos($modulePath, ModuleResolver::DEV_TESTS) === false) { + $codePathsToPattern[$modulePath] = ''; + } + + $vendorCodePath = DIRECTORY_SEPARATOR . ModuleResolver::VENDOR; + $codePathsToPattern[$magentoBaseCodePath . $vendorCodePath] = ModuleResolver::TEST_MFTF_PATTERN; + + $appCodePath = DIRECTORY_SEPARATOR . ModuleResolver::APP_CODE; + $codePathsToPattern[$magentoBaseCodePath . $appCodePath] = ModuleResolver::TEST_MFTF_PATTERN; + + foreach ($codePathsToPattern as $codePath => $pattern) { + $allModulePaths = array_merge_recursive($allModulePaths, $this->globRelevantPaths($codePath, $pattern)); + } + + return $allModulePaths; + } + + /** + * Returns an array of custom module paths defined by the user. + * + * @return string[] + */ + public function getCustomModulePaths(): array + { + $customModulePaths = []; + $paths = getenv(ModuleResolver::CUSTOM_MODULE_PATHS); + + if (!$paths) { + return $customModulePaths; + } + + foreach (explode(',', $paths) as $path) { + $customModulePaths[$this->findVendorAndModuleNameFromPath(trim($path))] = $path; + } + + return $customModulePaths; + } + + /** + * Find vendor and module name from path. + * + * @param string $path + * + * @return string + */ + private function findVendorAndModuleNameFromPath(string $path): string + { + $path = str_replace(DIRECTORY_SEPARATOR . ModuleResolver::TEST_MFTF_PATTERN, '', $path); + + return $this->findVendorNameFromPath($path) . '_' . basename($path); + } + + /** + * Find vendor name from path. + * + * @param string $path + * + * @return string + */ + private function findVendorNameFromPath(string $path): string + { + $possibleVendorName = 'UnknownVendor'; + $dirPaths = [ + ModuleResolver::VENDOR, + ModuleResolver::APP_CODE, + ModuleResolver::DEV_TESTS + ]; + + foreach ($dirPaths as $dirPath) { + $regex = "~.+\\/" . $dirPath . "\/(?<" . ModuleResolver::VENDOR . ">[^\/]+)\/.+~"; + $match = []; + preg_match($regex, $path, $match); + + if (isset($match[ModuleResolver::VENDOR])) { + $possibleVendorName = ucfirst($match[ModuleResolver::VENDOR]); + return $possibleVendorName; + } + } + + return $possibleVendorName; + } + + /** + * Get admin token. + * + * @return string + * @throws FastFailException + */ + public function getAdminToken(): string + { + return WebApiAuth::getAdminToken(); + } +} diff --git a/src/Magento/FunctionalTestingFramework/Util/Path/FilePathFormatter.php b/src/Magento/FunctionalTestingFramework/Util/Path/FilePathFormatter.php new file mode 100644 index 000000000..092e060b5 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Util/Path/FilePathFormatter.php @@ -0,0 +1,33 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\FunctionalTestingFramework\Util\Path; + +use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; + +class FilePathFormatter implements FormatterInterface +{ + /** + * Return formatted full file path from input string, or false on error. + * + * @param string $path + * @param boolean $withTrailingSeparator + * + * @return string + * @throws TestFrameworkException + */ + public static function format(string $path, bool $withTrailingSeparator = true): string + { + $validPath = realpath($path); + + if ($validPath) { + return $withTrailingSeparator ? $validPath . DIRECTORY_SEPARATOR : $validPath; + } + + throw new TestFrameworkException("Invalid or non-existing file: $path\n"); + } +} diff --git a/src/Magento/FunctionalTestingFramework/Util/Path/FormatterInterface.php b/src/Magento/FunctionalTestingFramework/Util/Path/FormatterInterface.php new file mode 100644 index 000000000..0da9fe6d8 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Util/Path/FormatterInterface.php @@ -0,0 +1,24 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\FunctionalTestingFramework\Util\Path; + +use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; + +interface FormatterInterface +{ + /** + * Return formatted path (file path, url, etc) from input string, or false on error. + * + * @param string $input + * @param boolean $withTrailingSeparator + * + * @return string + * @throws TestFrameworkException + */ + public static function format(string $input, bool $withTrailingSeparator = true): string; +} diff --git a/src/Magento/FunctionalTestingFramework/Util/Path/UrlFormatter.php b/src/Magento/FunctionalTestingFramework/Util/Path/UrlFormatter.php new file mode 100644 index 000000000..49f5e0e18 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Util/Path/UrlFormatter.php @@ -0,0 +1,120 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\FunctionalTestingFramework\Util\Path; + +use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; + +class UrlFormatter implements FormatterInterface +{ + /** + * Return formatted url path from input string. + * + * @param string $url + * @param boolean $withTrailingSeparator + * + * @return string + * @throws TestFrameworkException + */ + public static function format(string $url, bool $withTrailingSeparator = true): string + { + $sanitizedUrl = rtrim($url, '/'); + + // Remove all characters except letters, digits and $-_.+!*'(),{}|\\^~[]`<>#%";/?:@&= + $sanitizedUrl = filter_var($sanitizedUrl, FILTER_SANITIZE_URL); + + if (false === $sanitizedUrl) { + throw new TestFrameworkException("Invalid url: $url\n"); + } + + // Validate URL according to http://www.faqs.org/rfcs/rfc2396 + $validUrl = filter_var($sanitizedUrl, FILTER_VALIDATE_URL); + + if (false !== $validUrl) { + return $withTrailingSeparator ? $validUrl . '/' : $validUrl; + } + + // Validation might be failed due to missing URL scheme or host, attempt to build them and re-validate + $validUrl = filter_var(self::buildUrl($sanitizedUrl), FILTER_VALIDATE_URL); + + if (false !== $validUrl) { + return $withTrailingSeparator ? $validUrl . '/' : $validUrl; + } + + throw new TestFrameworkException("Invalid url: $url\n"); + } + + /** + * Try to build missing url scheme and host. + * + * @param string $url + * + * @return string + */ + private static function buildUrl(string $url): string + { + $urlParts = parse_url($url); + + if (!isset($urlParts['scheme'])) { + $urlParts['scheme'] = 'http'; + } + + if (!isset($urlParts['host'])) { + $urlParts['host'] = rtrim($urlParts['path'], '/'); + $urlParts['host'] = str_replace("//", '/', $urlParts['host']); + unset($urlParts['path']); + } + + if (isset($urlParts['path'])) { + $urlParts['path'] = rtrim($urlParts['path'], '/'); + } + + return str_replace("///", "//", self::merge($urlParts)); + } + + /** + * Returns url from $parts given, used with parse_url output for convenience. + * This only exists because of deprecation of http_build_url, which does the exact same thing as the code below. + * + * @param array $parts + * + * @return string + */ + private static function merge(array $parts): string + { + $get = function ($key) use ($parts) { + return $parts[$key] ?? ''; + }; + + $pass = $get('pass'); + $user = $get('user'); + $userinfo = $pass !== '' ? "$user:$pass" : $user; + $port = $get('port'); + $scheme = $get('scheme'); + $query = $get('query'); + $fragment = $get('fragment'); + $authority = ($userinfo !== '' ? "$userinfo@" : '') . $get('host') . ($port ? ":$port" : ''); + + return str_replace( + [ + '%scheme', + '%authority', + '%path', + '%query', + '%fragment' + ], + [ + strlen($scheme) ? "$scheme:" : '', + strlen($authority) ? "//$authority" : '', + $get('path'), + strlen($query) ? "?$query" : '', + strlen($fragment) ? "#$fragment" : '' + ], + '%scheme%authority%path%query%fragment' + ); + } +} diff --git a/src/Magento/FunctionalTestingFramework/Util/Script/ScriptUtil.php b/src/Magento/FunctionalTestingFramework/Util/Script/ScriptUtil.php new file mode 100644 index 000000000..e7fb1075f --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Util/Script/ScriptUtil.php @@ -0,0 +1,312 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\FunctionalTestingFramework\Util\Script; + +use Exception; +use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; +use Magento\FunctionalTestingFramework\Exceptions\TestReferenceException; +use Magento\FunctionalTestingFramework\Exceptions\XmlException; +use Magento\FunctionalTestingFramework\Page\Objects\ElementObject; +use Magento\FunctionalTestingFramework\Page\Objects\SectionObject; +use Magento\FunctionalTestingFramework\Util\Path\FilePathFormatter; +use Symfony\Component\Finder\Finder; +use Magento\FunctionalTestingFramework\Config\MftfApplicationConfig; +use Magento\FunctionalTestingFramework\Util\ModuleResolver; +use Magento\FunctionalTestingFramework\DataGenerator\Handlers\DataObjectHandler; +use Magento\FunctionalTestingFramework\Page\Handlers\PageObjectHandler; +use Magento\FunctionalTestingFramework\Page\Handlers\SectionObjectHandler; +use Magento\FunctionalTestingFramework\Test\Handlers\ActionGroupObjectHandler; +use Magento\FunctionalTestingFramework\Test\Handlers\TestObjectHandler; +use Magento\FunctionalTestingFramework\Test\Objects\ActionObject; +use Magento\FunctionalTestingFramework\Util\TestGenerator; + +/** + * ScriptUtil class that contains helper functions for static and upgrade scripts + * + * @package Magento\FunctionalTestingFramework\Util\Script + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class ScriptUtil +{ + const ACTIONGROUP_ARGUMENT_REGEX_PATTERN = '/<argument[^\/>]*name="([^"\']*)/'; + const ROOT_SUITE_DIR = 'tests/_suite'; + const DEV_TESTS_DIR = 'dev/tests/acceptance/'; + + /** + * Return all installed Magento module paths + * + * @return array + * @throws TestFrameworkException + */ + public function getAllModulePaths(): array + { + MftfApplicationConfig::create( + true, + MftfApplicationConfig::UNIT_TEST_PHASE, + false, + MftfApplicationConfig::LEVEL_DEFAULT, + true + ); + + return ModuleResolver::getInstance()->getModulesPath(); + } + + /** + * Prints out given errors to file, and returns summary result string + * @param array $errors + * @param string $filePath + * @param string $message + * @return string + */ + public function printErrorsToFile(array $errors, string $filePath, string $message): string + { + if (empty($errors)) { + return $message . ": No errors found."; + } + + $dirname = dirname($filePath); + if (!file_exists($dirname)) { + mkdir($dirname, 0777, true); + } + + $fileResource = fopen($filePath, 'w'); + + foreach ($errors as $test => $error) { + fwrite($fileResource, $error[0] . PHP_EOL); + } + + fclose($fileResource); + $errorCount = count($errors); + $output = $message . ": Errors found across {$errorCount} file(s). Error details output to {$filePath}"; + + return $output; + } + + /** + * Return all XML files for $scope in given module paths, empty array if no path is valid + * + * @param array $modulePaths + * @param string $scope + * @return Finder|array + */ + public function getModuleXmlFilesByScope(array $modulePaths, string $scope) + { + $found = false; + $scopePath = DIRECTORY_SEPARATOR . ucfirst($scope) . DIRECTORY_SEPARATOR; + $finder = new Finder(); + + foreach ($modulePaths as $modulePath) { + if (!realpath($modulePath . $scopePath)) { + continue; + } + $finder->files()->followLinks()->in($modulePath . $scopePath)->name("*.xml")->sortByName(); + $found = true; + } + return $found ? $finder->files() : []; + } + + /** + * Return suite XML files in TESTS_BP/ROOT_SUITE_DIR directory + * + * @return Finder|array + * @SuppressWarnings(PHPMD.CyclomaticComplexity) + */ + public function getRootSuiteXmlFiles() + { + $rootSuitePaths = []; + $defaultTestPath = null; + $devTestsPath = null; + + try { + $defaultTestPath = FilePathFormatter::format(TESTS_BP); + } catch (TestFrameworkException $e) { + } + + try { + $devTestsPath = FilePathFormatter::format(MAGENTO_BP) . self::DEV_TESTS_DIR; + } catch (TestFrameworkException $e) { + } + + if ($defaultTestPath) { + $rootSuitePaths[] = $defaultTestPath . self::ROOT_SUITE_DIR; + } + + if ($devTestsPath && realpath($devTestsPath) && $devTestsPath !== $defaultTestPath) { + $rootSuitePaths[] = $devTestsPath . self::ROOT_SUITE_DIR; + } + + $found = false; + $finder = new Finder(); + foreach ($rootSuitePaths as $rootSuitePath) { + if (!realpath($rootSuitePath)) { + continue; + } + $finder->files()->followLinks()->in($rootSuitePath)->name("*.xml"); + $found = true; + } + + return $found ? $finder->files() : []; + } + + /** + * Resolve entity reference in {{entity.field}} or {{entity.field('param')}} + * + * @param array $braceReferences + * @param string $contents + * @param boolean $resolveSectionElement + * @return array + * @throws XmlException + */ + public function resolveEntityReferences($braceReferences, $contents, $resolveSectionElement = false) + { + $entities = []; + foreach ($braceReferences as $reference) { + // trim `{{data.field}}` to `data` + preg_match('/{{([^.]+)/', $reference, $entityName); + // Double check that {{data.field}} isn't an argument for an ActionGroup + $entity = $this->findEntity($entityName[1]); + preg_match_all(self::ACTIONGROUP_ARGUMENT_REGEX_PATTERN, $contents, $possibleArgument); + if (array_search($entityName[1], $possibleArgument[1]) !== false) { + continue; + } + if ($entity !== null) { + $entities[$entity->getName()] = $entity; + if ($resolveSectionElement) { + if (get_class($entity) === SectionObject::class) { + // trim `{{data.field}}` to `field` + preg_match('/.([^.]+)}}/', $reference, $elementName); + /** @var ElementObject $element */ + $element = $entity->getElement($elementName[1]); + if ($element) { + $entities[$entity->getName() . '.' . $elementName[1]] = $element; + } + } + } + } + } + return $entities; + } + + /** + * Drill down into params in {{ref.params('string', $data.key$, entity.reference)}} to resolve entity reference + * + * @param array $braceReferences + * @param string $contents + * @param boolean $resolveSectionElement + * @return array + * @throws XmlException + * @SuppressWarnings(PHPMD.CyclomaticComplexity) + */ + public function resolveParametrizedReferences($braceReferences, $contents, $resolveSectionElement = false): array + { + $entities = []; + foreach ($braceReferences as $parameterizedReference) { + preg_match( + ActionObject::ACTION_ATTRIBUTE_VARIABLE_REGEX_PARAMETER, + $parameterizedReference, + $arguments + ); + $splitArguments = explode(',', ltrim(rtrim($arguments[0], ")"), "(")); + foreach ($splitArguments as $argument) { + // Do nothing for 'string' or $persisted.data$ + if (preg_match(ActionObject::STRING_PARAMETER_REGEX, $argument)) { + continue; + } elseif (preg_match(TestGenerator::PERSISTED_OBJECT_NOTATION_REGEX, $argument)) { + continue; + } + // trim `data.field` to `data` + preg_match('/([^.]+)/', $argument, $entityName); + // Double check that {{data.field}} isn't an argument for an ActionGroup + $entity = $this->findEntity($entityName[1]); + preg_match_all(self::ACTIONGROUP_ARGUMENT_REGEX_PATTERN, $contents, $possibleArgument); + if (array_search($entityName[1], $possibleArgument[1]) !== false) { + continue; + } + if ($entity !== null) { + $entities[$entity->getName()] = $entity; + if ($resolveSectionElement) { + if (get_class($entity) === SectionObject::class) { + // trim `data.field` to `field` + preg_match('/.([^.]+)/', $argument, $elementName); + /** @var ElementObject $element */ + $element = $entity->getElement($elementName[1]); + if ($element) { + $entities[$entity->getName() . '.' . $elementName[1]] = $element; + } + } + } + } + } + } + return $entities; + } + + /** + * Resolve entity by names + * + * @param array $references + * @return array + * @throws XmlException + */ + public function resolveEntityByNames(array $references): array + { + $entities = []; + foreach ($references as $reference) { + $entity = $this->findEntity($reference); + if ($entity !== null) { + $entities[$entity->getName()] = $entity; + } + } + return $entities; + } + + /** + * Attempts to find any MFTF entity by its name. Returns null if none are found + * + * @param string $name + * @return mixed + * @throws XmlException + * @throws Exception + */ + public function findEntity(string $name) + { + if ($name === '_ENV' || $name === '_CREDS') { + return null; + } + + if (DataObjectHandler::getInstance()->getObject($name)) { + return DataObjectHandler::getInstance()->getObject($name); + } elseif (PageObjectHandler::getInstance()->getObject($name)) { + return PageObjectHandler::getInstance()->getObject($name); + } elseif (SectionObjectHandler::getInstance()->getObject($name)) { + return SectionObjectHandler::getInstance()->getObject($name); + } elseif (ActionGroupObjectHandler::getInstance()->getObject($name)) { + return ActionGroupObjectHandler::getInstance()->getObject($name); + } + + try { + return TestObjectHandler::getInstance()->getObject($name); + } catch (TestReferenceException $e) { + } + return null; + } + + /** + * Return all XML files in given test name, empty array if no path is valid + * @param array $testNames + * @return array|Finder + */ + public function getModuleXmlFilesByTestNames(array $testNames) + { + $finder = new Finder(); + array_walk($testNames, function (&$value) { + $value = $value . ".xml"; + }); + $finder->files()->followLinks()->in(MAGENTO_BP)->name($testNames)->sortByName(); + + return $finder->files() ?? []; + } +} diff --git a/src/Magento/FunctionalTestingFramework/Util/Script/TestDependencyUtil.php b/src/Magento/FunctionalTestingFramework/Util/Script/TestDependencyUtil.php new file mode 100644 index 000000000..0fbf566a7 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Util/Script/TestDependencyUtil.php @@ -0,0 +1,184 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\FunctionalTestingFramework\Util\Script; + +/** + * TestDependencyUtil class that contains helper functions for static and upgrade scripts + * + * @package Magento\FunctionalTestingFramework\Util\Script + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class TestDependencyUtil +{ + /** + * Array of FullModuleName => [dependencies] + * @var array + */ + private $allDependencies; + + /** + * Transactional Array to keep track of what dependencies have already been extracted. + * @var array + */ + private $alreadyExtractedDependencies; + + /** + * Builds and returns array of FullModuleNae => composer name + * @param array $moduleNameToPath + * @return array + */ + public function buildModuleNameToComposerName(array $moduleNameToPath): array + { + $moduleNameToComposerName = []; + foreach ($moduleNameToPath as $moduleName => $path) { + $composerData = json_decode(file_get_contents($path . DIRECTORY_SEPARATOR . "composer.json")); + $moduleNameToComposerName[$moduleName] = $composerData->name; + } + return $moduleNameToComposerName; + } + + /** + * Builds and returns flattened dependency list based on composer dependencies + * @param array $moduleNameToPath + * @param array $moduleNameToComposerName + * @return array + */ + public function buildComposerDependencyList(array $moduleNameToPath, array $moduleNameToComposerName): array + { + $flattenedDependencies = []; + + foreach ($moduleNameToPath as $moduleName => $pathToModule) { + $composerData = json_decode( + file_get_contents($pathToModule . DIRECTORY_SEPARATOR . "composer.json"), + true + ); + $this->allDependencies[$moduleName] = $composerData['require']; + } + foreach ($this->allDependencies as $moduleName => $dependencies) { + $this->alreadyExtractedDependencies = []; + $flattenedDependencies[$moduleName] = $this->extractSubDependencies($moduleName, $moduleNameToComposerName); + } + return $flattenedDependencies; + } + + /** + * Recursive function to fetch dependencies of given dependency, and its child dependencies + * @param string $subDependencyName + * @param array $moduleNameToComposerName + * @return array + */ + private function extractSubDependencies(string $subDependencyName, array $moduleNameToComposerName): array + { + $flattenedArray = []; + + if (in_array($subDependencyName, $this->alreadyExtractedDependencies)) { + return $flattenedArray; + } + + if (isset($this->allDependencies[$subDependencyName])) { + $subDependencyArray = $this->allDependencies[$subDependencyName]; + $flattenedArray = array_merge($flattenedArray, $this->allDependencies[$subDependencyName]); + + // Keep track of dependencies that have already been used, prevents circular dependency problems + $this->alreadyExtractedDependencies[] = $subDependencyName; + foreach ($subDependencyArray as $composerDependencyName => $version) { + $subDependencyFullName = array_search($composerDependencyName, $moduleNameToComposerName); + $flattenedArray = array_merge( + $flattenedArray, + $this->extractSubDependencies($subDependencyFullName, $moduleNameToComposerName) + ); + } + } + return $flattenedArray; + } + + /** + * Finds unique array composer dependencies of given testObjects + * @param array $allEntities + * @param array $moduleComposerName + * @param array $moduleNameToPath + * @return array + */ + public function getModuleDependenciesFromReferences( + array $allEntities, + array $moduleComposerName, + array $moduleNameToPath + ): array { + $filenames = []; + foreach ($allEntities as $item) { + // Should it append ALL filenames, including merges? + $allFiles = explode(",", $item->getFilename()); + foreach ($allFiles as $file) { + $moduleName = $this->getModuleName($file, $moduleNameToPath); + if (isset($moduleComposerName[$moduleName])) { + $composerModuleName = $moduleComposerName[$moduleName]; + $filenames[$item->getName()][] = $composerModuleName; + } + } + } + return $filenames; + } + + /** + * Return module name for a file path + * + * @param string $filePath + * @param array $moduleNameToPath + * @return string|null + */ + public function getModuleName(string $filePath, array $moduleNameToPath): ?string + { + $moduleName = null; + foreach ($moduleNameToPath as $name => $path) { + if (strpos($filePath, $path. "/") !== false) { + $moduleName = $name; + break; + } + } + return $moduleName; + } + + /** + * Return array of merge test modules and file path with same test name. + * @param array $testDependencies + * @param array $extendedTestMapping + * @return array + */ + public function mergeDependenciesForExtendingTests(array $testDependencies, array $extendedTestMapping = []): array + { + $temp_array = array_reverse(array_column($testDependencies, "test_name"), true); + if (!empty($extendedTestMapping)) { + foreach ($extendedTestMapping as $value) { + $key = array_search($value["parent_test_name"], $temp_array); + if ($key !== false) { + #if parent test found merge this to child, for doing so just replace test name with child. + $testDependencies[$key]["test_name"] = $value["child_test_name"]; + } + } + } + $temp_array = []; + foreach ($testDependencies as $testDependency) { + $temp_array[$testDependency["test_name"]][] = $testDependency; + } + $testDependencies = []; + foreach ($temp_array as $testDependencyArray) { + $testDependencies[] = [ + "file_path" => array_column($testDependencyArray, 'file_path'), + "full_name" => $testDependencyArray[0]["full_name"], + "test_name" => $testDependencyArray[0]["test_name"], + "test_modules" =>array_values( + array_unique( + call_user_func_array( + 'array_merge', + array_column($testDependencyArray, 'test_modules') + ) + ) + ), + ]; + } + return $testDependencies; + } +} diff --git a/src/Magento/FunctionalTestingFramework/Util/Sorter/ParallelGroupSorter.php b/src/Magento/FunctionalTestingFramework/Util/Sorter/ParallelGroupSorter.php index 8f4cac8a2..74f5af3ca 100644 --- a/src/Magento/FunctionalTestingFramework/Util/Sorter/ParallelGroupSorter.php +++ b/src/Magento/FunctionalTestingFramework/Util/Sorter/ParallelGroupSorter.php @@ -5,7 +5,7 @@ */ namespace Magento\FunctionalTestingFramework\Util\Sorter; -use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; +use Magento\FunctionalTestingFramework\Exceptions\FastFailException; use Magento\FunctionalTestingFramework\Test\Handlers\TestObjectHandler; class ParallelGroupSorter @@ -26,19 +26,19 @@ public function __construct() } /** - * Function which returns tests and suites split according to desired number of lines divded into groups. + * Function which returns tests and suites split according to desired number of lines divided into groups. * * @param array $suiteConfiguration * @param array $testNameToSize * @param integer $time * @return array - * @throws TestFrameworkException + * @throws FastFailException */ public function getTestsGroupedBySize($suiteConfiguration, $testNameToSize, $time) { // we must have the lines argument in order to create the test groups - if ($time == 0) { - throw new TestFrameworkException( + if ($time === 0) { + throw new FastFailException( "Please provide the argument '--time' to the robo command in order to". " generate grouped tests manifests for a parallel execution" ); @@ -75,6 +75,189 @@ public function getTestsGroupedBySize($suiteConfiguration, $testNameToSize, $tim return $testGroups; } + /** + * Function which returns tests and suites split according to desired number of groups. + * + * @param array $suiteConfiguration + * @param array $testNameToSize + * @param integer $groupTotal + * @return array + * @throws FastFailException + */ + public function getTestsGroupedByFixedGroupCount($suiteConfiguration, $testNameToSize, $groupTotal) + { + $suiteNameToTestSize = $this->getSuiteNameToTestSize($suiteConfiguration); + + $minRequiredGroupCount = count($suiteNameToTestSize); + if (!empty($testNameToSize)) { + $minRequiredGroupCount += 1; + } + if ($groupTotal < $minRequiredGroupCount) { + throw new FastFailException( + "Invalid parameter 'groupTotal': must be equal or greater than {$minRequiredGroupCount}" + ); + } + + if (empty($suiteConfiguration)) { + return $this->convertArrayIndexStartingAtOne($this->splitTestsIntoGroups($testNameToSize, $groupTotal)); + } + + // Calculate suite group totals + $suiteNameToGroupCount = $this->getSuiteGroupCounts($suiteNameToTestSize, $testNameToSize, $groupTotal); + + // Calculate test group total + $testGroupTotal = $groupTotal - array_sum($suiteNameToGroupCount); + + // Split tests and suites + $testGroups = $this->splitTestsIntoGroups($testNameToSize, $testGroupTotal); + $testGroups = array_merge( + $testGroups, + $this->splitSuitesIntoGroups($suiteNameToTestSize, $suiteNameToGroupCount) + ); + + return $this->convertArrayIndexStartingAtOne($testGroups); + } + + /** + * Return suite's group counts from a group total + * + * @param array $suiteNameToTestSize + * @param array $testNameToSize + * @param integer $groupTotal + * @return array + */ + private function getSuiteGroupCounts($suiteNameToTestSize, $testNameToSize, $groupTotal) + { + // Calculate the minimum possible group time + $suiteNameToSize = $this->getSuiteToSize($suiteNameToTestSize); + $minGroupTime = ceil((array_sum($testNameToSize) + array_sum($suiteNameToSize)) / $groupTotal); + + // Find maximum suite time + $maxSuiteTime = max($suiteNameToSize); + + // Calculate 2 possible suite group times + $ceilSuiteGroupNumber = ceil($maxSuiteTime / $minGroupTime); + $ceilSuiteGroupTime = max(ceil($maxSuiteTime / $ceilSuiteGroupNumber), $minGroupTime); + $floorSuiteGroupNumber = floor($maxSuiteTime / $minGroupTime); + if ($floorSuiteGroupNumber !== 0) { + $floorSuiteGroupTime = max(ceil($maxSuiteTime / $floorSuiteGroupNumber), $minGroupTime); + } + + // Calculate test group time for ceiling + $ceilSuiteNameToGroupCount = $this->getSuiteGroupCountFromGroupTime($suiteNameToTestSize, $ceilSuiteGroupTime); + $ceilSuiteGroupTotal = array_sum($ceilSuiteNameToGroupCount); + $ceilTestGroupTotal = (int) $groupTotal - (int) $ceilSuiteGroupTotal; + + if ($ceilTestGroupTotal === 0) { + $ceilTestGroupTime = 0; + } else { + $ceilTestGroupTime = ceil(array_sum($testNameToSize) / $ceilTestGroupTotal); + } + + // Set suite group total to ceiling + $suiteNameToGroupCount = $ceilSuiteNameToGroupCount; + + if (isset($floorSuiteGroupTime) && $ceilSuiteGroupTime !== $floorSuiteGroupTime) { + // Calculate test group time for floor + $floorSuiteNameToGroupCount = $this->getSuiteGroupCountFromGroupTime( + $suiteNameToTestSize, + $floorSuiteGroupTime + ); + $floorSuiteGroupTotal = array_sum($floorSuiteNameToGroupCount); + $floorTestGroupTotal = $groupTotal - $floorSuiteGroupTotal; + if ($floorTestGroupTotal === 0) { + $floorTestGroupTime = 0; + } else { + $floorTestGroupTime = ceil(array_sum($testNameToSize) / $floorTestGroupTotal); + } + + // Choose the closer value between test group time and suite group time + $ceilDiff = abs($ceilTestGroupTime - $ceilSuiteGroupTime); + $floorDiff = abs($floorTestGroupTime - $floorSuiteGroupTime); + if ($ceilDiff > $floorDiff) { + // Adjust suite group total to floor + $suiteNameToGroupCount = $floorSuiteNameToGroupCount; + } + } + + return $suiteNameToGroupCount; + } + + /** + * Return array contains suitename to number of groups to be split based on time. + * + * @param array $suiteNameToTestSize + * @param integer $time + * @return array + */ + private function getSuiteGroupCountFromGroupTime($suiteNameToTestSize, $time) + { + $suiteNameToGroupCount = []; + foreach ($suiteNameToTestSize as $suiteName => $tests) { + $maxCount = count($tests); + $suiteTime = array_sum($tests); + if ($suiteTime <= $time) { + $suiteNameToGroupCount[$suiteName] = 1; + } else { + $suiteNameToGroupCount[$suiteName] = min(ceil($suiteTime/$time), $maxCount); + } + } + return $suiteNameToGroupCount; + } + + /** + * Split tests into given number of groups. + * + * @param array $tests + * @param integer $groupCnt + * @return array + */ + private function splitTestsIntoGroups($tests, $groupCnt) + { + // Reverse sort the test array by size + uasort($tests, function ($a, $b) { + return $a >= $b ? -1 : 1; + }); + $groups = array_fill(0, $groupCnt, []); + $sums = array_fill(0, $groupCnt, 0); + + foreach ($tests as $test => $size) { + // Always add the next test to the group with the smallest sum + $key = array_search(min($sums), $sums); + $groups[$key][$test] = $size; + $sums[$key] += $size; + } + // Filter empty array + return array_filter($groups); + } + + /** + * Split suites into given number of groups. + * + * @param array $suiteNameToTestSize + * @param array $suiteNameToGroupCount + * @return array + */ + private function splitSuitesIntoGroups($suiteNameToTestSize, $suiteNameToGroupCount) + { + $groups = []; + foreach ($suiteNameToTestSize as $suiteName => $suiteTests) { + $suiteCnt = $suiteNameToGroupCount[$suiteName]; + if ($suiteCnt === 1) { + $groups[][$suiteName] = array_sum($suiteTests); + $this->addSuiteToConfig($suiteName, null, $suiteTests); + } elseif ($suiteCnt > 1) { + $suiteGroups = $this->splitTestsIntoGroups($suiteTests, $suiteCnt); + foreach ($suiteGroups as $index => $tests) { + $newSuiteName = $suiteName . '_' . strval($index) . '_G'; + $groups[][$newSuiteName] = array_sum($tests); + $this->addSuiteToConfig($suiteName, $newSuiteName, $tests); + } + } + } + return $groups; + } + /** * Function which returns the newly formed suite objects created as a part of the sort * @@ -232,7 +415,7 @@ private function getSuiteToSize($suiteNamesToTests) * * E.g. * Input {suitename = 'sample', tests = ['test1' => 100,'test2' => 150, 'test3' => 300], linelimit = 275} - * Result { ['sample_01' => ['test3' => 300], 'sample_02' => ['test2' => 150, 'test1' => 100]] } + * Result { ['sample_01_G' => ['test3' => 300], 'sample_02_G' => ['test2' => 150, 'test1' => 100]] } * * @param string $suiteName * @param array $tests @@ -252,8 +435,8 @@ private function splitTestSuite($suiteName, $tests, $maxTime) } $group = $this->createTestGroup($maxTime, $test, $size, $availableTests); - $splitSuites["{$suiteName}_${splitCount}"] = $group; - $this->addSuiteToConfig($suiteName, "{$suiteName}_${splitCount}", $group); + $splitSuites["{$suiteName}_${splitCount}_G"] = $group; + $this->addSuiteToConfig($suiteName, "{$suiteName}_${splitCount}_G", $group); $availableTests = array_diff_key($availableTests, $group); $splitCount++; @@ -274,11 +457,28 @@ private function splitTestSuite($suiteName, $tests, $maxTime) */ private function addSuiteToConfig($originalSuiteName, $newSuiteName, $tests) { - if ($newSuiteName == null) { + if ($newSuiteName === null) { $this->suiteConfig[$originalSuiteName] = array_keys($tests); return; } $this->suiteConfig[$originalSuiteName][$newSuiteName] = array_keys($tests); } + + /** + * Convert array index starting at 1 + * + * @param array $inArray + * @return array + */ + private function convertArrayIndexStartingAtOne($inArray) + { + $outArray = []; + $index = 1; + foreach ($inArray as $value) { + $outArray[$index] = $value; + $index += 1; + } + return $outArray; + } } diff --git a/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php b/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php index ce45af963..d95526676 100644 --- a/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php +++ b/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php @@ -7,25 +7,31 @@ namespace Magento\FunctionalTestingFramework\Util; use Magento\FunctionalTestingFramework\Config\MftfApplicationConfig; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\CredentialStore; use Magento\FunctionalTestingFramework\DataGenerator\Handlers\PersistedObjectHandler; use Magento\FunctionalTestingFramework\DataGenerator\Objects\EntityDataObject; +use Magento\FunctionalTestingFramework\Exceptions\FastFailException; +use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; use Magento\FunctionalTestingFramework\Exceptions\TestReferenceException; +use Magento\FunctionalTestingFramework\Exceptions\XmlException; +use Magento\FunctionalTestingFramework\Filter\FilterInterface; use Magento\FunctionalTestingFramework\Suite\Handlers\SuiteObjectHandler; use Magento\FunctionalTestingFramework\Test\Handlers\ActionGroupObjectHandler; use Magento\FunctionalTestingFramework\Test\Handlers\TestObjectHandler; use Magento\FunctionalTestingFramework\Test\Objects\ActionGroupObject; use Magento\FunctionalTestingFramework\Test\Objects\ActionObject; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\DataObjectHandler; use Magento\FunctionalTestingFramework\Test\Objects\TestHookObject; use Magento\FunctionalTestingFramework\Test\Objects\TestObject; +use Magento\FunctionalTestingFramework\Test\Util\BaseObjectExtractor; +use Magento\FunctionalTestingFramework\Util\Filesystem\CestFileCreatorUtil; use Magento\FunctionalTestingFramework\Util\Logger\LoggingUtil; use Magento\FunctionalTestingFramework\Util\Manifest\BaseTestManifest; -use Magento\FunctionalTestingFramework\Util\Manifest\TestManifestFactory; use Magento\FunctionalTestingFramework\Test\Util\ActionObjectExtractor; -use Magento\FunctionalTestingFramework\Test\Util\TestObjectExtractor; use Magento\FunctionalTestingFramework\Util\Filesystem\DirSetupUtil; use Magento\FunctionalTestingFramework\Test\Util\ActionMergeUtil; +use Magento\FunctionalTestingFramework\Util\Path\FilePathFormatter; +use Mustache_Engine; +use Mustache_Loader_FilesystemLoader; +use Magento\FunctionalTestingFramework\DataGenerator\Handlers\DataObjectHandler; /** * Class TestGenerator @@ -38,22 +44,42 @@ class TestGenerator const REQUIRED_ENTITY_REFERENCE = 'createDataKey'; const GENERATED_DIR = '_generated'; const DEFAULT_DIR = 'default'; + const TEST_SCOPE = 'test'; const HOOK_SCOPE = 'hook'; const SUITE_SCOPE = 'suite'; + const PRESSKEY_ARRAY_ANCHOR_KEY = '987654321098765432109876543210'; const PERSISTED_OBJECT_NOTATION_REGEX = '/\${1,2}[\w.\[\]]+\${1,2}/'; const NO_STEPKEY_ACTIONS = [ 'comment', - 'createData', - 'deleteData', - 'updateData', - 'getData', + 'retrieveEntityField', + 'getSecret', 'magentoCLI', + 'magentoCron', 'generateDate', 'field' ]; + const RULE_ERROR = 'On step with stepKey "%s", only one of the attributes: "%s" can be use for action "%s"'; + const STEP_KEY_ANNOTATION = " // stepKey: %s"; + const CRON_INTERVAL = 60; + const ARRAY_WRAP_OPEN = '['; + const ARRAY_WRAP_CLOSE = ']'; + + /** + * Array with helpers classes and methods. + * + * @var array + */ + private $customHelpers = []; + + /** + * Actor name for AcceptanceTest + * + * @var string + */ + private $actor = 'I'; /** * Path to the export dir. @@ -95,25 +121,30 @@ class TestGenerator * * @var string */ - private $currentGenerationScope; + private $currentGenerationScope = TestGenerator::TEST_SCOPE; + + /** + * Test deprecation messages. + * + * @var array + */ + private $deprecationMessages = []; /** - * TestGenerator constructor. + * Private constructor for Factory * * @param string $exportDir * @param array $tests * @param boolean $debug + * @throws TestFrameworkException */ private function __construct($exportDir, $tests, $debug = false) { - // private constructor for factory $this->exportDirName = $exportDir ?? self::DEFAULT_DIR; - $exportDir = $exportDir ?? self::DEFAULT_DIR; - $this->exportDirectory = TESTS_MODULE_PATH - . DIRECTORY_SEPARATOR + $this->exportDirectory = FilePathFormatter::format(TESTS_MODULE_PATH) . self::GENERATED_DIR . DIRECTORY_SEPARATOR - . $exportDir; + . $this->exportDirName; $this->tests = $tests; $this->consoleOutput = new \Symfony\Component\Console\Output\ConsoleOutput(); $this->debug = $debug; @@ -148,6 +179,9 @@ public function getExportDir() * * @param array $testsToIgnore * @return array + * @throws TestReferenceException + * @throws TestFrameworkException + * @throws FastFailException */ private function loadAllTestObjects($testsToIgnore) { @@ -175,20 +209,13 @@ private function loadAllTestObjects($testsToIgnore) * * @param string $testPhp * @param string $filename + * * @return void - * @throws \Exception + * @throws TestFrameworkException */ - private function createCestFile($testPhp, $filename) + private function createCestFile(string $testPhp, string $filename) { - $exportFilePath = $this->exportDirectory . DIRECTORY_SEPARATOR . $filename . ".php"; - $file = fopen($exportFilePath, 'w'); - - if (!$file) { - throw new \Exception("Could not open the file."); - } - - fwrite($file, $testPhp); - fclose($file); + CestFileCreatorUtil::getInstance()->create($filename, $this->exportDirectory, $testPhp); } /** @@ -198,8 +225,10 @@ private function createCestFile($testPhp, $filename) * @param BaseTestManifest $testManifest * @param array $testsToIgnore * @return void + * @throws TestFrameworkException + * @throws XmlException + * @throws FastFailException * @throws TestReferenceException - * @throws \Exception */ public function createAllTestFiles($testManifest = null, $testsToIgnore = null) { @@ -230,12 +259,12 @@ public function createAllTestFiles($testManifest = null, $testsToIgnore = null) */ public function assembleTestPhp($testObject) { + $this->customHelpers = []; $usePhp = $this->generateUseStatementsPhp(); - $classAnnotationsPhp = $this->generateAnnotationsPhp($testObject->getAnnotations()); $className = $testObject->getCodeceptionName(); try { - if (!$testObject->isSkipped() && !MftfApplicationConfig::getConfig()->allowSkipped()) { + if (!$testObject->isSkipped() || MftfApplicationConfig::getConfig()->allowSkipped()) { $hookPhp = $this->generateHooksPhp($testObject->getHooks()); } else { $hookPhp = null; @@ -244,6 +273,7 @@ public function assembleTestPhp($testObject) } catch (TestReferenceException $e) { throw new TestReferenceException($e->getMessage() . "\n" . $testObject->getFilename()); } + $classAnnotationsPhp = $this->generateAnnotationsPhp($testObject); $cestPhp = "<?php\n"; $cestPhp .= "namespace Magento\AcceptanceTest\\_" . $this->exportDirName . "\Backend;\n\n"; @@ -251,6 +281,11 @@ public function assembleTestPhp($testObject) $cestPhp .= $classAnnotationsPhp; $cestPhp .= sprintf("class %s\n", $className); $cestPhp .= "{\n"; + $cestPhp .= "\t/**\n"; + $cestPhp .= "\t * @var bool\n"; + $cestPhp .= "\t */\n"; + $cestPhp .= "\tprivate \$isSuccess = false;\n\n"; + $cestPhp .= $this->generateInjectMethod(); $cestPhp .= $hookPhp; $cestPhp .= $testsPhp; $cestPhp .= "}\n"; @@ -258,41 +293,108 @@ public function assembleTestPhp($testObject) return $cestPhp; } + /** + * Generates _injectMethod based on $this->customHelpers. + * + * @return string + */ + private function generateInjectMethod() + { + if (empty($this->customHelpers)) { + return ""; + } + + $mustacheEngine = new Mustache_Engine([ + 'loader' => new Mustache_Loader_FilesystemLoader( + dirname(__DIR__) . DIRECTORY_SEPARATOR . "Helper" . DIRECTORY_SEPARATOR . 'views' + ) + ]); + + $argumentsWithType = []; + $arguments = []; + foreach ($this->customHelpers as $customHelperVar => $customHelperType) { + $argumentsWithType[] = $customHelperType . ' ' . $customHelperVar; + $arguments[] = ['type' => $customHelperType, 'var' => $customHelperVar]; + } + $mustacheData['argumentsWithTypes'] = implode(', ' . PHP_EOL, $argumentsWithType); + $mustacheData['arguments'] = $arguments; + + return $mustacheEngine->render('TestInjectMethod', $mustacheData); + } + /** * Load ALL Test objects. Loop over and pass each to the assembleTestPhp function. * * @param BaseTestManifest $testManifest * @param array $testsToIgnore * @return array + * @throws TestFrameworkException + * @throws TestReferenceException + * @throws FastFailException */ private function assembleAllTestPhp($testManifest, array $testsToIgnore) { /** @var TestObject[] $testObjects */ $testObjects = $this->loadAllTestObjects($testsToIgnore); $cestPhpArray = []; + $filters = MftfApplicationConfig::getConfig()->getFilterList()->getFilters(); + /** @var FilterInterface $filter */ + foreach ($filters as $filter) { + $filter->filter($testObjects); + } foreach ($testObjects as $test) { - // Do not generate test if it is an extended test and parent does not exist - if ($test->isSkipped() && !empty($test->getParentName())) { - try { - TestObjectHandler::getInstance()->getObject($test->getParentName()); - } catch (TestReferenceException $e) { - print("{$test->getName()} will not be generated. Parent {$e->getMessage()} \n"); - continue; + try { + // Reset flag for new test + $removeLastTest = false; + + // Do not generate test if it is an extended test and parent does not exist + if ($test->isSkipped() && !empty($test->getParentName())) { + try { + TestObjectHandler::getInstance()->getObject($test->getParentName()); + } catch (TestReferenceException $e) { + TestObjectHandler::getInstance()->sanitizeTests([$test->getName()]); + $errMessage = "{$test->getName()} will not be generated. " + . "Parent test {$test->getParentName()} not defined in xml."; + // There are tests extend from non-existing parent on purpose on certain Magento editions. + // To keep backward compatibility, we will skip the test and continue + if (MftfApplicationConfig::getConfig()->verboseEnabled()) { + print("NOTICE: {$errMessage}"); + } + LoggingUtil::getInstance()->getLogger(self::class)->warning($errMessage); + continue; + } } - } - $this->debug("<comment>Start creating test: " . $test->getCodeceptionName() . "</comment>"); - $php = $this->assembleTestPhp($test); - $cestPhpArray[] = [$test->getCodeceptionName(), $php]; + $this->debug("<comment>Start creating test: " . $test->getCodeceptionName() . "</comment>"); + $php = $this->assembleTestPhp($test); + $cestPhpArray[] = [$test->getCodeceptionName(), $php]; + // Set flag in case something goes wrong + $removeLastTest = true; - $debugInformation = $test->getDebugInformation(); - $this->debug($debugInformation); - $this->debug("<comment>Finish creating test: " . $test->getCodeceptionName() . "</comment>" . PHP_EOL); + $debugInformation = $test->getDebugInformation(); + $this->debug($debugInformation); + $this->debug("<comment>Finish creating test: " . $test->getCodeceptionName() . "</comment>" . PHP_EOL); - //write to manifest here if manifest is not null - if ($testManifest != null) { - $testManifest->addTest($test); + // Write to manifest here if manifest is not null + if ($testManifest !== null) { + $testManifest->addTest($test); + } + } catch (FastFailException $e) { + throw $e; + } catch (\Exception $e) { + GenerationErrorHandler::getInstance()->addError( + 'test', + $test->getName(), + self::class . ': ' . $e->getMessage() + ); + LoggingUtil::getInstance()->getLogger(self::class)->error( + "Failed to generate {$test->getName()}" + ); + if ($removeLastTest) { + array_pop($cestPhpArray); + } + TestObjectHandler::getInstance()->sanitizeTests([$test->getName()]); } } @@ -324,8 +426,6 @@ private function debug($messages) private function generateUseStatementsPhp() { $useStatementsPhp = "use Magento\FunctionalTestingFramework\AcceptanceTester;\n"; - $useStatementsPhp .= "use Magento\FunctionalTestingFramework\DataGenerator\Handlers\CredentialStore;\n"; - $useStatementsPhp .= "use Magento\FunctionalTestingFramework\DataGenerator\Handlers\PersistedObjectHandler;\n"; $useStatementsPhp .= "use \Codeception\Util\Locator;\n"; $allureStatements = [ @@ -349,12 +449,13 @@ private function generateUseStatementsPhp() /** * Generates Annotations PHP for given object, using given scope to determine indentation and additional output. * - * @param array $annotationsObject + * @param array $testObject * @param boolean $isMethod * @return string */ - private function generateAnnotationsPhp($annotationsObject, $isMethod = false) + private function generateAnnotationsPhp($testObject, $isMethod = false) { + $annotationsObject = $testObject->getAnnotations(); //TODO: Refactor to deal with PHPMD.CyclomaticComplexity if ($isMethod) { $indent = "\t"; @@ -366,11 +467,11 @@ private function generateAnnotationsPhp($annotationsObject, $isMethod = false) foreach ($annotationsObject as $annotationType => $annotationName) { //Remove conditional and output useCaseId upon completion of MQE-588 - if ($annotationType == "useCaseId") { + if ($annotationType === 'useCaseId') { continue; } if (!$isMethod) { - $annotationsPhp .= $this->generateClassAnnotations($annotationType, $annotationName); + $annotationsPhp .= $this->generateClassAnnotations($annotationType, $annotationName, $testObject); } else { $annotationsPhp .= $this->generateMethodAnnotations($annotationType, $annotationName); } @@ -427,11 +528,7 @@ private function generateMethodAnnotations($annotationType = null, $annotationNa break; case null: - $annotationToAppend = sprintf( - "{$indent} * @Parameter(name = \"%s\", value=\"$%s\")\n", - "AcceptanceTester", - "I" - ); + $annotationToAppend = ""; $annotationToAppend .= sprintf("{$indent} * @param %s $%s\n", "AcceptanceTester", "I"); $annotationToAppend .= "{$indent} * @return void\n"; $annotationToAppend .= "{$indent} * @throws \Exception\n"; @@ -440,26 +537,49 @@ private function generateMethodAnnotations($annotationType = null, $annotationNa return $annotationToAppend; } + /** + * Returs required credentials to configure + * + * @param TestObject $testObject + * @return string + */ + public function requiredCredentials($testObject) + { + $requiredCredentials = (!empty($testObject->getCredentials())) + ? implode(",", $testObject->getCredentials()) + : ""; + + return $requiredCredentials; + } /** * Method which return formatted class level annotations based on type and name(s). * * @param string $annotationType - * @param string $annotationName + * @param array $annotationName + * @param array $testObject * @return null|string * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ - private function generateClassAnnotations($annotationType, $annotationName) + private function generateClassAnnotations($annotationType, $annotationName, $testObject) { $annotationToAppend = null; - + if (!$testObject->isSkipped() && !empty($annotationName['main'])) { + $requiredCredentialsMessage = $this->requiredCredentials($testObject); + $credMsg = "\n\n"."This test uses the following credentials:"."\n"; + $annotationName = (!empty($requiredCredentialsMessage)) ? + ['main'=>$annotationName['main'].', '.$credMsg.''.$requiredCredentialsMessage, + 'test_files'=> "\n".$annotationName['test_files'], 'deprecated'=>$annotationName['deprecated']] + : $annotationName; + } switch ($annotationType) { case "title": $annotationToAppend = sprintf(" * @Title(\"%s\")\n", $annotationName[0]); break; case "description": - $annotationToAppend = sprintf(" * @Description(\"%s\")\n", $annotationName[0]); + $template = " * @Description(\"%s\")\n"; + $annotationToAppend = sprintf($template, $this->generateDescriptionAnnotation($annotationName)); break; case "testCaseId": @@ -480,6 +600,36 @@ private function generateClassAnnotations($annotationType, $annotationName) return $annotationToAppend; } + /** + * Generates Description + * + * @param array $descriptions + * @return string + */ + private function generateDescriptionAnnotation(array $descriptions) + { + $descriptionText = ""; + + $descriptionText .= $descriptions["main"] ?? ''; + if (!empty($descriptions[BaseObjectExtractor::OBJ_DEPRECATED]) || !empty($this->deprecationMessages)) { + $deprecatedMessages = array_merge( + $descriptions[BaseObjectExtractor::OBJ_DEPRECATED], + $this->deprecationMessages + ); + + $descriptionText .= "<h3 class='y-label y-label_status_broken'>Deprecated Notice(s):</h3>"; + $descriptionText .= "<ul>"; + + foreach ($deprecatedMessages as $deprecatedMessage) { + $descriptionText .= "<li>" . $deprecatedMessage . "</li>"; + } + $descriptionText .= "</ul>"; + } + $descriptionText .= $descriptions["test_files"]; + + return $descriptionText; + } + /** * Creates a PHP string for the actions contained withing a <test> block. * Since nearly half of all Codeception methods don't share the same signature I had to setup a massive Case @@ -497,10 +647,13 @@ private function generateClassAnnotations($annotationType, $annotationName) public function generateStepsPhp($actionObjects, $generationScope = TestGenerator::TEST_SCOPE, $actor = "I") { //TODO: Refactor Method according to PHPMD warnings, remove @SuppressWarnings accordingly. - $testSteps = ""; + $testSteps = ''; + $this->actor = $actor; $this->currentGenerationScope = $generationScope; + $this->deprecationMessages = []; foreach ($actionObjects as $actionObject) { + $this->deprecationMessages = array_merge($this->deprecationMessages, $actionObject->getDeprecatedUsages()); $stepKey = $actionObject->getStepKey(); $customActionAttributes = $actionObject->getCustomActionAttributes(); $attribute = null; @@ -517,6 +670,7 @@ public function generateStepsPhp($actionObjects, $generationScope = TestGenerato $function = null; $time = null; $locale = null; + $currency = null; $username = null; $password = null; $width = null; @@ -528,6 +682,7 @@ public function generateStepsPhp($actionObjects, $generationScope = TestGenerato $dependentSelector = null; $visible = null; $command = null; + $cronGroups = ''; $arguments = null; $sortOrder = null; $storeCode = null; @@ -545,6 +700,9 @@ public function generateStepsPhp($actionObjects, $generationScope = TestGenerato if (isset($customActionAttributes['command'])) { $command = $this->addUniquenessFunctionCall($customActionAttributes['command']); } + if (isset($customActionAttributes['groups'])) { + $cronGroups = $this->addUniquenessFunctionCall($customActionAttributes['groups']); + } if (isset($customActionAttributes['arguments'])) { $arguments = $this->addUniquenessFunctionCall($customActionAttributes['arguments']); } @@ -557,7 +715,11 @@ public function generateStepsPhp($actionObjects, $generationScope = TestGenerato $sortOrder = $customActionAttributes['sortOrder']; } - if (isset($customActionAttributes['userInput']) && isset($customActionAttributes['url'])) { + if (isset($customActionAttributes['userInput']) + && isset($customActionAttributes['locale']) + && isset($customActionAttributes['currency'])) { + $input = $this->parseUserInput($customActionAttributes['userInput']); + } elseif (isset($customActionAttributes['userInput']) && isset($customActionAttributes['url'])) { $input = $this->addUniquenessFunctionCall($customActionAttributes['userInput']); $url = $this->addUniquenessFunctionCall($customActionAttributes['url']); } elseif (isset($customActionAttributes['userInput'])) { @@ -565,9 +727,6 @@ public function generateStepsPhp($actionObjects, $generationScope = TestGenerato } elseif (isset($customActionAttributes['url'])) { $input = $this->addUniquenessFunctionCall($customActionAttributes['url']); $url = $this->addUniquenessFunctionCall($customActionAttributes['url']); - } elseif (isset($customActionAttributes['expectedValue'])) { - //For old Assert backwards Compatibility, remove when deprecating - $assertExpected = $this->addUniquenessFunctionCall($customActionAttributes['expectedValue']); } elseif (isset($customActionAttributes['regex'])) { $input = $this->addUniquenessFunctionCall($customActionAttributes['regex']); } @@ -611,15 +770,20 @@ public function generateStepsPhp($actionObjects, $generationScope = TestGenerato if (isset($customActionAttributes['timeout'])) { $time = $customActionAttributes['timeout']; } - $time = $time ?? ActionObject::getDefaultWaitTimeout(); - if (isset($customActionAttributes['parameterArray']) && $actionObject->getType() != 'pressKey') { + if (in_array($actionObject->getType(), ActionObject::COMMAND_ACTION_ATTRIBUTES)) { + $time = $time ?? ActionObject::DEFAULT_COMMAND_WAIT_TIMEOUT; + } else { + $time = $time ?? ActionObject::getDefaultWaitTimeout(); + } + + if (isset($customActionAttributes['parameterArray']) && $actionObject->getType() !== 'pressKey') { // validate the param array is in the correct format $this->validateParameterArray($customActionAttributes['parameterArray']); - $parameterArray = "["; - $parameterArray .= $this->addUniquenessToParamArray($customActionAttributes['parameterArray']); - $parameterArray .= "]"; + $parameterArray = $this->wrapParameterArray( + $this->addUniquenessToParamArray($customActionAttributes['parameterArray']) + ); } if (isset($customActionAttributes['requiredAction'])) { @@ -632,6 +796,11 @@ public function generateStepsPhp($actionObjects, $generationScope = TestGenerato $selector = $this->addUniquenessFunctionCall($customActionAttributes['selector']); $selector = $this->resolveLocatorFunctionInAttribute($selector); } + if (isset($customActionAttributes['count'])) { + $countClickValue = $customActionAttributes['count']; + $countValue = $this->addUniquenessFunctionCall($countClickValue); + $countValue = $this->resolveLocatorFunctionInAttribute($countValue); + } if (isset($customActionAttributes['selector1']) || isset($customActionAttributes['filterSelector'])) { $selectorOneValue = $customActionAttributes['selector1'] ?? $customActionAttributes['filterSelector']; @@ -654,16 +823,13 @@ public function generateStepsPhp($actionObjects, $generationScope = TestGenerato } if (isset($customActionAttributes['function'])) { - $function = $this->addUniquenessFunctionCall( - $customActionAttributes['function'], - $actionObject->getType() !== "executeInSelenium" - ); + $function = $this->addUniquenessFunctionCall($customActionAttributes['function']); if (in_array($actionObject->getType(), ActionObject::FUNCTION_CLOSURE_ACTIONS)) { // Argument must be a closure function, not a string. $function = trim($function, '"'); } // turn $javaVariable => \$javaVariable but not {$mftfVariable} - if ($actionObject->getType() == "executeJS") { + if ($actionObject->getType() === "executeJS") { $function = preg_replace('/(?<!{)(\$[A-Za-z._]+)(?![A-z.]*+\$)/', '\\\\$1', $function); } } @@ -676,6 +842,10 @@ public function generateStepsPhp($actionObjects, $generationScope = TestGenerato $locale = $this->wrapWithDoubleQuotes($customActionAttributes['locale']); } + if (isset($customActionAttributes['currency'])) { + $currency = $this->wrapWithDoubleQuotes($customActionAttributes['currency']); + } + if (isset($customActionAttributes['username'])) { $username = $this->wrapWithDoubleQuotes($customActionAttributes['username']); } @@ -717,23 +887,60 @@ public function generateStepsPhp($actionObjects, $generationScope = TestGenerato } switch ($actionObject->getType()) { - case "createData": - $entity = $customActionAttributes['entity']; - //Add an informative statement to help the user debug test runs + case "helper": + if (!in_array($customActionAttributes['class'], $this->customHelpers)) { + $this->customHelpers['$' . $stepKey] = $customActionAttributes['class']; + } + + $arguments = []; + $classReader = new \Magento\FunctionalTestingFramework\Helper\Code\ClassReader(); + $parameters = $classReader->getParameters( + $customActionAttributes['class'], + $customActionAttributes['method'] + ); + $errors = []; + foreach ($parameters as $parameter) { + if (array_key_exists($parameter['variableName'], $customActionAttributes)) { + $value = $customActionAttributes[$parameter['variableName']]; + $arguments[] = $this->addUniquenessFunctionCall( + $value, + $parameter['type'] === 'string' || $parameter['type'] === null + ); + } elseif ($parameter['isOptional']) { + $value = $parameter['optionalValue']; + $arguments[] = str_replace(PHP_EOL, '', var_export($value, true)); + } else { + $errors[] = 'Argument \'' . $parameter['variableName'] . '\' for method ' + . $customActionAttributes['class'] . '::' . $customActionAttributes['method'] + . ' is not found.'; + } + } + if (!empty($errors)) { + throw new TestFrameworkException(implode(PHP_EOL, $errors)); + } $testSteps .= sprintf( - "\t\t$%s->comment(\"[%s] create '%s' entity\");\n", + "\t\t$%s->comment('[%s] %s()');" . PHP_EOL, $actor, $stepKey, - $entity + $customActionAttributes['class'] . '::' . $customActionAttributes['method'] ); - + $testSteps .= $this->wrapFunctionCallWithReturnValue( + $stepKey, + $actor, + $actionObject, + $arguments + ); + break; + case "createData": + $entity = $customActionAttributes['entity']; + $this->entityExistsCheck($entity, $stepKey); //TODO refactor entity field override to not be individual actionObjects $customEntityFields = $customActionAttributes[ActionObjectExtractor::ACTION_OBJECT_PERSISTENCE_FIELDS] ?? []; $requiredEntityKeys = []; foreach ($actionObject->getCustomActionAttributes() as $actionAttribute) { - if (is_array($actionAttribute) && $actionAttribute['nodeName'] == 'requiredEntity') { + if (is_array($actionAttribute) && $actionAttribute['nodeName'] === 'requiredEntity') { //append ActionGroup if provided $requiredEntityActionGroup = $actionAttribute['actionGroup'] ?? null; $requiredEntityKeys[] = $actionAttribute['createDataKey'] . $requiredEntityActionGroup; @@ -744,28 +951,22 @@ public function generateStepsPhp($actionObjects, $generationScope = TestGenerato if (!empty($requiredEntityKeys)) { $requiredEntityKeysArray = '"' . implode('", "', $requiredEntityKeys) . '"'; } - //Determine Scope - $scope = PersistedObjectHandler::TEST_SCOPE; - if ($generationScope == TestGenerator::HOOK_SCOPE) { - $scope = PersistedObjectHandler::HOOK_SCOPE; - } elseif ($generationScope == TestGenerator::SUITE_SCOPE) { - $scope = PersistedObjectHandler::SUITE_SCOPE; - } + $scope = $this->getObjectScope($generationScope); - $createEntityFunctionCall = "\t\tPersistedObjectHandler::getInstance()->createEntity("; - $createEntityFunctionCall .= "\n\t\t\t\"{$stepKey}\","; - $createEntityFunctionCall .= "\n\t\t\t\"{$scope}\","; - $createEntityFunctionCall .= "\n\t\t\t\"{$entity}\""; - $createEntityFunctionCall .= ",\n\t\t\t[{$requiredEntityKeysArray}]"; + $createEntityFunctionCall = "\t\t\${$actor}->createEntity("; + $createEntityFunctionCall .= "\"{$stepKey}\","; + $createEntityFunctionCall .= " \"{$scope}\","; + $createEntityFunctionCall .= " \"{$entity}\","; + $createEntityFunctionCall .= " [{$requiredEntityKeysArray}],"; if (count($customEntityFields) > 1) { - $createEntityFunctionCall .= ",\n\t\t\t\${$stepKey}Fields"; + $createEntityFunctionCall .= " \${$stepKey}Fields"; } else { - $createEntityFunctionCall .= ",\n\t\t\t[]"; + $createEntityFunctionCall .= " []"; } if ($storeCode !== null) { - $createEntityFunctionCall .= ",\n\t\t\t\"{$storeCode}\""; + $createEntityFunctionCall .= ", \"{$storeCode}\""; } - $createEntityFunctionCall .= "\n\t\t);\n"; + $createEntityFunctionCall .= ");"; $testSteps .= $createEntityFunctionCall; break; case "deleteData": @@ -777,34 +978,20 @@ public function generateStepsPhp($actionObjects, $generationScope = TestGenerato ); $actionGroup = $actionObject->getCustomActionAttributes()['actionGroup'] ?? null; $key .= $actionGroup; - //Add an informative statement to help the user debug test runs - $contextSetter = sprintf( - "\t\t$%s->comment(\"[%s] delete entity '%s'\");\n", - $actor, - $stepKey, - $key - ); - //Determine Scope - $scope = PersistedObjectHandler::TEST_SCOPE; - if ($generationScope == TestGenerator::HOOK_SCOPE) { - $scope = PersistedObjectHandler::HOOK_SCOPE; - } elseif ($generationScope == TestGenerator::SUITE_SCOPE) { - $scope = PersistedObjectHandler::SUITE_SCOPE; - } + $scope = $this->getObjectScope($generationScope); - $deleteEntityFunctionCall = "\t\tPersistedObjectHandler::getInstance()->deleteEntity("; - $deleteEntityFunctionCall .= "\n\t\t\t\"{$key}\","; - $deleteEntityFunctionCall .= "\n\t\t\t\"{$scope}\""; - $deleteEntityFunctionCall .= "\n\t\t);\n"; + $deleteEntityFunctionCall = "\t\t\${$actor}->deleteEntity("; + $deleteEntityFunctionCall .= "\"{$key}\","; + $deleteEntityFunctionCall .= " \"{$scope}\""; + $deleteEntityFunctionCall .= ");"; - $testSteps .= $contextSetter; $testSteps .= $deleteEntityFunctionCall; } else { $url = $this->resolveAllRuntimeReferences([$url])[0]; $url = $this->resolveTestVariable([$url], null)[0]; $output = sprintf( - "\t\t$%s->deleteEntityByUrl(%s);\n", + "\t\t$%s->deleteEntityByUrl(%s);", $actor, $url ); @@ -821,19 +1008,10 @@ public function generateStepsPhp($actionObjects, $generationScope = TestGenerato $actionGroup = $actionObject->getCustomActionAttributes()['actionGroup'] ?? null; $key .= $actionGroup; - //Add an informative statement to help the user debug test runs - $testSteps .= sprintf( - "\t\t$%s->comment(\"[%s] update '%s' entity to '%s'\");\n", - $actor, - $stepKey, - $key, - $updateEntity - ); - // Build array of requiredEntities $requiredEntityKeys = []; foreach ($actionObject->getCustomActionAttributes() as $actionAttribute) { - if (is_array($actionAttribute) && $actionAttribute['nodeName'] == 'requiredEntity') { + if (is_array($actionAttribute) && $actionAttribute['nodeName'] === 'requiredEntity') { //append ActionGroup if provided $requiredEntityActionGroup = $actionAttribute['actionGroup'] ?? null; $requiredEntityKeys[] = $actionAttribute['createDataKey'] . $requiredEntityActionGroup; @@ -844,22 +1022,17 @@ public function generateStepsPhp($actionObjects, $generationScope = TestGenerato $requiredEntityKeysArray = '"' . implode('", "', $requiredEntityKeys) . '"'; } - $scope = PersistedObjectHandler::TEST_SCOPE; - if ($generationScope == TestGenerator::HOOK_SCOPE) { - $scope = PersistedObjectHandler::HOOK_SCOPE; - } elseif ($generationScope == TestGenerator::SUITE_SCOPE) { - $scope = PersistedObjectHandler::SUITE_SCOPE; - } + $scope = $this->getObjectScope($generationScope); - $updateEntityFunctionCall = "\t\tPersistedObjectHandler::getInstance()->updateEntity("; - $updateEntityFunctionCall .= "\n\t\t\t\"{$key}\","; - $updateEntityFunctionCall .= "\n\t\t\t\"{$scope}\","; - $updateEntityFunctionCall .= "\n\t\t\t\"{$updateEntity}\""; - $updateEntityFunctionCall .= ",\n\t\t\t[{$requiredEntityKeysArray}]"; + $updateEntityFunctionCall = "\t\t\${$actor}->updateEntity("; + $updateEntityFunctionCall .= "\"{$key}\","; + $updateEntityFunctionCall .= " \"{$scope}\","; + $updateEntityFunctionCall .= " \"{$updateEntity}\","; + $updateEntityFunctionCall .= "[{$requiredEntityKeysArray}]"; if ($storeCode !== null) { - $updateEntityFunctionCall .= ",\n\t\t\t\"{$storeCode}\""; + $updateEntityFunctionCall .= ", \"{$storeCode}\""; } - $updateEntityFunctionCall .= "\n\t\t);\n"; + $updateEntityFunctionCall .= ");"; $testSteps .= $updateEntityFunctionCall; break; @@ -869,18 +1042,11 @@ public function generateStepsPhp($actionObjects, $generationScope = TestGenerato if (isset($customActionAttributes['index'])) { $index = (int)$customActionAttributes['index']; } - //Add an informative statement to help the user debug test runs - $testSteps .= sprintf( - "\t\t$%s->comment(\"[%s] get '%s' entity\");\n", - $actor, - $stepKey, - $entity - ); // Build array of requiredEntities $requiredEntityKeys = []; foreach ($actionObject->getCustomActionAttributes() as $actionAttribute) { - if (is_array($actionAttribute) && $actionAttribute['nodeName'] == 'requiredEntity') { + if (is_array($actionAttribute) && $actionAttribute['nodeName'] === 'requiredEntity') { $requiredEntityActionGroup = $actionAttribute['actionGroup'] ?? null; $requiredEntityKeys[] = $actionAttribute['createDataKey'] . $requiredEntityActionGroup; } @@ -890,29 +1056,23 @@ public function generateStepsPhp($actionObjects, $generationScope = TestGenerato $requiredEntityKeysArray = '"' . implode('", "', $requiredEntityKeys) . '"'; } - //Determine Scope - $scope = PersistedObjectHandler::TEST_SCOPE; - if ($generationScope == TestGenerator::HOOK_SCOPE) { - $scope = PersistedObjectHandler::HOOK_SCOPE; - } elseif ($generationScope == TestGenerator::SUITE_SCOPE) { - $scope = PersistedObjectHandler::SUITE_SCOPE; - } + $scope = $this->getObjectScope($generationScope); //Create Function - $getEntityFunctionCall = "\t\tPersistedObjectHandler::getInstance()->getEntity("; - $getEntityFunctionCall .= "\n\t\t\t\"{$stepKey}\","; - $getEntityFunctionCall .= "\n\t\t\t\"{$scope}\","; - $getEntityFunctionCall .= "\n\t\t\t\"{$entity}\""; - $getEntityFunctionCall .= ",\n\t\t\t[{$requiredEntityKeysArray}]"; + $getEntityFunctionCall = "\t\t\${$actor}->getEntity("; + $getEntityFunctionCall .= "\"{$stepKey}\","; + $getEntityFunctionCall .= " \"{$scope}\","; + $getEntityFunctionCall .= " \"{$entity}\","; + $getEntityFunctionCall .= " [{$requiredEntityKeysArray}],"; if ($storeCode !== null) { - $getEntityFunctionCall .= ",\n\t\t\t\"{$storeCode}\""; + $getEntityFunctionCall .= " \"{$storeCode}\""; } else { - $getEntityFunctionCall .= ",\n\t\t\tnull"; + $getEntityFunctionCall .= " null"; } if ($index !== null) { - $getEntityFunctionCall .= ",\n\t\t\t{$index}"; + $getEntityFunctionCall .= ", {$index}"; } - $getEntityFunctionCall .= "\n\t\t);\n"; + $getEntityFunctionCall .= ");"; $testSteps .= $getEntityFunctionCall; break; @@ -962,6 +1122,7 @@ public function generateStepsPhp($actionObjects, $generationScope = TestGenerato $parameterArray ); break; + case "grabCookieAttributes": case "grabCookie": $testSteps .= $this->wrapFunctionCallWithReturnValue( $stepKey, @@ -999,6 +1160,7 @@ public function generateStepsPhp($actionObjects, $generationScope = TestGenerato break; case "selectOption": case "unselectOption": + case "seeNumberOfElements": $testSteps .= $this->wrapFunctionCall( $actor, $actionObject, @@ -1026,6 +1188,14 @@ public function generateStepsPhp($actionObjects, $generationScope = TestGenerato $y ); break; + case "rapidClick": + $testSteps .= $this->wrapFunctionCall( + $actor, + $actionObject, + $selector, + $countValue + ); + break; case "selectMultipleOptions": $testSteps .= $this->wrapFunctionCall( $actor, @@ -1036,9 +1206,6 @@ public function generateStepsPhp($actionObjects, $generationScope = TestGenerato $parameterArray ); break; - case "executeInSelenium": - $testSteps .= $this->wrapFunctionCall($actor, $actionObject, $function); - break; case "executeJS": $testSteps .= $this->wrapFunctionCallWithReturnValue( $stepKey, @@ -1047,7 +1214,6 @@ public function generateStepsPhp($actionObjects, $generationScope = TestGenerato $function ); break; - case "performOn": case "waitForElementChange": $testSteps .= $this->wrapFunctionCall( $actor, @@ -1084,13 +1250,24 @@ public function generateStepsPhp($actionObjects, $generationScope = TestGenerato $selector ); break; - case "formatMoney": + case "return": + $actionOrigin = $actionObject->getActionOrigin(); + $actionOriginStepKey = $actionOrigin[ActionGroupObject::ACTION_GROUP_ORIGIN_TEST_REF]; + $testSteps .= $this->wrapFunctionCallWithReturnValue( + $actionOriginStepKey, + $actor, + $actionObject, + $value + ); + break; + case "formatCurrency": $testSteps .= $this->wrapFunctionCallWithReturnValue( $stepKey, $actor, $actionObject, $input, - $locale + $locale, + $currency ); break; case "mSetLocale": @@ -1117,10 +1294,12 @@ public function generateStepsPhp($actionObjects, $generationScope = TestGenerato ); break; case "grabPageSource": + case "getOTP": $testSteps .= $this->wrapFunctionCallWithReturnValue( $stepKey, $actor, - $actionObject + $actionObject, + $input ); break; case "resizeWindow": @@ -1166,15 +1345,6 @@ public function generateStepsPhp($actionObjects, $generationScope = TestGenerato case "seeOptionIsSelected": $testSteps .= $this->wrapFunctionCall($actor, $actionObject, $selector, $input); break; - case "seeNumberOfElements": - $testSteps .= $this->wrapFunctionCall( - $actor, - $actionObject, - $selector, - $input, - $parameterArray - ); - break; case "seeInPageSource": case "dontSeeInPageSource": case "seeInSource": @@ -1194,15 +1364,12 @@ public function generateStepsPhp($actionObjects, $generationScope = TestGenerato $visible ); break; - case "assertEquals": case "assertGreaterOrEquals": case "assertGreaterThan": case "assertGreaterThanOrEqual": - case "assertInternalType": case "assertLessOrEquals": case "assertLessThan": case "assertLessThanOrEqual": - case "assertNotEquals": case "assertInstanceOf": case "assertNotInstanceOf": case "assertNotRegExp": @@ -1216,6 +1383,10 @@ public function generateStepsPhp($actionObjects, $generationScope = TestGenerato case "assertCount": case "assertContains": case "assertNotContains": + case "assertStringContainsString": + case "assertStringContainsStringIgnoringCase": + case "assertStringNotContainsString": + case "assertStringNotContainsStringIgnoringCase": case "expectException": $testSteps .= $this->wrapFunctionCall( $actor, @@ -1226,6 +1397,31 @@ public function generateStepsPhp($actionObjects, $generationScope = TestGenerato $assertDelta ); break; + case "assertEquals": + case "assertNotEquals": + case "assertEqualsIgnoringCase": + case "assertNotEqualsIgnoringCase": + case "assertEqualsCanonicalizing": + case "assertNotEqualsCanonicalizing": + $testSteps .= $this->wrapFunctionCall( + $actor, + $actionObject, + $assertExpected, + $assertActual, + $assertMessage + ); + break; + case "assertEqualsWithDelta": + case "assertNotEqualsWithDelta": + $testSteps .= $this->wrapFunctionCall( + $actor, + $actionObject, + $assertExpected, + $assertActual, + $assertDelta, + $assertMessage + ); + break; case "assertElementContainsAttribute": // If a blank string or null is passed in we need to pass a blank string to the function. if (empty($assertExpected)) { @@ -1256,16 +1452,6 @@ public function generateStepsPhp($actionObjects, $generationScope = TestGenerato $assertMessage ); break; - case "assertArraySubset": - $testSteps .= $this->wrapFunctionCall( - $actor, - $actionObject, - $assertExpected, - $assertActual, - $assertIsStrict, - $assertMessage - ); - break; case "fail": $testSteps .= $this->wrapFunctionCall( $actor, @@ -1280,6 +1466,23 @@ public function generateStepsPhp($actionObjects, $generationScope = TestGenerato $actor, $actionObject, $command, + $time, + $arguments + ); + $testSteps .= sprintf(self::STEP_KEY_ANNOTATION, $stepKey) . PHP_EOL; + $testSteps .= sprintf( + "\t\t$%s->comment(\$%s);", + $actor, + $stepKey + ); + break; + case 'magentoCron': + $testSteps .= $this->wrapFunctionCallWithReturnValue( + $stepKey, + $actor, + $actionObject, + $cronGroups, + self::CRON_INTERVAL + $time, $arguments ); $testSteps .= sprintf(self::STEP_KEY_ANNOTATION, $stepKey) . PHP_EOL; @@ -1291,16 +1494,18 @@ public function generateStepsPhp($actionObjects, $generationScope = TestGenerato break; case "field": $fieldKey = $actionObject->getCustomActionAttributes()['key']; + $input = $this->resolveStepKeyReferences($input, $actionObject->getActionOrigin()); $input = $this->resolveTestVariable( [$input], $actionObject->getActionOrigin() )[0]; $argRef = "\t\t\$"; - $input = $this->resolveAllRuntimeReferences([$input])[0]; + $input = (isset($actionObject->getCustomActionAttributes()['unique'])) ? + $this->getUniqueIdForInput($actionObject->getCustomActionAttributes()['unique'], $input) + : $input; $argRef .= str_replace(ucfirst($fieldKey), "", $stepKey) . "Fields['{$fieldKey}'] = ${input};"; - $testSteps .= $argRef; break; case "generateDate": @@ -1316,12 +1521,19 @@ public function generateStepsPhp($actionObjects, $generationScope = TestGenerato $testSteps .= $dateGenerateCode; break; - case "skipReadinessCheck": - $testSteps .= $this->wrapFunctionCall($actor, $actionObject, $customActionAttributes['state']); + case "pause": + $pauseAttr = $actionObject->getCustomActionAttributes( + ActionObject::PAUSE_ACTION_INTERNAL_ATTRIBUTE + ); + if ($pauseAttr) { + $testSteps .= sprintf("\t\t$%s->%s(%s);", $actor, $actionObject->getType(), 'true'); + } else { + $testSteps .= sprintf("\t\t$%s->%s();", $actor, $actionObject->getType()); + } break; case "comment": $input = $input === null ? strtr($value, ['$' => '\$', '{' => '\{', '}' => '\}']) : $input; - // Combining userInput from native XML comment and <comment/> action to fall-through 'default' case + // Combining userInput from native XML comment and <comment/> action to fall-through 'default' case default: $testSteps .= $this->wrapFunctionCall( $actor, @@ -1336,10 +1548,24 @@ public function generateStepsPhp($actionObjects, $generationScope = TestGenerato } $testSteps .= PHP_EOL; } - return $testSteps; } + /** + * Get unique value appended to input string + * + * @param string $uniqueValue + * @param string $input + * @return string + */ + public function getUniqueIdForInput($uniqueValue, $input) + { + $input = ($uniqueValue == 'prefix') + ? '"'.uniqid().str_replace('"', '', $input).'"' + : '"'.str_replace('"', '', $input).uniqid().'"'; + return $input; + } + /** * Resolves Locator:: in given $attribute if it is found. * @@ -1398,9 +1624,9 @@ private function trimVariableIfNeeded($input) preg_match('/"{\$[a-z][a-zA-Z\d]+}"/', $input, $match); if (isset($match[0])) { return trim($input, '{}"'); - } else { - return $input; } + + return $input; } /** @@ -1419,14 +1645,18 @@ private function replaceMatchesIntoArg($matches, &$outputArg) $replacement = null; $delimiter = '$'; $variable = $this->stripAndSplitReference($match, $delimiter); - if (count($variable) != 2) { + if (count($variable) !== 2) { throw new \Exception( - "Invalid Persisted Entity Reference: {$match}. + "Invalid Persisted Entity Reference: {$match}. Test persisted entity references must follow {$delimiter}entityStepKey.field{$delimiter} format." ); } - $replacement = "PersistedObjectHandler::getInstance()->retrieveEntityField"; + $actor = "\$" . $this->actor; + if ($this->currentGenerationScope === TestGenerator::SUITE_SCOPE) { + $actor = 'PersistedObjectHandler::getInstance()'; + } + $replacement = "{$actor}->retrieveEntityField"; $replacement .= "('{$variable[0]}', '$variable[1]', '{$this->currentGenerationScope}')"; //Determine if quoteBreak check is necessary. Assume replacement is surrounded in quotes, then override @@ -1467,7 +1697,7 @@ private function processQuoteBreaks($match, $argument, $replacement) */ private function resolveStepKeyReferences($input, $actionGroupOrigin, $matchAll = false) { - if ($actionGroupOrigin == null) { + if ($actionGroupOrigin === null) { return $input; } $output = $input; @@ -1481,15 +1711,20 @@ private function resolveStepKeyReferences($input, $actionGroupOrigin, $matchAll foreach ($stepKeys as $stepKey) { // MQE-1011 $stepKeyVarRef = "$" . $stepKey; - $persistedVarRef = "PersistedObjectHandler::getInstance()->retrieveEntityField('{$stepKey}'" + + $actor = "\$" . $this->actor; + if ($this->currentGenerationScope === TestGenerator::SUITE_SCOPE) { + $actor = 'PersistedObjectHandler::getInstance()'; + } + $persistedVarRef = "{$actor}->retrieveEntityField('{$stepKey}'" . ", 'field', 'test')"; - $persistedVarRefInvoked = "PersistedObjectHandler::getInstance()->retrieveEntityField('" + $persistedVarRefInvoked = "{$actor}->retrieveEntityField('" . $stepKey . $testInvocationKey . "', 'field', 'test')"; // only replace when whole word matches exactly // e.g. testVar => $testVar but not $testVar2 if (strpos($output, $stepKeyVarRef) !== false) { - $output = preg_replace('/\B\\' .$stepKeyVarRef. '\b/', $stepKeyVarRef . $testInvocationKey, $output); + $output = preg_replace('/\B\\' . $stepKeyVarRef . '\b/', $stepKeyVarRef . $testInvocationKey, $output); } if (strpos($output, $persistedVarRef) !== false) { @@ -1524,8 +1759,8 @@ private function wrapFunctionArgsWithQuotes($functionRegex, $input) foreach ($allArguments as $argument) { $argument = trim($argument); - if ($argument[0] == "[") { - $replacement = "[" . $this->addUniquenessToParamArray($argument) . "]"; + if ($argument[0] === self::ARRAY_WRAP_OPEN) { + $replacement = $this->wrapParameterArray($this->addUniquenessToParamArray($argument)); } elseif (is_numeric($argument)) { $replacement = $argument; } else { @@ -1566,6 +1801,10 @@ private function generateHooksPhp($hookObjects) { $hooks = ""; + if (!isset($hookObjects['after'])) { + $hookObjects['after'] = new TestHookObject('after', '', []); + } + foreach ($hookObjects as $hookObject) { $type = $hookObject->getType(); $dependencies = 'AcceptanceTester $I'; @@ -1584,9 +1823,40 @@ private function generateHooksPhp($hookObjects) throw new TestReferenceException($e->getMessage() . " in Element \"" . $type . "\""); } + if ($type === 'before' && $steps) { + $steps = sprintf( + "\t\t$%s->comment('[%s]');" . PHP_EOL, + 'I', + 'START BEFORE HOOK' + ) . $steps; + $steps = $steps . sprintf( + "\t\t$%s->comment('[%s]');" . PHP_EOL, + 'I', + 'END BEFORE HOOK' + ); + } + + if ($type === 'after' && $steps) { + $steps = sprintf( + "\t\t$%s->comment('[%s]');" . PHP_EOL, + 'I', + 'START AFTER HOOK' + ) . $steps; + $steps = $steps . sprintf( + "\t\t$%s->comment('[%s]');" . PHP_EOL, + 'I', + 'END AFTER HOOK' + ); + } + $hooks .= sprintf("\tpublic function _{$type}(%s)\n", $dependencies); $hooks .= "\t{\n"; $hooks .= $steps; + if ($type === 'after') { + $hooks .= "\t\t" . 'if ($this->isSuccess) {' . "\n"; + $hooks .= "\t\t\t" . 'unlink(__FILE__);' . "\n"; + $hooks .= "\t\t" . '}' . "\n"; + } $hooks .= "\t}\n\n"; } @@ -1608,9 +1878,15 @@ private function generateTestPhp($test) $testName = $test->getName(); $testName = str_replace(' ', '', $testName); - $testAnnotations = $this->generateAnnotationsPhp($test->getAnnotations(), true); + $testAnnotations = $this->generateAnnotationsPhp($test, true); $dependencies = 'AcceptanceTester $I'; - if ($test->isSkipped() && !MftfApplicationConfig::getConfig()->allowSkipped()) { + if (!$test->isSkipped() || MftfApplicationConfig::getConfig()->allowSkipped()) { + try { + $steps = $this->generateStepsPhp($test->getOrderedActions()); + } catch (\Exception $e) { + throw new TestReferenceException($e->getMessage() . " in Test \"" . $test->getName() . "\""); + } + } else { $skipString = "This test is skipped due to the following issues:\\n"; $issues = $test->getAnnotations()['skip'] ?? null; if (isset($issues)) { @@ -1618,14 +1894,9 @@ private function generateTestPhp($test) } else { $skipString .= "No issues have been specified."; } - $steps = "\t\t" . '$scenario->skip("' . $skipString . '");' . "\n"; + $steps = "\t\t" . 'unlink(__FILE__);' . "\n"; + $steps .= "\t\t" . '$scenario->skip("' . $skipString . '");' . "\n"; $dependencies .= ', \Codeception\Scenario $scenario'; - } else { - try { - $steps = $this->generateStepsPhp($test->getOrderedActions()); - } catch (\Exception $e) { - throw new TestReferenceException($e->getMessage() . " in Test \"" . $test->getName() . "\""); - } } $testPhp .= $testAnnotations; @@ -1634,6 +1905,14 @@ private function generateTestPhp($test) $testPhp .= $steps; $testPhp .= "\t}\n"; + if (!isset($skipString)) { + $testPhp .= PHP_EOL; + $testPhp .= sprintf("\tpublic function _passed(%s)\n", $dependencies); + $testPhp .= "\t{\n"; + $testPhp .= "\t\t// Test passed successfully." . PHP_EOL; + $testPhp .= "\t\t\$this->isSuccess = true;" . PHP_EOL; + $testPhp .= "\t}\n"; + } return $testPhp; } @@ -1694,8 +1973,9 @@ private function processPressKey($input) preg_match_all('/[\[][^\]]*?[\]]/', $input, $paramInput); if (!empty($paramInput)) { foreach ($paramInput[0] as $param) { - $arrayResult[self::PRESSKEY_ARRAY_ANCHOR_KEY . $count] = - '[' . trim($this->addUniquenessToParamArray($param)) . ']'; + $arrayResult[self::PRESSKEY_ARRAY_ANCHOR_KEY . $count] = $this->wrapParameterArray( + trim($this->addUniquenessToParamArray($param)) + ); $input = str_replace($param, self::PRESSKEY_ARRAY_ANCHOR_KEY . $count, $input); $count++; } @@ -1770,7 +2050,7 @@ private function addUniquenessFunctionCall($input, $wrapWithDoubleQuotes = true) */ private function wrapWithDoubleQuotes($input) { - if ($input == null) { + if ($input === null || $input === '') { return ''; } //Only replace " with \" so that it doesn't break outer string. @@ -1789,13 +2069,8 @@ private function stripWrappedQuotes($input) if (empty($input)) { return ''; } - if (substr($input, 0, 1) === '"') { - $input = substr($input, 1); - } - if (substr($input, -1, 1) === '"') { - $input = substr($input, 0, -1); - } - return $input; + + return trim($input, '"'); } /** @@ -1809,28 +2084,42 @@ private function addDollarSign($input) return sprintf("$%s", ltrim($this->stripQuotes($input), '$')); } - // @codingStandardsIgnoreStart + /** + * Check if the entity exists + * + * @param string $entity + * @param string $stepKey + * @return void + * @throws TestReferenceException + */ + public function entityExistsCheck($entity, $stepKey) + { + $retrievedEntity = DataObjectHandler::getInstance()->getObject($entity); + if ($retrievedEntity === null) { + throw new TestReferenceException( + "Test generation failed as entity \"" . $entity . "\" does not exist. at stepkey ".$stepKey + ); + } + } /** * Wrap parameters into a function call. * - * @param string $actor + * @param string $actor * @param actionObject $action - * @param string $scope - * @param array ...$args + * @param array ...$args * @return string * @throws \Exception */ private function wrapFunctionCall($actor, $action, ...$args) { - $isFirst = true; $output = sprintf("\t\t$%s->%s(", $actor, $action->getType()); for ($i = 0; $i < count($args); $i++) { if (null === $args[$i]) { continue; } if ($args[$i] === "") { - $args[$i] = '"' . $args[$i] . '"'; + $args[$i] = '""'; } } if (!is_array($args)) { @@ -1838,31 +2127,35 @@ private function wrapFunctionCall($actor, $action, ...$args) } $args = $this->resolveAllRuntimeReferences($args); $args = $this->resolveTestVariable($args, $action->getActionOrigin()); - $output .= implode(", ", array_filter($args, function($value) { return $value !== null; })) . ");"; + $output .= implode(", ", array_filter($args, $this->filterNullCallback())) . ");"; return $output; } /** * Wrap parameters into a function call with a return value. * - * @param string $returnVariable - * @param string $actor - * @param string $action - * @param string $scope - * @param array ...$args + * @param string $returnVariable + * @param string $actor + * @param actionObject $action + * @param array ...$args * @return string * @throws \Exception */ private function wrapFunctionCallWithReturnValue($returnVariable, $actor, $action, ...$args) { - $isFirst = true; - $output = sprintf("\t\t$%s = $%s->%s(", $returnVariable, $actor, $action->getType()); + $actionType = $action->getType(); + if ($actionType === 'helper') { + $actor = "this->helperContainer->get('" . $action->getCustomActionAttributes()['class'] . "')"; + $args = $args[0]; + $actionType = $action->getCustomActionAttributes()['method']; + } + $output = sprintf("\t\t$%s = $%s->%s(", $returnVariable, $actor, $actionType); for ($i = 0; $i < count($args); $i++) { if (null === $args[$i]) { continue; } if ($args[$i] === "") { - $args[$i] = '"' . $args[$i] . '"'; + $args[$i] = '""'; } } if (!is_array($args)) { @@ -1870,13 +2163,25 @@ private function wrapFunctionCallWithReturnValue($returnVariable, $actor, $actio } $args = $this->resolveAllRuntimeReferences($args); $args = $this->resolveTestVariable($args, $action->getActionOrigin()); - $output .= implode(", ", array_filter($args, function($value) { return $value !== null; })) . ");"; + $output .= implode(", ", array_filter($args, $this->filterNullCallback())) . ");"; return $output; } - // @codingStandardsIgnoreEnd + + /** + * Closure returned is used as a callable for array_filter to remove null values from array + * + * @return callable + */ + private function filterNullCallback() + { + return function ($value) { + return $value !== null; + }; + } /** * Resolves {{_ENV.variable}} into getenv("variable") for test-runtime ENV referencing. + * * @param array $args * @param string $regex * @param string $func @@ -1887,18 +2192,20 @@ private function resolveRuntimeReference($args, $regex, $func) $newArgs = []; foreach ($args as $key => $arg) { - preg_match_all($regex, $arg, $matches); - if (!empty($matches[0])) { - $fullMatch = $matches[0][0]; - $refVariable = $matches[1][0]; - unset($matches); - $replacement = "{$func}(\"{$refVariable}\")"; - - $outputArg = $this->processQuoteBreaks($fullMatch, $arg, $replacement); - $newArgs[$key] = $outputArg; - continue; - } $newArgs[$key] = $arg; + if ($arg !== null) { + preg_match_all($regex, $arg, $matches); + if (!empty($matches[0])) { + foreach ($matches[0] as $matchKey => $fullMatch) { + $refVariable = $matches[1][$matchKey]; + $replacement = $this->getReplacement($func, $refVariable); + $outputArg = $this->processQuoteBreaks($fullMatch, $newArgs[$key], $replacement); + $newArgs[$key] = $outputArg; + } + unset($matches); + continue; + } + } } // override passed in args for use later. @@ -1916,7 +2223,7 @@ private function resolveAllRuntimeReferences($args) { $runtimeReferenceRegex = [ "/{{_ENV\.([\w]+)}}/" => 'getenv', - ActionMergeUtil::CREDS_REGEX => 'CredentialStore::getInstance()->getSecret' + ActionMergeUtil::CREDS_REGEX => "\${$this->actor}->getSecret" ]; $argResult = $args; @@ -1936,43 +2243,79 @@ private function resolveAllRuntimeReferences($args) */ private function validateParameterArray($paramArray) { - if (substr($paramArray, 0, 1) != "[" || substr($paramArray, strlen($paramArray) - 1, 1) != "]") { - throw new TestReferenceException("parameterArray must begin with `[` and end with `]"); + if (!$this->isWrappedArray($paramArray)) { + throw new TestReferenceException(sprintf( + "parameterArray must begin with `%s` and end with `%s`", + self::ARRAY_WRAP_OPEN, + self::ARRAY_WRAP_CLOSE + )); } } + /** + * Verifies whether we have correctly wrapped array syntax + * + * @param string $paramArray + * @return boolean + */ + private function isWrappedArray(string $paramArray) + { + return 0 === strpos($paramArray, self::ARRAY_WRAP_OPEN) + && substr($paramArray, -1) === self::ARRAY_WRAP_CLOSE; + } + /** * Resolve value based on type. * - * @param string $value - * @param string $type - * @return string + * @param string|null $value + * @param string|null $type + * @return string|null * @throws TestReferenceException - * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ - private function resolveValueByType($value, $type) + private function resolveValueByType($value = null, $type = null) { - //TODO: Refactor to deal with PHPMD.CyclomaticComplexity, and remove @SuppressWarnings if (null === $value) { return null; } + if (null === $type) { $type = 'const'; } - if ($type == "string") { - return $this->addUniquenessFunctionCall($value); - } elseif ($type == "bool") { - return $this->toBoolean($value) ? "true" : "false"; - } elseif ($type == "int" || $type == "float") { - return $this->toNumber($value); - } elseif ($type == "array") { - $this->validateParameterArray($value); - return "[" . $this->addUniquenessToParamArray($value) . "]"; - } elseif ($type == "variable") { - return $this->addDollarSign($value); - } else { - return $value; + + switch ($type) { + case 'string': + return $this->addUniquenessFunctionCall($value); + case 'bool': + return $this->toBoolean($value) ? "true" : "false"; + case 'int': + case 'float': + return $this->toNumber($value); + case 'array': + $this->validateParameterArray($value); + return $this->wrapParameterArray($this->addUniquenessToParamArray($value)); + case 'variable': + return $this->addDollarSign($value); + } + + return $value; + } + + /** + * Determines correct scope based on parameter + * + * @param string $generationScope + * @return string + */ + private function getObjectScope(string $generationScope): string + { + switch ($generationScope) { + case TestGenerator::SUITE_SCOPE: + return PersistedObjectHandler::SUITE_SCOPE; + case TestGenerator::HOOK_SCOPE: + return PersistedObjectHandler::HOOK_SCOPE; } + + return PersistedObjectHandler::TEST_SCOPE; } /** @@ -1995,11 +2338,11 @@ private function toBoolean($inStr) private function toNumber($inStr) { $outStr = $this->stripQuotes($inStr); - if (strpos($outStr, localeconv()['decimal_point']) === false) { - return intval($outStr); - } else { + if ($this->hasDecimalPoint($outStr)) { return floatval($outStr); } + + return intval($outStr); } /** @@ -2051,6 +2394,7 @@ private function validateXmlAttributesMutuallyExclusive($key, $tagName, $attribu 'excludes' => [ 'dontSeeCookie', 'grabCookie', + 'grabCookieAttributes', 'resetCookie', 'seeCookie', 'setCookie', @@ -2086,9 +2430,68 @@ private function printRuleErrorToConsole($key, $tagName, $attributes) if (empty($tagName) || empty($attributes)) { return; } - $message = 'On step with stepKey "' . $key . '", only one of the attributes: "'; - $message .= implode('", "', $attributes); - $message .= '" can be use for action "' . $tagName . "\".\n"; - print $message; + + printf(self::RULE_ERROR, $key, implode('", "', $attributes), $tagName); + } + + /** + * Wraps parameters array with opening and closing symbol. + * + * @param string $value + * @return string + */ + private function wrapParameterArray(string $value): string + { + return sprintf('%s%s%s', self::ARRAY_WRAP_OPEN, $value, self::ARRAY_WRAP_CLOSE); + } + + /** + * Determines whether string provided contains decimal point characteristic for current locale + * + * @param string $outStr + * @return boolean + */ + private function hasDecimalPoint(string $outStr) + { + return strpos($outStr, localeconv()['decimal_point']) !== false; + } + + /** + * Parse action attribute `userInput` + * + * @param string $userInput + * @return string + */ + private function parseUserInput($userInput) + { + $floatPattern = '/^\s*([+-]?[0-9]*\.?[0-9]+)\s*$/'; + preg_match($floatPattern, $userInput, $float); + if (isset($float[1])) { + return $float[1]; + } + + $intPattern = '/^\s*([+-]?[0-9]+)\s*$/'; + preg_match($intPattern, $userInput, $int); + if (isset($int[1])) { + return $int[1]; + } + + return $this->addUniquenessFunctionCall($userInput); + } + + /** + * Supports fallback for BACKEND URL + * + * @param string $func + * @param string $refVariable + * @return string + */ + private function getReplacement($func, $refVariable): string + { + if ($refVariable === 'MAGENTO_BACKEND_BASE_URL') { + return "({$func}(\"{$refVariable}\") ? rtrim({$func}(\"{$refVariable}\"), \"/\") : \"\")"; + } + + return "{$func}(\"{$refVariable}\")"; } } diff --git a/src/Magento/FunctionalTestingFramework/Util/Validation/DuplicateNodeValidationUtil.php b/src/Magento/FunctionalTestingFramework/Util/Validation/DuplicateNodeValidationUtil.php index 0c323a2dd..2eba2c493 100644 --- a/src/Magento/FunctionalTestingFramework/Util/Validation/DuplicateNodeValidationUtil.php +++ b/src/Magento/FunctionalTestingFramework/Util/Validation/DuplicateNodeValidationUtil.php @@ -65,10 +65,10 @@ public function validateChildUniqueness(\DOMElement $parentNode, $filename, $par $withoutDuplicates = array_unique($keyValues); - if (count($withoutDuplicates) != count($keyValues)) { + if (count($withoutDuplicates) !== count($keyValues)) { $duplicates = array_diff_assoc($keyValues, $withoutDuplicates); $keyError = ""; - foreach ($duplicates as $duplicateKey => $duplicateValue) { + foreach ($duplicates as $duplicateValue) { $keyError .= "\t{$this->uniqueKey}: {$duplicateValue} is used more than once."; if ($parentKey !== null) { $keyError .=" (Parent: {$parentKey})"; diff --git a/src/Magento/FunctionalTestingFramework/Util/Validation/NameValidationUtil.php b/src/Magento/FunctionalTestingFramework/Util/Validation/NameValidationUtil.php index 60b4660fb..68642c53a 100644 --- a/src/Magento/FunctionalTestingFramework/Util/Validation/NameValidationUtil.php +++ b/src/Magento/FunctionalTestingFramework/Util/Validation/NameValidationUtil.php @@ -6,14 +6,41 @@ namespace Magento\FunctionalTestingFramework\Util\Validation; +use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; use Magento\FunctionalTestingFramework\Exceptions\XmlException; +use Magento\FunctionalTestingFramework\Util\Logger\LoggingUtil; class NameValidationUtil { const PHP_CLASS_REGEX_PATTERN = '/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/'; + const DATA_ENTITY_NAME = "data entity name"; + const DATA_ENTITY_KEY = "data entity key"; + const METADATA_OPERATION_NAME = "metadata operation name"; + const PAGE = "Page"; + const SECTION = "Section"; + const SECTION_ELEMENT_NAME = "section element name"; + const ACTION_GROUP_NAME = "action group name"; + const TEST_NAME = "test name"; + + /** + * The number of violations this instance has detected. + * + * @var integer + */ + private $count; + /** - * Function which runs a validation against the blacklisted char defined in this class. Validation occurs to insure + * NameValidationUtil constructor. + * + */ + public function __construct() + { + $this->count = 0; + } + + /** + * Function which runs a validation against the blocklisted char defined in this class. Validation occurs to insure * allure report does not error/future devOps builds do not error against illegal char. * * @param string $name @@ -50,4 +77,110 @@ public static function validateName($name, $type) throw new XmlException($errorMessage); } } + + /** + * Validates that the string is PascalCase. + * + * @param string $str + * @param string $type + * @param string $filename + * @throws TestFrameworkException + * @return void + */ + public function validatePascalCase($str, $type, $filename = null) + { + if (!is_string($str) || !ctype_upper($str[0])) { + $message = "The {$type} {$str} should be PascalCase with an uppercase first letter."; + + if ($filename !== null) { + $message .= " See file {$filename}."; + } + + LoggingUtil::getInstance()->getLogger(self::class)->notification( + $message, + [], + false + ); + + $this->count++; + } + } + + /** + * Validates that the string is camelCase. + * + * @param string $str + * @param string $type + * @param string $filename + * @throws TestFrameworkException + * @return void + */ + public function validateCamelCase($str, $type, $filename = null) + { + if (!is_string($str) || !ctype_lower($str[0])) { + $message = "The {$type} {$str} should be camelCase with a lowercase first letter."; + + if ($filename !== null) { + $message .= " See file {$filename}."; + } + + LoggingUtil::getInstance()->getLogger(self::class)->notification( + $message, + [], + false + ); + + $this->count++; + } + } + + /** + * Validates that the string is of the pattern {Admin or Storefront}{Description}{Type}. + * + * @param string $str + * @param string $type + * @param string $filename + * @throws TestFrameworkException + * @return void + */ + public function validateAffixes($str, $type, $filename = null) + { + $isPrefixAdmin = substr($str, 0, 5) === "Admin"; + $isPrefixStorefront = substr($str, 0, 10) === "Storefront"; + $isSuffixType = substr($str, -strlen($type)) === $type; + + if ((!$isPrefixAdmin && !$isPrefixStorefront) || !$isSuffixType) { + $message = "The {$type} name {$str} should follow the pattern {Admin or Storefront}{Description}{$type}."; + + if ($filename !== null) { + $message .= " See file {$filename}."; + } + + LoggingUtil::getInstance()->getLogger(self::class)->notification( + $message, + [], + false + ); + + $this->count++; + } + } + + /** + * Outputs the number of validations detected by this instance. + * + * @param string $type + * @throws TestFrameworkException + * @return void + */ + public function summarize($type) + { + if ($this->count > 0) { + LoggingUtil::getInstance()->getLogger(self::class)->notification( + "{$this->count} {$type} violations detected. See mftf.log for details.", + [], + true + ); + } + } } diff --git a/src/Magento/FunctionalTestingFramework/Util/Validation/SingleNodePerFileValidationUtil.php b/src/Magento/FunctionalTestingFramework/Util/Validation/SingleNodePerFileValidationUtil.php new file mode 100644 index 000000000..0fd440888 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Util/Validation/SingleNodePerFileValidationUtil.php @@ -0,0 +1,53 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\FunctionalTestingFramework\Util\Validation; + +use Magento\FunctionalTestingFramework\Exceptions\Collector\ExceptionCollector; + +/** + * Class SingleNodePerDocumentValidationUtil + * @package Magento\FunctionalTestingFramework\Util\Validation + */ +class SingleNodePerFileValidationUtil +{ + /** + * ExceptionColletor used to catch errors + * + * @var ExceptionCollector + */ + private $exceptionCollector; + + /** + * SingleNodePerDocumentValidationUtil constructor + * + * @param ExceptionCollector $exceptionCollector + */ + public function __construct($exceptionCollector) + { + $this->exceptionCollector = $exceptionCollector; + } + + /** + * Validate single node per dom document for a given tag name + * + * @param \DOMDocument $dom + * @param string $tag + * @param string $filename + * @return void + */ + public function validateSingleNodeForTag($dom, $tag, $filename = '') + { + $tagNodes = $dom->getElementsByTagName($tag); + $count = $tagNodes->length; + if ($count === 1) { + return; + } + + $errorMsg = "Single <{$tag}> node per xml file. {$count} found in file: {$filename}\n"; + $this->exceptionCollector->addError($filename, $errorMsg); + } +} diff --git a/src/Magento/FunctionalTestingFramework/_bootstrap.php b/src/Magento/FunctionalTestingFramework/_bootstrap.php index 34807d2b9..9b7f7a821 100644 --- a/src/Magento/FunctionalTestingFramework/_bootstrap.php +++ b/src/Magento/FunctionalTestingFramework/_bootstrap.php @@ -6,6 +6,8 @@ */ // define framework basepath for schema pathing +use Symfony\Component\Dotenv\Exception\PathException; + defined('FW_BP') || define('FW_BP', realpath(__DIR__ . '/../../../')); // get the root path of the project $projectRootPath = substr(FW_BP, 0, strpos(FW_BP, DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR)); @@ -15,12 +17,17 @@ return; } defined('PROJECT_ROOT') || define('PROJECT_ROOT', $projectRootPath); -$envFilepath = realpath($projectRootPath . '/dev/tests/acceptance/'); +$envFilePath = realpath($projectRootPath . '/dev/tests/acceptance/') . DIRECTORY_SEPARATOR; +defined('ENV_FILE_PATH') || define('ENV_FILE_PATH', $envFilePath); -if (file_exists($envFilepath . DIRECTORY_SEPARATOR . '.env')) { - $env = new \Dotenv\Loader($envFilepath . DIRECTORY_SEPARATOR . '.env'); - $env->load(); +//Load constants from .env file +if (file_exists(ENV_FILE_PATH . '.env')) { + $env = new \Symfony\Component\Dotenv\Dotenv(); + if (function_exists('putenv')) { + $env->usePutenv(); + } + $env->populate($env->parse(file_get_contents(ENV_FILE_PATH . '.env'), ENV_FILE_PATH . '.env'), true); if (array_key_exists('TESTS_MODULE_PATH', $_ENV) xor array_key_exists('TESTS_BP', $_ENV)) { throw new Exception( @@ -40,13 +47,20 @@ 'MAGENTO_CLI_COMMAND_PATH', 'dev/tests/acceptance/utils/command.php' ); - $env->setEnvironmentVariable('MAGENTO_CLI_COMMAND_PATH', MAGENTO_CLI_COMMAND_PATH); - defined('MAGENTO_CLI_COMMAND_PARAMETER') || define('MAGENTO_CLI_COMMAND_PARAMETER', 'command'); - $env->setEnvironmentVariable('MAGENTO_CLI_COMMAND_PARAMETER', MAGENTO_CLI_COMMAND_PARAMETER); - defined('DEFAULT_TIMEZONE') || define('DEFAULT_TIMEZONE', 'America/Los_Angeles'); - $env->setEnvironmentVariable('DEFAULT_TIMEZONE', DEFAULT_TIMEZONE); + defined('WAIT_TIMEOUT') || define('WAIT_TIMEOUT', 30); + defined('VERBOSE_ARTIFACTS') || define('VERBOSE_ARTIFACTS', false); + $env->populate( + [ + 'MAGENTO_CLI_COMMAND_PATH' => MAGENTO_CLI_COMMAND_PATH, + 'MAGENTO_CLI_COMMAND_PARAMETER' => MAGENTO_CLI_COMMAND_PARAMETER, + 'DEFAULT_TIMEZONE' => DEFAULT_TIMEZONE, + 'WAIT_TIMEOUT' => WAIT_TIMEOUT, + 'VERBOSE_ARTIFACTS' => VERBOSE_ARTIFACTS, + ], + true + ); try { new DateTimeZone(DEFAULT_TIMEZONE); @@ -59,16 +73,10 @@ defined('MAGENTO_BP') || define('MAGENTO_BP', realpath(PROJECT_ROOT)); // TODO REMOVE THIS CODE ONCE WE HAVE STOPPED SUPPORTING dev/tests/acceptance PATH // define TEST_PATH and TEST_MODULE_PATH -defined('TESTS_BP') || define('TESTS_BP', realpath(MAGENTO_BP . DIRECTORY_SEPARATOR . 'dev/tests/acceptance/')); +defined('TESTS_BP') || define('TESTS_BP', realpath(MAGENTO_BP . DIRECTORY_SEPARATOR . 'dev/tests/acceptance')); -$RELATIVE_TESTS_MODULE_PATH = '/tests/functional/Magento/FunctionalTest'; +$RELATIVE_TESTS_MODULE_PATH = '/tests/functional/Magento'; defined('TESTS_MODULE_PATH') || define( 'TESTS_MODULE_PATH', realpath(TESTS_BP . $RELATIVE_TESTS_MODULE_PATH) ); - -// add the debug flag here -$debugMode = $_ENV['MFTF_DEBUG'] ?? false; -if (!(bool)$debugMode && extension_loaded('xdebug')) { - xdebug_disable(); -}