diff --git a/.github/.metadata.json b/.github/.metadata.json new file mode 100644 index 000000000..f1f6926ab --- /dev/null +++ b/.github/.metadata.json @@ -0,0 +1,33 @@ +{ + "templateVersion": "0.2", + "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": "Adobe Commerce Quality Engineering / Pangolin", + "DL": "GRP-Pangolin", + "slackChannel": "mftf" + } + }, + "ticketTracker": { + "functionalJiraQueue": { + "projectKey": "ACQE", + "component": "Framework - MFTF" + }, + "securityJiraQueue": { + "projectKey": "MAGREQ", + "component": "Test Infrastructure" + } + }, + "productionCodeBranches": ["master"], + "staticScan": { + "enable": true, + "frequency": "monthly", + "customName": "", + "branchesToScan": [ + "develop" + ] + } +} diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 000000000..9aaee78fb --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,3 @@ +# CODEOWNERS file for /docs/ folder. +# Forces a review from other writers for anything within /docs/. +/docs/ @magento/devdocs-admins diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md new file mode 100644 index 000000000..b037250ef --- /dev/null +++ b/.github/CONTRIBUTING.md @@ -0,0 +1,181 @@ +# Contribution Guidelines for the Magento Functional Testing Framework + +- [Contribution requirements](#contribution-requirements) +- [Fork a repository](#fork-a-repository) + - [Update the fork with the latest changes](#update-the-fork-with-the-latest-changes) +- [Create a pull request](#create-a-pull-request) +- [Report an issue](#report-an-issue) +- [Read labels](#read-labels) + - [Pull request status](#pull-request-status) + - [Issue resolution status](#issue-resolution-status) + - [Domains impacted](#domains-impacted) + - [Type](#type) + +Use the [fork] & [pull] model to contribute to the Magento Functional Testing Framework (MFTF) code base. +This contribution model has contributors maintaining their own copy of the forked code base (which can be easily synced with the main copy). +The forked repository is then used to submit a request to the base repository to pull a set of changes (pull request). + +Contributions can take the form of new components or features, changes to existing features, tests, documentation (such as developer guides, user guides, examples, or specifications), bug fixes, optimizations, or just good suggestions. + +The MFTF development team reviews all issues and contributions submitted by the community of developers in a "first-in, first-out" basis. +During the review we might require clarifications from the contributor. +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. + +## 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 + +1. Contributions must adhere to [Magento coding standards]. +2. Refer to the Magento development team’s [Definition of Done]. + We use these guidelines internally to ensure that we deliver well-tested, well-documented, solid code, and we encourage you to as well! +3. Pull requests (PRs) must be accompanied by a meaningful description of their purpose. + Comprehensive descriptions increase the chances that a pull request is merged quickly and without additional clarification requests. +4. Commits must be accompanied by meaningful commit messages. +5. PRs that include bug fixing must be accompanied by a step-by-step description of how to reproduce the bug. +6. PRs that include new logic or new features must be submitted along with: + + - Unit/integration test coverage + - Proposed documentation update. + For the documentation contribution guidelines, see [DOCUMENTATION_TEMPLATE][]. +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 must be green). + +## Fork a repository + +To fork a repository on Github, do the following: + +1. Navigate to the [MFTF repository]. +2. Click **Fork** at the top right. +3. Clone the repo into your development environment and start playing. + +Learn more in the [Fork a repo][github fork] GitHub article. + +### Update the fork with the latest changes + +As community and Magento writers’ changes are merged to the repository, your fork becomes outdated and pull requests might result in conflicts. +To see if your fork is outdated, open the fork page in GitHub and if at the top displays the following message: + +__This branch is NUMBER commits behind magento:develop.__ + +It means your fork must be updated. + +There are two ways to update your fork. +The typical way is discussed in the [Syncing a fork][github sync fork] GitHub article. +Make sure to update from the correct branch! + +The other way is to create a reverse pull request from the original repository. +Though this method has the downside of inserting unnecessary information into fork commit history. + +1. In your fork, click **New pull request**. +2. Click the "switching the base" link and then click **Create pull request**. +3. Provide a descriptive name for your pull request in the provided field. +4. Scroll to the bottom of the page and click **Merge pull request**, then click **Confirm Merge**. + +## Create a pull request + +First, check the [existing PRs] and make sure you are not duplicating others’ work! + +To create a pull request do the following: + +1. Create a feature branch for your changes and push those changes to the copy of your repository on GitHub. + This is the best way to organize and even update your PR. +2. In your repository, click **Pull requests**, and then click **New pull request**. +3. Ensure that you are creating a PR to the **magento/magento2-functional-testing-framework: develop** branch. + We accept PRs to this branch only. +4. Review the changes, then click **Create pull request**. + Fill out the form, and click **Create pull request** again to submit the PR—that’s it! + +Learn more in the [Creating a pull request][create pr] GitHub article. + +After submitting your PR, you can head over to the repository’s [Pull Requests panel][existing PRs] to see your PR along with the others. +Your PR undergoes automated testing, and if it passes, the core team considers it for inclusion in the Magento Functional Testing Framework codebase. +If some tests fail, make the corresponding corrections in your code. + +## Report an issue + +If you find a bug in Magento Functional Testing Framework code, you can report it by creating an issue in the Magento Functional Testing Framework repository. + +Before creating an issue, do the following: + +1. Read the [issue reporting guidelines][issue reporting] to learn how to create an issue that can be processed in a timely manner. +2. Check the documentation to make sure the behavior you are reporting is really a bug, not a feature. +3. Check the [existing issues] to make sure you are not duplicating somebody’s work. + +To add an issue: + +1. [Open a new issue][open new issue] +2. Fill in the **Title** and issue description +3. Click **Submit new issue** + +Learn more in the [Creating an issue][create issue] GitHub article. + +## Read labels + +Refer to the tables with descriptions of each label below. +The labels reflect the status, impact, or which team is working on it. + +### Pull request status + +Label| Description +---|--- +**accept**| The pull request has been accepted to be merged into mainline code. +**reject**| The pull request has been rejected. The most common cases are when the issue has already been fixed in another code contribution, or there is an issue with the code contribution. +**needsUpdate**| We need more information from the PR author to properly prioritize and process the pull request. + +### Issue resolution status + +Label| Description +---|--- +**acknowledged**| We validated the issue and created an internal ticket. +**needsUpdate**| We need more information from the PR author to properly prioritize and process the issue. +**cannot reproduce**| We do not have enough details from the issue description to reproduce the issue. +**non-issue**| We don't think that this is an issue according to the provided information. + +### Domains impacted + +Label| Description +---|--- +**PROD**| Affects the Product team (mostly feature requests or business logic change). +**DOC**| Affects Documentation domain. +**TECH**| Affects Architect Group (mostly to make decisions around technology changes). + +### Type + +Label| Description +---|--- +**bugfix**| The issue or pull request is about fixing a bug. +**enhancement**| The issue or pull request that makes the MFTF even more awesome (for example new features, optimization, refactoring, etc). + +[fork]: #fork-a-repository +[issue]: #report-an-issue +[labels]: #read-labels +[pull]: #create-a-pull-request + +[create issue]: https://help.github.com/articles/creating-an-issue/ +[create pr]: https://help.github.com/articles/creating-a-pull-request/ +[Definition of Done]: https://devdocs.magento.com/guides/v2.2/contributor-guide/contributing_dod.html +[DOCUMENTATION_TEMPLATE]: https://github.com/magento/devdocs/blob/master/.github/DOCUMENTATION_TEMPLATE.md +[existing issues]: https://github.com/magento/magento2-functional-testing-framework/issues?q=is%3Aopen+is%3Aissue +[existing PRs]: https://github.com/magento/magento2-functional-testing-framework/pulls?q=is%3Aopen+is%3Apr +[GitHub documentation]: https://help.github.com/articles/syncing-a-fork +[github fork]: https://help.github.com/articles/fork-a-repo/ +[github sync fork]: https://help.github.com/articles/syncing-a-fork/ +[issue reporting]: https://github.com/magento/magento2-functional-testing-framework/wiki/Issue-reporting-guidelines +[Magento coding standards]: https://devdocs.magento.com/guides/v2.2/coding-standards/bk-coding-standards.html +[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 diff --git a/.github/DOCUMENTATION_TEMPLATE.md b/.github/DOCUMENTATION_TEMPLATE.md new file mode 100644 index 000000000..222ff23be --- /dev/null +++ b/.github/DOCUMENTATION_TEMPLATE.md @@ -0,0 +1,26 @@ +# How to contribute to MFTF docs + +We welcome contributions to the MFTF documentation, which is kept within the `docs/` folder in this repository. +This page describes the submitting process and serves as a template for a properly written content. +The contribution workflow is the same as submitting code. + +1. Create a branch from the `develop` branch in the [MFTF repository][]. +1. Make edits/additions/deletions as needed. Read the [Basic Template][] for tips on how to write with Markdown. +1. Submit your pull request to the `develop` branch. + +Once submitted, a member of the documentation team will review and merge it. +We will inform you if it needs any additional processing. + +The documentation in this repository is used as the source for the [MFTF documentation][]. +Any changes to the table of contents will need to be made through a separate pull request in the regular [Magento Developer documentation repository][]. + +Read more about how to [Contribute to Magento Devdocs][]. + + +[Magento Developer documentation repository]: https://github.com/magento/devdocs/blob/master/_data/toc/mftf.yml +[MFTF repository]: https://github.com/magento/magento2-functional-testing-framework +[Contribute to Magento Devdocs]: https://github.com/magento/devdocs/blob/master/.github/CONTRIBUTING.md +[MFTF documentation]: https://devdocs.magento.com/mftf/docs/introduction.html +[Basic Template]: https://devdocs.magento.com/guides/v2.3/contributor-guide/templates/basic_template.html 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..adc202cb8 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,151 @@ +# Copyright © Magento, Inc. All rights reserved. +# See COPYING.txt for license details. + +name: CI + +on: [pull_request] + +jobs: + unit-tests: + name: Unit Tests + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + php-versions: ['8.2', '8.3', '8.4'] + 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@v4 + 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: ['8.2', '8.3', '8.4'] + 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@v4 + 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: ['8.2', '8.3', '8.4'] + 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@v4 + 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: ['8.2', '8.3', '8.4'] + + 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@v4 + 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 d39929c6f..419218ba4 100755 --- a/.gitignore +++ b/.gitignore @@ -15,4 +15,11 @@ codeception.yml dev/tests/functional/MFTF.suite.yml dev/tests/functional/_output dev/mftf.log -dev/tests/mftf.log \ No newline at end of file +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 diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 71b1607ee..000000000 --- a/.travis.yml +++ /dev/null @@ -1,14 +0,0 @@ -language: php -php: - - 7.0 - - 7.1 - - 7.2 -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 c8a7efa13..4f80b255a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,975 @@ Magento Functional Testing Framework Changelog ================================================ +5.0.6 +--------- +### Fixes +* Updated copyright check to use the new Adobe copyright. +* Resolved errors caused by whitespace-only comments. +* Addressed deprecation warnings from spomky-labs/otphp. + +5.0.5 +--------- +### Enhancements +* Bumped aws/aws-sdk-php package to 3.344.6 +* Bumped composer/composer to 2.8.9 +* Bumped codeception/codeception to 5.3.2 +* Bumped codeception/module-asserts to 3.2.0 +* Bumped csharpru/vault-php to 4.4.1 +* Bumped laminas/laminas-diactoros to 3.6.0 +* Bumped nikic/php-parser to 5.5.0 +* Bumped php-coveralls/php-coveralls to 2.8.0 +* Bumped behat/gherkin to 4.14.0 +* Bumped symfony/css-selector to 7.3.0 +* Bumped symfony/mime to 6.4.21 +* Bumped symfony/string to 7.3.0 +* Bumped symfony/event-dispatcher to 7.3.0 +* Bumped symfony/filesystem to 7.3.0 +* Bumped symfony/console to 6.4.22 +* Bumped symfony/service-contracts to 3.6.0 + +5.0.4 +--------- +### Enhancements +* Bumped aws/aws-sdk-php package to 3.342.28 +* Bumped composer/composer to 2.8.8 +* Bumped codeception/codeception to 5.2.1 +* Bumped codeception/module-webdriver to 4.0.3 +* Bumped guzzlehttp/guzzle to 7.9.3 +* Bumped monolog/monolog to 3.9.0 +* Updated cache package version to @v4 + +5.0.3 +--------- +### Fixes +* Allowed additional actions to read from credentials file to fix page builder failures. +* Added support for chrome 131 + +5.0.2 +--------- +### Fixes +* Removed support for chrome 131 + +5.0.1 +--------- +### Enhancement +* Provided support for chrome 131 + +5.0.0 +--------- +### Enhancements +* Provided support for PHP 8.4 +* Dropped the support for PHP 8.1 +* Removed unwanted dependent packages +* Removed the dependency of codeception/module-sequence and implemented internal adjustments to address PHP 8.4 deprecations. + +4.8.3 +--------- +### Enhancements +* Bumped aws/aws-sdk-php package to 3.323.4 +* Bumped composer/composer to 2.8.1 +* Bumped laminas/laminas-diactoros to 3.4.1 +* Bumped nikic/php-parser to 5.3.1 +* Bumped squizlabs/php_codesniffer to 3.10.3 +* Remove any unused files remaining after upgrading Codeception. + +4.8.2 +--------- +### Enhancements +* Bumped brainmaestro/composer-git-hook to ^3.0 +* Bumped nikic/php-parser to 5.1.0 +* Bumped monolog/monolog to 3.7.0 +* Bumped guzzlehttp/guzzle to 7.9.2 + +4.8.1 +--------- +### Enhancements +* Bumped allure-codeception to ^2.4 +* Bumped squizlabs/php_codesniffer to 3.10.1 +* Bumped composer/composer to 2.7.7 +* Bumped monolog/monolog to 3.6.0 +* Bumped spomky-labs/otphp to 11.3.0 +* Bumped aws-sdk-php to 3.314.1 + +### Fixes +* Unneeded reports are shown when MFTF Static tests fail. + +4.8.0 +--------- +### Enhancements +* Bumped phpunit/phpunit to ^10.0 +* Bumped allure-framework/allure-phpunit to ^3 +* Bumped codeception/module-webdriver ^4.0 + +### Fixes +* Fixed Class "Magento\FunctionalTestingFramework\StaticCheck\ActionGroupArgumentsCheck" not found on running vendor/bin/mftf build:project --upgrade. + +4.7.2 +--------- +### Enhancements +* Fail static test when introduced filename does not equal the MFTF object name + contained within. + +4.7.1 +--------- +### Enhancements +* Bumped all symfony dependencies to `^6.0 +* Removed abandoned package codacy/coverage +* Removed abandoned package sebastian/phpcpd +* Bumped monolog/monolog to ^3.0 +* Bumped nikic/php-parser to ^5.0 + +4.7.0 +--------- +### Enhancements +* Bumped all symfony dependencies to `^6.0 +* Unit Test for PTS enabled doesn't apply filter fix + +4.6.1 +--------- +### Enhancements +* Supported setting custom timeout value for `magentoCLI` command via an environment variable `MAGENTO_CLI_WAIT_TIMEOUT`. + +4.6.0 +--------- +### Enhancements +* Added Support for PHP 8.3 and enabled PR checks for PHP 8.3. +* Bumped `symfony/console` dependency to `^6.0`. +* Bumped `laminas/laminas-diactoros` dependency to `^3.0`. +* Added no-ansi option to bin/mftf command. + +### Fixes +* Fixed 8.3 deprecation errors. +* Fixed The build with PTS enabled doesn't apply filter issue. +* Change MFTF command to maintain Magento CLI output. + +4.5.0 +--------- +### Enhancements +* Increase browser resolution to 1920x1080. +* Add metadata to ACQE Repositories. +* Add magento admin password to credentials example. + +### Fixes +* Fixed test failure while running any test suite with an argument. + +4.4.2 +--------- +### Fixes +* Fixed PHP 8.2 deprecation warnings. + +4.4.1 +--------- +* Same as previous release + +4.4.0 +--------- +### Enhancements +* Bumped `doctrine/annotations` dependency to `^2.0`. +* Bumped `squizlabs/php_codesniffer` dependency to `^3.7`. +* Bumped `php-webdriver/webdriver` dependency to `^1.14`. +* Bumped `symfony/string` dependency to `^6.3`. +* Bumped `symfony/dotenv` dependency to `^6.3`. +* Bumped `symfony/finder` dependency to `^6.3`. +* Bumped `symfony/http-foundation` dependency to `^6.3`. +* Bumped `symfony/mime` dependency to `^6.3`. +* Enhanced MFTF Modularity Test with "allow failure list". + +4.3.4 +--------- +### Fixes +* Resolving an issue when test is marked as failed due to Suite after section failure + +4.3.3 +--------- +### Enhancements +* Enhance the details in the testgroupmembership.txt file. + +### Fixes +* Fixed MFTF helpers & actionGroups allow duplicate argument names to be passed. + +4.3.2 +--------- +### Enhancements +* 'bootstrap' argument added to indicate that no additional background processes will be run and the jobs complete in the foreground process. + +### Fixes +* Fixed serialization of weakmap exception thrown for every internal exception after codeception upgrade. +* Fixed suites no longer separated by MFTF Suite. + +4.3.1 +--------- +### Fixes +* Fixed cannot bind closure to scope of internal class Exception. +* Fixed broken Mftf doctor command. + +4.3.0 +--------- +### Enhancements +* Bumped `allure-framework/allure-codeception` dependency to `^2.1`. +* Bumped `codeception/codeception` to `^5.0` and upgraded its dependent packages. +* Replaced Yandex methods with Qameta related methods. +* Created methods for modifying step name and for formatting allure. + +### Fixes +* Fixed all issues and exceptions thrown after codeception upgrade. +* Removed dependency of MagentoAllureAdapter in codeception.yml file. + +4.2.1 +--------- +### Fixes + +* Updated constraint for php-webdriver to restrict pulling versions above 1.14.0 + +4.2.0 +--------- +### Fixes + +* Bumped `allure-framework/allure-codeception` dependency to `^1.5` to fix downstream dependency issues in Magento. + + +4.1.0 +--------- +### Enhancements + +* Dropped Support for PHP 8.0 and disabled PR checks for PHP 8.0 +* Allow MFTF generate minimum possible groups runnable by codeception + +### Fixes + +* Fixed Allure report not generating issue +* MFTF displays an appropriate message for unable to connect to Selenium server + +4.0.1 +--------- +### Fixes + +* Fixed HTML files and images not attached to allure report issue + +4.0.0 +--------- +### Enhancements + +* Added Supported for PHP 8.2 and enabled PR checks for PHP 8.2 +* Dropped Support for PHP 7.4 and disabled PR checks for PHP 7.4 +* Upgraded allure-framework/allure-phpunit to its latest version + +### Fixes + +* MFTF deprecation errors fixes +* Composer downgraded from 2.4 to 2.2 due to lamina issue + +3.12.0 +--------- + +### Fixes +* Removed obsolete docs/directories + +3.11.1 +--------- + +### Fixes + +* Removed environment variable MAGENTO_ADMIN_PASSWORD +* Fixed WaitForElementClickable action cannot be used more than once + +3.11.0 +--------- +### Enhancements +* Composer updated to 2.4.2 version +* Static check for duplicate step keys in action group + + +### Fixes + +* Fixed incorrect MFTF test dependencies path +* Removed PHP 7.3 build check from MFTF PR build as PHP 7.3 is no longer supported +* Fixed fatal error when running generate:tests --config parallel -g + + +3.10.3 +--------- + +### Fixes + +* Chrome settings for potential cost reductions + +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 +----- + +### Fixes +* Fixed missing `use` statement in the generate:suite command + +### GitHub Issues +* [#471](https://github.com/magento/magento2-functional-testing-framework/issues/471) -- PHP Fatal error: MftfApplicationConfig not found in GenerateSuiteCommand + +2.5.0 +----- +* Traceability + * Allure output has been enhanced to contain new test artifacts created and used per MFTF step: + * `makeScreenshot` will contain an attachment under its Allure step. + * `seeInCurrentUrl` and all other `Url` asserts now contain an attachment with the expects vs actual comparison. + * `createData` and all other `Data` actions now contain attachments with `Request Body` and `Response Body`. +* Modularity + * Added a new `mftf run:manifest` command to run testManifest files generated by `generate:tests`. + * See DevDocs for details + * `mftf generate/run:test` commands now implicitly generates the `suite` the test exists in. + * If a test exists in multiple suites, it will generate it in all suite contexts. + * `mftf run:test ` will now only run the exact test provided, regardless of what is generated. +* Maintainability + * Added an `--allow-skipped` flag that allows MFTF to ignore the `` annotation. This was added to the following commands: + * `generate:test` + * `run:test` + * `run:group` + * `run:failed` +* Customizability + * `` defined in data.xml can now reference other `` directly. + * See DevDocs for details + * Added vault as an alternative credential storage. + * See DevDocs for details + +### Fixes +* Fixed an issue where `grab` action variables were not substituting correctly when used as an element parameter. +* Framework will not throw a descriptive error when referencing a `$persisted.field$` that does not exist. +* MFTF test materials that `extends=""` itself will no longer cause infinite recursion. +* Fixed an issue where a test could not reference a `$data.field$` whose casing was modified by the API that it used. +* Fixed an issue with the default `functional.suite.yml` where it was incompatible with `symfony/yaml 4.0.0`. +* Improved test generation performance via class refactors (`~10%` faster). + +### GitHub Issues/Pull requests: +* [#377](https://github.com/magento/magento2-functional-testing-framework/pull/377) -- Non-API operations fixes + +2.4.5 +----- +### Fixes +* Fixed an issue where `.credentials` was required when using `` actions with field overrides. + +2.4.4 +----- +### Fixes +* Fixed an issue where `_CREDS` could not be resolved when used in a suite. + +2.4.3 +----- +* 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`. + +### Fixes +* `static-checks` command now properly returns `1` if any static check failed. +* MFTF Console Printer class correctly utilizes `--steps` and other flags passed directly to `codecept commands`. +* `*source` actions correctly print when using `userInput` or `html` attributes. +* XML Comments should no longer throw an error in parsing when used outside `test/actionGroup` + +### GitHub Issues/Pull requests: +* [#703](https://github.com/magento/magento2-functional-testing-framework/pull/403) -- SMALL_CHANGE: Minor change suggested + +2.4.2 +----- +* Traceability + * Test action `stepKey`s are now included in both console output and Allure report. + * XML Comments are now automatically converted into a `` action. + +### Fixes +* Moved `epfremme/swagger-php` dependency to `suggests` block due to a conflict with Magento extensions. + +2.4.1 +----- +* Traceability + * XSD Schema validation is now enabled by default in `generate:tests`, `run:test`, `run:failed`, `run:group` + * `--debug` option for the above commands has been updated to include different debug levels + * See DevDocs for details + +### Fixes +* Fixed an issue where `skipReadiness` attribute would cause false XSD Schema validation errors. + +2.4.0 +----- +### Enhancements +* Maintainability + * Added new `mftf static-checks` command to run new static checks against the attached test codebase + * See DevDocs for details + * Added new `mftf generate:docs` command that generates documentation about attached test codebase + * See DevDocs for details +* Traceability + * Allure reports for tests now contain collapsible sections for `actionGroup`s used in execution. + +### Fixes +* Fixed an issue where `magentoCli` would treat `argument="0"` as a null value. +* Fixed an issue where `amOnPage` and `waitForPwaElementVisible` would not utilize the `timeout` attribute correctly when MagentoPwaWebDriver is enabled. +* Fixed an issue where invalid XML characters would cause Allure to throw an exception without a resulting report. +* Fixed `codeception.dist.yml` configuration for keeping previous test run results. +* PHP Notices are no longer thrown when XML is missing non-necessary attributes. +* Removed unusable `fillSecretField` action from schema. + +### GitHub Issues/Pull requests: +* [#338](https://github.com/magento/magento2-functional-testing-framework/pull/338) -- Return exit codes of process started by 'run:test', 'run:group' or 'run:failed' command +* [#333](https://github.com/magento/magento2-functional-testing-framework/pull/333) -- Added Nginx specific settings to getting started doc +* [#332](https://github.com/magento/magento2-functional-testing-framework/pull/332) -- executeInSelenium action does not generate proper code +* [#318](https://github.com/magento/magento2-functional-testing-framework/pull/318) -- Reduce cyclomatic complexity in Problem Methods +* [#287](https://github.com/magento/magento2-functional-testing-framework/pull/287) -- Update requirements to include php7.3 support + +2.3.14 +----- +### Enhancements +* Maintainability + * `command.php` is now configured with an `idleTimeout` of `60` seconds, which will allow tests to continue execution if a CLI command is hanging indefinitely. + +2.3.13 +----- +### Enhancements +* Traceability + * Failed test steps are now marked with a red `x` in the generated Allure report. + * A failed `suite` `` now correctly causes subsequent tests to marked as `failed` instead of `skipped`. +* Customizability + * Added `waitForPwaElementVisible` and `waitForPwaElementNotVisible` actions. +* Modularity + * Added support for parsing of symlinked modules under `vendor`. + +### Fixes +* Fixed a PHP Fatal error that occurred if the given `MAGENTO_BASE_URL` responded with anything but a `200`. +* Fixed an issue where a test's `` would run twice with Codeception `2.4.x` +* Fixed an issue where tests using `extends` would not correctly override parent test steps +* Test actions can now send an empty string to parameterized selectors. + +### GitHub Issues/Pull requests: +* [#297](https://github.com/magento/magento2-functional-testing-framework/pull/297) -- Allow = to be part of the secret value +* [#267](https://github.com/magento/magento2-functional-testing-framework/pull/267) -- Add PHPUnit missing in dependencies +* [#266](https://github.com/magento/magento2-functional-testing-framework/pull/266) -- General refactor: ext-curl dependency + review of singletones (refactor constructs) +* [#264](https://github.com/magento/magento2-functional-testing-framework/pull/264) -- Use custom Backend domain, refactoring to Executors responsible for calling HTTP endpoints +* [#258](https://github.com/magento/magento2-functional-testing-framework/pull/258) -- Removed unused variables in FunctionCommentSniff.php +* [#256](https://github.com/magento/magento2-functional-testing-framework/pull/256) -- Removed unused variables + +2.3.12 +----- +### Enhancements +* Fetched latest allure-codeception package + +2.3.11 +----- +### Fixes +* `mftf run:failed` now correctly regenerates tests that are in suites that were parallelized (`suite` => `suite_0`, `suite_1`) + +2.3.10 +----- +### 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. +* Fixed an issue where an `element` with no `type` would cause PHP warnings during test runs. + +2.3.9 +----- +### Fixes +* Logic for parallel execution were updated to split default tests and suites from running in one group. + +2.3.8 +----- +### Fixes +* `ModuleResolver` will now only scan under `MAGENTO_BP/app/code/...` and `MAGENTO_BP/vendor/...` for `/Test/Mftf` directories. +* Fixed an issue where `Test.xml` files that did not end with `*Test.xml` would not be scanned for duplicates and other XML validation. + +2.3.7 +----- +### Enhancements +* Traceability + * Test generation errors output xml filename where they were encountered, as well as xml parent nodes where applicable. + * Duplicate element detection now outputs parent element where duplicate was found. +* Maintainability + * Standalone MFTF can now be pointed at a Magento installation folder to generate and execute tests. + * See DevDocs for more information. + * MFTF now checks for `test` and `actionGroup` elements that have the same `name` in the same file. +* Customizability + * Updated prefered syntax for `actionGroup` `argument`s that use `xml.data` (old syntax is still supported) + * Old: `xml.data` + * New: `{{xml.data}}` +* Modularity + * `ModuleResolver` now utilizes each Magento module's `registration.php` to map MFTF test material directories. +### Fixes +* The `waitForPageLoad` action now correctly uses the given `timeout` attribute for all of its checks. +* Firefox compatibility issues in javascript error logging were fixed. +* Fixed an issue where arguments containing `-` would not properly resolve parameterized selectors. +* Fixed an issue where actions using `parameterArray` would not resolve `$persisted.data$` references. +* Fixed an issue where composer installations of Magento would fail to parse MFTF materials under a path `vendor/magento/module-/` + 2.3.6 ----- ### Enhancements @@ -50,15 +1019,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). @@ -109,7 +1078,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. @@ -186,7 +1155,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. @@ -222,7 +1191,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 9687c3b47..02a2b0cd5 100755 --- a/README.md +++ b/README.md @@ -1,74 +1,73 @@ -# Magento Functional Testing Framework - -[![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) +# Magento Functional Testing Framework (MFTF) ---- -## System Requirements -[Magento Functional Testing Framework system requirements](http://devdocs.magento.com/guides/v2.2/magento-functional-testing-framework/getting-started.html#prepare-environment) - ## Installation -To install the Magento Functional Testing Framework, see [Getting Started](http://devdocs.magento.com/guides/v2.2/magento-functional-testing-framework/getting-started.html) -## Contributing -Contributions can take the form of new components or features, changes to existing features, tests, documentation (such as developer guides, user guides, examples, or specifications), bug fixes, optimizations, or just good suggestions. +For the installation guidelines and system requirements, refer to [Getting Started][]. -To learn about how to make a contribution, click [here][1]. - -To open an issue, click [here][2]. +## Contributing -To suggest documentation improvements, click [here][3]. +We would appreciate your contributions to new components or new features, changes to the existing features, tests, documentation, specifications, bug fixes, optimizations, or just good suggestions. +Report about an issue or request features opening a GitHub issue. +Learn more about contributing in our [Contribution Guidelines][]. -[1]: -[2]: -[3]: +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. +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. ### Pull Request Status -Label| Description ----|--- -**accept**| The pull request has been accepted and will be merged into mainline code. -**reject**| The pull request has been rejected and will not be merged into mainline code. Possible reasons can include but are not limited to: issue has already been fixed in another code contribution, or there is an issue with the code contribution. -**needsUpdate**| The Magento Team needs additional information from the reporter to properly prioritize and process the pull request. +| Label | Description | +| --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **accept** | The pull request has been accepted and will be merged into mainline code. | +| **reject** | The pull request has been rejected and will not be merged into mainline code. Possible reasons can include but are not limited to: issue has already been fixed in another code contribution, or there is an issue with the code contribution. | +| **needsUpdate** | The Magento Team needs additional information from the reporter to properly prioritize and process the pull request. | ### Issue Resolution Status -Label| Description ----|--- -**acknowledged**| The Magento Team has validated the issue and an internal ticket has been created. -**needsUpdate**| The Magento Team needs additional information from the reporter to properly prioritize and process the issue or pull request. -**cannot reproduce**| The Magento Team has not confirmed that this issue contains the minimum required information to reproduce. -**non-issue**| The Magento Team has not recognised any issue according to provided information. +| Label | Description | +| -------------------- | ----------------------------------------------------------------------------------------------------------------------------- | +| **acknowledged** | The Magento Team has validated the issue and an internal ticket has been created. | +| **needsUpdate** | The Magento Team needs additional information from the reporter to properly prioritize and process the issue or pull request. | +| **cannot reproduce** | The Magento Team has not confirmed that this issue contains the minimum required information to reproduce. | +| **non-issue** | The Magento Team has not recognized any issue according to provided information. | ### Domains Impacted -Label| Description ----|--- -**PROD**| Affects the Product team (mostly feature requests or business logic change). -**DOC**| Affects Documentation domain. -**TECH**| Affects Architect Group (mostly to make decisions around technology changes). +| Label | Description | +| -------- | ----------------------------------------------------------------------------- | +| **PROD** | Affects the Product team (mostly feature requests or business logic change). | +| **DOC** | Affects Documentation domain. | +| **TECH** | Affects Architect Group (mostly to make decisions around technology changes). | ### Type -Label| Description ----|--- -**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). +| Label | Description | +| --------------- | ----------------------------------------------------------------------------------------------------------------------------- | +| **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 in Magento software or web sites, please e-mail security@magento.com. Please 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. +Stay up-to-date on the latest security news and patches for Magento by signing up for [Security Alert Notifications][]. ## License -Each Magento source file included in this distribution is licensed under AGPL 3.0 +Each Magento source file included in this distribution is licensed under AGPL 3.0. + +See the [license][] or contact [license@magentocommerce.com][] for a copy. -Please see LICENSE_AGPL3.txt for the full text of the AGPL 3.0 license or contact license@magentocommerce.com for a copy. + +[Getting Started]: docs/getting-started.md +[Contribution Guidelines]: .github/CONTRIBUTING.md +[DevDocs Contributing]: https://github.com/magento/devdocs/blob/master/.github/CONTRIBUTING.md +[Security Alert Notifications]: https://magento.com/security/sign-up +[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..649a115ef 100755 --- a/bin/copyright-check +++ b/bin/copyright-check @@ -1,28 +1,38 @@ #!/bin/bash -# Copyright © Magento, Inc. All rights reserved. -# See COPYING.txt for license details. +# Copyright 2017 Adobe +# All Rights Reserved. FILE_EXTENSIONS='.php\|.xml\|.xsd' -BLACKLIST='bin/blacklist.txt' +BLOCKLIST='bin/blocklist.txt' RESULT='' +CURRENT_YEAR=$(date +"%Y") + # 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` -do - if echo `cat $i` | grep -q -v "Copyright © Magento, Inc. All rights reserved."; then - # Copyright is missing - RESULT+="$i\n" +for i in $(git ls-tree --full-tree -r --name-only HEAD | grep $FILE_EXTENSIONS | grep -v -f $BLOCKLIST); do + CONTENT=$(cat "$i") + + # Extract year from copyright line + if echo "$CONTENT" | grep -qE "Copyright [0-9]{4} Adobe"; then + YEAR=$(echo "$CONTENT" | grep -oE "Copyright [0-9]{4} Adobe" | grep -oE "[0-9]{4}") + + if [[ $YEAR -ge 2010 && $YEAR -le $CURRENT_YEAR ]]; then + continue # Valid copyright + else + RESULT+="$i (Invalid year: $YEAR)\n" + fi + else + RESULT+="$i (Missing copyright)\n" fi done if [[ ! -z $RESULT ]]; then - printf "\nTHE FOLLOWING FILES ARE MISSING THE MAGENTO COPYRIGHT:\n\n" - printf " Copyright © Magento, Inc. All rights reserved.\n" - printf " See COPYING.txt for license details.\n\n" + printf "\nTHE FOLLOWING FILES HAVE COPYRIGHT ISSUES:\n\n" + printf "Expected format: Copyright Adobe (year between 2010 and $CURRENT_YEAR)\n\n" printf "$RESULT\n" exit 1 fi 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 13d6ca7d1..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.3.6'); + $application->setVersion($version); /** @var \Magento\FunctionalTestingFramework\Console\CommandListInterface $commandList */ $commandList = new \Magento\FunctionalTestingFramework\Console\CommandList; foreach ($commandList->getCommands() as $command) { diff --git a/bin/phpunit-checks b/bin/phpunit-checks index fad7bcc05..49658b2b1 100755 --- a/bin/phpunit-checks +++ b/bin/phpunit-checks @@ -1,5 +1,5 @@ -# Copyright © Magento, Inc. All rights reserved. -# See COPYING.txt for license details. +# Copyright 2025 Adobe +# All Rights Reserved. # REMEMBER TO UPDATE THE .BAT FILE diff --git a/bin/phpunit-checks.bat b/bin/phpunit-checks.bat index 27914ef1e..cd0ffeed4 100644 --- a/bin/phpunit-checks.bat +++ b/bin/phpunit-checks.bat @@ -1,5 +1,5 @@ -:: Copyright © Magento, Inc. All rights reserved. -:: See COPYING.txt for license details. +:: Copyright 2025 Adobe +:: All Rights Reserved. :: REMEMBER TO UPDATE THE BASH FILE diff --git a/bin/static-checks b/bin/static-checks index c711726e4..b6733ba25 100755 --- a/bin/static-checks +++ b/bin/static-checks @@ -16,8 +16,6 @@ echo "" echo "===============================" echo " COPY PASTE DETECTOR" echo "===============================" -vendor/bin/phpcpd ./src -echo "" echo "===============================" echo " MESS DETECTOR" diff --git a/composer.json b/composer.json index 60d6f2653..57dc77c3b 100755 --- a/composer.json +++ b/composer.json @@ -2,48 +2,62 @@ "name": "magento/magento2-functional-testing-framework", "description": "Magento2 Functional Testing Framework", "type": "library", - "version": "2.3.6", + "version": "5.0.6", "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", - "allure-framework/allure-codeception": "~1.2.6", - "codeception/codeception": "~2.3.4", - "consolidation/robo": "^1.0.0", - "epfremme/swagger-php": "^2.0", - "flow/jsonpath": ">0.2", - "fzaninotto/faker": "^1.6", - "monolog/monolog": "^1.0", + "allure-framework/allure-codeception": "^2.1", + "aws/aws-sdk-php": "^3.132", + "codeception/codeception": "^5.0", + "codeception/module-asserts": "^3.0", + "codeception/module-webdriver": "^4.0", + "composer/composer": "^1.9||^2.0,!=2.2.16", + "csharpru/vault-php": "^4.2.1", + "ext-curl": "*", + "ext-dom": "*", + "ext-iconv": "*", + "ext-intl": "*", + "ext-json": "*", + "ext-openssl": "*", + "guzzlehttp/guzzle": "^7.3.0", + "laminas/laminas-diactoros": "^3.0", + "monolog/monolog": "^2.3||^3.0", "mustache/mustache": "~2.5", - "symfony/process": "^2.8 || ^3.1 || ^4.0", - "vlucas/phpdotenv": "^2.4" + "nikic/php-parser": "^4.4||^5.0", + "php": ">=8.2", + "php-webdriver/webdriver": "^1.14.0", + "spomky-labs/otphp": "^10.0||^11.0", + "symfony/console": "^6.4", + "symfony/dotenv": "^6.4", + "symfony/finder": "^6.4", + "symfony/mime": "^6.4", + "symfony/process": "^6.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", - "doctrine/cache": "<1.7.0", - "codeception/aspect-mock": "^3.0", - "goaop/framework": "2.2.0", - "codacy/coverage": "^1.4", - "phpmd/phpmd": "^2.6.0", - "rregeer/phpunit-coverage-check": "^0.1.4", - "php-coveralls/php-coveralls": "^1.0", - "symfony/stopwatch": "~3.4.6" + "brainmaestro/composer-git-hooks": "^3.0", + "php-coveralls/php-coveralls": "^1.0||^2.2", + "phpmd/phpmd": "^2.8.0", + "phpunit/phpunit": "^10.0", + "squizlabs/php_codesniffer": "~3.10.1" + }, + "suggest": { + "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", + "Magento\\FunctionalTestingFramework\\Tests\\Verification\\": "dev/tests/verification/Tests" } }, "autoload-dev": { "psr-4": { - "tests\\unit\\": "dev/tests/unit" + "tests\\": "dev/tests" } }, "scripts": { diff --git a/composer.lock b/composer.lock index 8ec534fda..8adfd5085 100644 --- a/composer.lock +++ b/composer.lock @@ -4,33 +4,39 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "4dc0a2b0fc82647238bf0892f3b65d0f", + "content-hash": "321fa6ea41d9fbba05f0597ee9cb7f7f", "packages": [ { "name": "allure-framework/allure-codeception", - "version": "1.2.7", + "version": "v2.4.0", "source": { "type": "git", "url": "https://github.com/allure-framework/allure-codeception.git", - "reference": "48598f4b4603b50b663bfe977260113a40912131" + "reference": "854320894b5e65952eb0cafd1555e9efb4543350" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/allure-framework/allure-codeception/zipball/48598f4b4603b50b663bfe977260113a40912131", - "reference": "48598f4b4603b50b663bfe977260113a40912131", + "url": "https://api.github.com/repos/allure-framework/allure-codeception/zipball/854320894b5e65952eb0cafd1555e9efb4543350", + "reference": "854320894b5e65952eb0cafd1555e9efb4543350", "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-commons": "^2.3.1", + "codeception/codeception": "^5.0.3", + "ext-json": "*", + "php": "^8" + }, + "require-dev": { + "psalm/plugin-phpunit": "^0.19.0", + "remorhaz/php-json-data": "^0.5.3", + "remorhaz/php-json-path": "^0.7.7", + "squizlabs/php_codesniffer": "^3.7.2", + "vimeo/psalm": "^5.12" }, "type": "library", "autoload": { - "psr-0": { - "Yandex": "src/" + "psr-4": { + "Qameta\\Allure\\Codeception\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -40,12 +46,17 @@ "authors": [ { "name": "Ivan Krutov", - "email": "vania-pooh@yandex-team.ru", + "email": "vania-pooh@aerokube.com", + "role": "Developer" + }, + { + "name": "Edward Surov", + "email": "zoohie@gmail.com", "role": "Developer" } ], - "description": "A Codeception adapter for Allure report.", - "homepage": "http://allure.qatools.ru/", + "description": "Allure Codeception integration", + "homepage": "https://allurereport.org/", "keywords": [ "allure", "attachments", @@ -55,37 +66,49 @@ "steps", "testing" ], - "time": "2018-03-07T11:18:27+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": "2024-05-28T09:54:01+00:00" }, { - "name": "allure-framework/allure-php-api", - "version": "1.1.4", + "name": "allure-framework/allure-php-commons", + "version": "v2.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-commons2.git", + "reference": "5d7ed5ab510339652163ca1473eb499d4b7ec488" }, "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-commons2/zipball/5d7ed5ab510339652163ca1473eb499d4b7ec488", + "reference": "5d7ed5ab510339652163ca1473eb499d4b7ec488", "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" + "doctrine/annotations": "^1.12 || ^2", + "ext-json": "*", + "php": "^8", + "psr/log": "^1 || ^2 || ^3", + "ramsey/uuid": "^3 || ^4" + }, + "conflict": { + "amphp/byte-stream": "<1.5.1" + }, + "require-dev": { + "jetbrains/phpstorm-attributes": "^1", + "phpunit/phpunit": "^9.6.8", + "psalm/plugin-phpunit": "^0.18.4", + "squizlabs/php_codesniffer": "^3.7.2", + "vimeo/psalm": "^5.12" }, "type": "library", "autoload": { - "psr-0": { - "Yandex": [ - "src/", - "test/" - ] + "psr-4": { + "Qameta\\Allure\\": "src", + "Yandex\\Allure\\Adapter\\": "src/Legacy" } }, "notification-url": "https://packagist.org/downloads/", @@ -94,299 +117,406 @@ ], "authors": [ { - "name": "Ivan Krutov", - "email": "vania-pooh@yandex-team.ru", + "name": "Dmitry Baev", + "email": "baev.dm@gmail.com", + "role": "Developer" + }, + { + "name": "Edward Surov", + "email": "zoohie@gmail.com", "role": "Developer" } ], - "description": "PHP API for Allure adapter", + "description": "Allure PHP commons", "homepage": "http://allure.qatools.ru/", "keywords": [ "allure", - "api", + "commons", "php", - "report" + "report", + "testing" ], - "time": "2016-12-07T12:15:46+00:00" + "support": { + "email": "allure@qameta.io", + "issues": "https://github.com/allure-framework/allure-php-commons2/issues", + "source": "https://github.com/allure-framework/allure-php-commons" + }, + "time": "2023-05-30T10:55:43+00:00" }, { - "name": "behat/gherkin", - "version": "v4.4.5", + "name": "aws/aws-crt-php", + "version": "v1.2.7", "source": { "type": "git", - "url": "https://github.com/Behat/Gherkin.git", - "reference": "5c14cff4f955b17d20d088dec1bde61c0539ec74" + "url": "https://github.com/awslabs/aws-crt-php.git", + "reference": "d71d9906c7bb63a28295447ba12e74723bd3730e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Behat/Gherkin/zipball/5c14cff4f955b17d20d088dec1bde61c0539ec74", - "reference": "5c14cff4f955b17d20d088dec1bde61c0539ec74", + "url": "https://api.github.com/repos/awslabs/aws-crt-php/zipball/d71d9906c7bb63a28295447ba12e74723bd3730e", + "reference": "d71d9906c7bb63a28295447ba12e74723bd3730e", "shasum": "" }, "require": { - "php": ">=5.3.1" + "php": ">=5.5" }, "require-dev": { - "phpunit/phpunit": "~4.5|~5", - "symfony/phpunit-bridge": "~2.7|~3", - "symfony/yaml": "~2.3|~3" + "phpunit/phpunit": "^4.8.35||^5.6.3||^9.5", + "yoast/phpunit-polyfills": "^1.0" }, "suggest": { - "symfony/yaml": "If you want to parse features, represented in YAML files" + "ext-awscrt": "Make sure you install awscrt native extension to use any of the functionality." }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "4.4-dev" - } - }, "autoload": { - "psr-0": { - "Behat\\Gherkin": "src/" - } + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "Apache-2.0" ], "authors": [ { - "name": "Konstantin Kudryashov", - "email": "ever.zet@gmail.com", - "homepage": "http://everzet.com" + "name": "AWS SDK Common Runtime Team", + "email": "aws-sdk-common-runtime@amazon.com" } ], - "description": "Gherkin DSL parser for PHP 5.3", - "homepage": "http://behat.org/", + "description": "AWS Common Runtime for PHP", + "homepage": "https://github.com/awslabs/aws-crt-php", "keywords": [ - "BDD", - "Behat", - "Cucumber", - "DSL", - "gherkin", - "parser" + "amazon", + "aws", + "crt", + "sdk" ], - "time": "2016-10-30T11:50:56+00:00" + "support": { + "issues": "https://github.com/awslabs/aws-crt-php/issues", + "source": "https://github.com/awslabs/aws-crt-php/tree/v1.2.7" + }, + "time": "2024-10-18T22:15:13+00:00" }, { - "name": "codeception/codeception", - "version": "2.3.9", + "name": "aws/aws-sdk-php", + "version": "3.344.6", "source": { "type": "git", - "url": "https://github.com/Codeception/Codeception.git", - "reference": "104f46fa0bde339f1bcc3a375aac21eb36e65a1e" + "url": "https://github.com/aws/aws-sdk-php.git", + "reference": "eb0bc621472592545539329499961a15a3f9f6dc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Codeception/Codeception/zipball/104f46fa0bde339f1bcc3a375aac21eb36e65a1e", - "reference": "104f46fa0bde339f1bcc3a375aac21eb36e65a1e", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/eb0bc621472592545539329499961a15a3f9f6dc", + "reference": "eb0bc621472592545539329499961a15a3f9f6dc", "shasum": "" }, "require": { - "behat/gherkin": "~4.4.0", - "codeception/stub": "^1.0", + "aws/aws-crt-php": "^1.2.3", "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" + "ext-pcre": "*", + "ext-simplexml": "*", + "guzzlehttp/guzzle": "^7.4.5", + "guzzlehttp/promises": "^2.0", + "guzzlehttp/psr7": "^2.4.5", + "mtdowling/jmespath.php": "^2.8.0", + "php": ">=8.1", + "psr/http-message": "^2.0" }, "require-dev": { - "codeception/specify": "~0.3", - "facebook/graph-sdk": "~5.3", - "flow/jsonpath": "~0.2", - "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" + "andrewsville/php-token-reflection": "^1.4", + "aws/aws-php-sns-message-validator": "~1.0", + "behat/behat": "~3.0", + "composer/composer": "^2.7.8", + "dms/phpunit-arraysubset-asserts": "^0.4.0", + "doctrine/cache": "~1.4", + "ext-dom": "*", + "ext-openssl": "*", + "ext-pcntl": "*", + "ext-sockets": "*", + "phpunit/phpunit": "^5.6.3 || ^8.5 || ^9.5", + "psr/cache": "^2.0 || ^3.0", + "psr/simple-cache": "^2.0 || ^3.0", + "sebastian/comparator": "^1.2.3 || ^4.0 || ^5.0", + "symfony/filesystem": "^v6.4.0 || ^v7.1.0", + "yoast/phpunit-polyfills": "^2.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", - "stecman/symfony-console-completion": "For BASH autocompletion", - "symfony/phpunit-bridge": "For phpunit-bridge support" + "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" }, - "bin": [ - "codecept" - ], "type": "library", "extra": { - "branch-alias": [] + "branch-alias": { + "dev-master": "3.0-dev" + } }, "autoload": { + "files": [ + "src/functions.php" + ], "psr-4": { - "Codeception\\": "src\\Codeception", - "Codeception\\Extension\\": "ext" - } + "Aws\\": "src/" + }, + "exclude-from-classmap": [ + "src/data/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "Apache-2.0" ], "authors": [ { - "name": "Michael Bodnarchuk", - "email": "davert@mail.ua", - "homepage": "http://codegyre.com" + "name": "Amazon Web Services", + "homepage": "http://aws.amazon.com" } ], - "description": "BDD-style testing framework", - "homepage": "http://codeception.com/", + "description": "AWS SDK for PHP - Use Amazon Web Services in your PHP project", + "homepage": "http://aws.amazon.com/sdkforphp", "keywords": [ - "BDD", - "TDD", - "acceptance testing", - "functional testing", - "unit testing" - ], - "time": "2018-02-26T23:29:41+00:00" + "amazon", + "aws", + "cloud", + "dynamodb", + "ec2", + "glacier", + "s3", + "sdk" + ], + "support": { + "forum": "https://github.com/aws/aws-sdk-php/discussions", + "issues": "https://github.com/aws/aws-sdk-php/issues", + "source": "https://github.com/aws/aws-sdk-php/tree/3.344.6" + }, + "time": "2025-06-12T18:03:59+00:00" }, { - "name": "codeception/stub", - "version": "1.0.4", + "name": "behat/gherkin", + "version": "v4.14.0", "source": { "type": "git", - "url": "https://github.com/Codeception/Stub.git", - "reference": "681b62348837a5ef07d10d8a226f5bc358cc8805" + "url": "https://github.com/Behat/Gherkin.git", + "reference": "34c9b59c59355a7b4c53b9f041c8dbd1c8acc3b4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Codeception/Stub/zipball/681b62348837a5ef07d10d8a226f5bc358cc8805", - "reference": "681b62348837a5ef07d10d8a226f5bc358cc8805", + "url": "https://api.github.com/repos/Behat/Gherkin/zipball/34c9b59c59355a7b4c53b9f041c8dbd1c8acc3b4", + "reference": "34c9b59c59355a7b4c53b9f041c8dbd1c8acc3b4", "shasum": "" }, "require": { - "phpunit/phpunit-mock-objects": ">2.3 <7.0" + "composer-runtime-api": "^2.2", + "php": "8.1.* || 8.2.* || 8.3.* || 8.4.*" }, "require-dev": { - "phpunit/phpunit": ">=4.8 <8.0" + "cucumber/gherkin-monorepo": "dev-gherkin-v32.1.1", + "friendsofphp/php-cs-fixer": "^3.65", + "mikey179/vfsstream": "^1.6", + "phpstan/extension-installer": "^1", + "phpstan/phpstan": "^2", + "phpstan/phpstan-phpunit": "^2", + "phpunit/phpunit": "^10.5", + "symfony/yaml": "^5.4 || ^6.4 || ^7.0" + }, + "suggest": { + "symfony/yaml": "If you want to parse features, represented in YAML files" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.x-dev" + } + }, "autoload": { "psr-4": { - "Codeception\\": "src/" + "Behat\\Gherkin\\": "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": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "https://everzet.com" + } + ], + "description": "Gherkin DSL parser for PHP", + "homepage": "https://behat.org/", + "keywords": [ + "BDD", + "Behat", + "Cucumber", + "DSL", + "gherkin", + "parser" + ], + "support": { + "issues": "https://github.com/Behat/Gherkin/issues", + "source": "https://github.com/Behat/Gherkin/tree/v4.14.0" + }, + "time": "2025-05-23T15:06:40+00:00" }, { - "name": "consolidation/annotated-command", - "version": "2.8.5", + "name": "brick/math", + "version": "0.13.1", "source": { "type": "git", - "url": "https://github.com/consolidation/annotated-command.git", - "reference": "1e8ff512072422b850b44aa721b5b303e4a5ebb3" + "url": "https://github.com/brick/math.git", + "reference": "fc7ed316430118cc7836bf45faff18d5dfc8de04" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/consolidation/annotated-command/zipball/1e8ff512072422b850b44aa721b5b303e4a5ebb3", - "reference": "1e8ff512072422b850b44aa721b5b303e4a5ebb3", + "url": "https://api.github.com/repos/brick/math/zipball/fc7ed316430118cc7836bf45faff18d5dfc8de04", + "reference": "fc7ed316430118cc7836bf45faff18d5dfc8de04", "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" + "php": "^8.1" }, "require-dev": { - "g1a/composer-test-scenarios": "^2", - "phpunit/phpunit": "^6", - "satooshi/php-coveralls": "^2", - "squizlabs/php_codesniffer": "^2.7" + "php-coveralls/php-coveralls": "^2.2", + "phpunit/phpunit": "^10.1", + "vimeo/psalm": "6.8.8" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.x-dev" - } - }, "autoload": { "psr-4": { - "Consolidation\\AnnotatedCommand\\": "src" + "Brick\\Math\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], - "authors": [ - { - "name": "Greg Anderson", - "email": "greg.1.anderson@greenknowe.org" - } - ], - "description": "Initialize Symfony Console commands from annotated command class methods.", - "time": "2018-08-18T23:51:49+00:00" + "description": "Arbitrary-precision arithmetic library", + "keywords": [ + "Arbitrary-precision", + "BigInteger", + "BigRational", + "arithmetic", + "bigdecimal", + "bignum", + "bignumber", + "brick", + "decimal", + "integer", + "math", + "mathematics", + "rational" + ], + "support": { + "issues": "https://github.com/brick/math/issues", + "source": "https://github.com/brick/math/tree/0.13.1" + }, + "funding": [ + { + "url": "https://github.com/BenMorel", + "type": "github" + } + ], + "time": "2025-03-29T13:50:30+00:00" }, { - "name": "consolidation/config", - "version": "1.1.0", + "name": "codeception/codeception", + "version": "5.3.2", "source": { "type": "git", - "url": "https://github.com/consolidation/config.git", - "reference": "c9fc25e9088a708637e18a256321addc0670e578" + "url": "https://github.com/Codeception/Codeception.git", + "reference": "582112d7a603d575e41638df1e96900b10ae91b8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/consolidation/config/zipball/c9fc25e9088a708637e18a256321addc0670e578", - "reference": "c9fc25e9088a708637e18a256321addc0670e578", + "url": "https://api.github.com/repos/Codeception/Codeception/zipball/582112d7a603d575e41638df1e96900b10ae91b8", + "reference": "582112d7a603d575e41638df1e96900b10ae91b8", "shasum": "" }, "require": { - "dflydev/dot-access-data": "^1.1.0", - "grasmash/expander": "^1", - "php": ">=5.4.0" + "behat/gherkin": "^4.12", + "codeception/lib-asserts": "^2.2", + "codeception/stub": "^4.1", + "ext-curl": "*", + "ext-json": "*", + "ext-mbstring": "*", + "php": "^8.2", + "phpunit/php-code-coverage": "^9.2 | ^10.0 | ^11.0 | ^12.0", + "phpunit/php-text-template": "^2.0 | ^3.0 | ^4.0 | ^5.0", + "phpunit/php-timer": "^5.0.3 | ^6.0 | ^7.0 | ^8.0", + "phpunit/phpunit": "^9.5.20 | ^10.0 | ^11.0 | ^12.0", + "psy/psysh": "^0.11.2 | ^0.12", + "sebastian/comparator": "^4.0.5 | ^5.0 | ^6.0 | ^7.0", + "sebastian/diff": "^4.0.3 | ^5.0 | ^6.0 | ^7.0", + "symfony/console": ">=5.4.24 <8.0", + "symfony/css-selector": ">=5.4.24 <8.0", + "symfony/event-dispatcher": ">=5.4.24 <8.0", + "symfony/finder": ">=5.4.24 <8.0", + "symfony/var-dumper": ">=5.4.24 <8.0", + "symfony/yaml": ">=5.4.24 <8.0" + }, + "conflict": { + "codeception/lib-innerbrowser": "<3.1.3", + "codeception/module-filesystem": "<3.0", + "codeception/module-phpbrowser": "<2.5" + }, + "replace": { + "codeception/phpunit-wrapper": "*" }, "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" + "codeception/lib-innerbrowser": "*@dev", + "codeception/lib-web": "*@dev", + "codeception/module-asserts": "*@dev", + "codeception/module-cli": "*@dev", + "codeception/module-db": "*@dev", + "codeception/module-filesystem": "*@dev", + "codeception/module-phpbrowser": "*@dev", + "codeception/module-webdriver": "*@dev", + "codeception/util-universalframework": "*@dev", + "doctrine/orm": "^3.3", + "ext-simplexml": "*", + "jetbrains/phpstorm-attributes": "^1.0", + "laravel-zero/phar-updater": "^1.4", + "php-webdriver/webdriver": "^1.15", + "stecman/symfony-console-completion": "^0.14", + "symfony/dotenv": ">=5.4.24 <8.0", + "symfony/error-handler": ">=5.4.24 <8.0", + "symfony/process": ">=5.4.24 <8.0", + "vlucas/phpdotenv": "^5.1" }, "suggest": { - "symfony/yaml": "Required to use Consolidation\\Config\\Loader\\YamlConfigLoader" + "codeception/specify": "BDD-style code blocks", + "codeception/verify": "BDD-style assertions", + "ext-simplexml": "For loading params from XML files", + "stecman/symfony-console-completion": "For BASH autocompletion", + "symfony/dotenv": "For loading params from .env files", + "symfony/phpunit-bridge": "For phpunit-bridge support", + "vlucas/phpdotenv": "For loading params from .env files" }, + "bin": [ + "codecept" + ], "type": "library", "extra": { "branch-alias": { - "dev-master": "1.x-dev" + "dev-main": "5.2.x-dev" } }, "autoload": { + "files": [ + "functions.php" + ], "psr-4": { - "Consolidation\\Config\\": "src" - } + "Codeception\\": "src/Codeception", + "Codeception\\Extension\\": "ext" + }, + "classmap": [ + "src/PHPUnit/TestCase.php" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -394,48 +524,56 @@ ], "authors": [ { - "name": "Greg Anderson", - "email": "greg.1.anderson@greenknowe.org" + "name": "Michael Bodnarchuk", + "email": "davert.ua@gmail.com", + "homepage": "https://codeception.com" + } + ], + "description": "BDD-style testing framework", + "homepage": "https://codeception.com/", + "keywords": [ + "BDD", + "TDD", + "acceptance testing", + "functional testing", + "unit testing" + ], + "support": { + "issues": "https://github.com/Codeception/Codeception/issues", + "source": "https://github.com/Codeception/Codeception/tree/5.3.2" + }, + "funding": [ + { + "url": "https://opencollective.com/codeception", + "type": "open_collective" } ], - "description": "Provide configuration services for a commandline tool.", - "time": "2018-08-07T22:57:00+00:00" + "time": "2025-05-26T07:47:39+00:00" }, { - "name": "consolidation/log", - "version": "1.0.6", + "name": "codeception/lib-asserts", + "version": "2.2.0", "source": { "type": "git", - "url": "https://github.com/consolidation/log.git", - "reference": "dfd8189a771fe047bf3cd669111b2de5f1c79395" + "url": "https://github.com/Codeception/lib-asserts.git", + "reference": "06750a60af3ebc66faab4313981accec1be4eefc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/consolidation/log/zipball/dfd8189a771fe047bf3cd669111b2de5f1c79395", - "reference": "dfd8189a771fe047bf3cd669111b2de5f1c79395", + "url": "https://api.github.com/repos/Codeception/lib-asserts/zipball/06750a60af3ebc66faab4313981accec1be4eefc", + "reference": "06750a60af3ebc66faab4313981accec1be4eefc", "shasum": "" }, "require": { - "php": ">=5.5.0", - "psr/log": "~1.0", - "symfony/console": "^2.8|^3|^4" - }, - "require-dev": { - "g1a/composer-test-scenarios": "^1", - "phpunit/phpunit": "4.*", - "satooshi/php-coveralls": "^2", - "squizlabs/php_codesniffer": "2.*" + "codeception/phpunit-wrapper": "^7.7.1 | ^8.0.3 | ^9.0", + "ext-dom": "*", + "php": "^7.4 | ^8.0" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.x-dev" - } - }, "autoload": { - "psr-4": { - "Consolidation\\Log\\": "src" - } + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -443,54 +581,61 @@ ], "authors": [ { - "name": "Greg Anderson", - "email": "greg.1.anderson@greenknowe.org" + "name": "Michael Bodnarchuk", + "email": "davert@mail.ua", + "homepage": "http://codegyre.com" + }, + { + "name": "Gintautas Miselis" + }, + { + "name": "Gustavo Nieves", + "homepage": "https://medium.com/@ganieves" } ], - "description": "Improved Psr-3 / Psr\\Log logger based on Symfony Console components.", - "time": "2018-05-25T18:14:39+00:00" + "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/2.2.0" + }, + "time": "2025-03-10T20:41:33+00:00" }, { - "name": "consolidation/output-formatters", - "version": "3.2.1", + "name": "codeception/lib-web", + "version": "1.0.7", "source": { "type": "git", - "url": "https://github.com/consolidation/output-formatters.git", - "reference": "d78ef59aea19d3e2e5a23f90a055155ee78a0ad5" + "url": "https://github.com/Codeception/lib-web.git", + "reference": "1444ccc9b1d6721f3ced8703c8f4a9041b80df93" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/consolidation/output-formatters/zipball/d78ef59aea19d3e2e5a23f90a055155ee78a0ad5", - "reference": "d78ef59aea19d3e2e5a23f90a055155ee78a0ad5", + "url": "https://api.github.com/repos/Codeception/lib-web/zipball/1444ccc9b1d6721f3ced8703c8f4a9041b80df93", + "reference": "1444ccc9b1d6721f3ced8703c8f4a9041b80df93", "shasum": "" }, "require": { - "php": ">=5.4.0", - "symfony/console": "^2.8|^3|^4", - "symfony/finder": "^2.5|^3|^4" + "ext-mbstring": "*", + "guzzlehttp/psr7": "^2.0", + "php": "^8.1", + "phpunit/phpunit": "^9.5 | ^10.0 | ^11.0 | ^12", + "symfony/css-selector": ">=4.4.24 <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" + "conflict": { + "codeception/codeception": "<5.0.0-alpha3" }, - "suggest": { - "symfony/var-dumper": "For using the var_dump formatter" + "require-dev": { + "php-webdriver/webdriver": "^1.12" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.x-dev" - } - }, "autoload": { - "psr-4": { - "Consolidation\\OutputFormatters\\": "src" - } + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -498,80 +643,47 @@ ], "authors": [ { - "name": "Greg Anderson", - "email": "greg.1.anderson@greenknowe.org" + "name": "Gintautas Miselis" } ], - "description": "Format text by applying transformations provided by plug-in formatters.", - "time": "2018-05-25T18:02:34+00:00" + "description": "Library containing files used by module-webdriver and lib-innerbrowser or module-phpbrowser", + "homepage": "https://codeception.com/", + "keywords": [ + "codeception" + ], + "support": { + "issues": "https://github.com/Codeception/lib-web/issues", + "source": "https://github.com/Codeception/lib-web/tree/1.0.7" + }, + "time": "2025-02-09T12:05:55+00:00" }, { - "name": "consolidation/robo", - "version": "1.3.1", + "name": "codeception/module-asserts", + "version": "3.2.0", "source": { "type": "git", - "url": "https://github.com/consolidation/Robo.git", - "reference": "31f2d2562c4e1dcde70f2659eefd59aa9c7f5b2d" + "url": "https://github.com/Codeception/module-asserts.git", + "reference": "eb1f7c980423888f3def5116635754ae4a75bd47" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/consolidation/Robo/zipball/31f2d2562c4e1dcde70f2659eefd59aa9c7f5b2d", - "reference": "31f2d2562c4e1dcde70f2659eefd59aa9c7f5b2d", + "url": "https://api.github.com/repos/Codeception/module-asserts/zipball/eb1f7c980423888f3def5116635754ae4a75bd47", + "reference": "eb1f7c980423888f3def5116635754ae4a75bd47", "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" + "codeception/codeception": "*@dev", + "codeception/lib-asserts": "^2.2", + "php": "^8.2" }, - "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." + "conflict": { + "codeception/codeception": "<5.0" }, - "bin": [ - "robo" - ], "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.x-dev", - "dev-state": "1.x-dev" - } - }, "autoload": { - "psr-4": { - "Robo\\": "src" - } + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -579,45 +691,61 @@ ], "authors": [ { - "name": "Davert", - "email": "davert.php@resend.cc" + "name": "Michael Bodnarchuk" + }, + { + "name": "Gintautas Miselis" + }, + { + "name": "Gustavo Nieves", + "homepage": "https://medium.com/@ganieves" } ], - "description": "Modern task runner", - "time": "2018-08-17T18:44:18+00:00" + "description": "Codeception module containing various assertions", + "homepage": "https://codeception.com/", + "keywords": [ + "assertions", + "asserts", + "codeception" + ], + "support": { + "issues": "https://github.com/Codeception/module-asserts/issues", + "source": "https://github.com/Codeception/module-asserts/tree/3.2.0" + }, + "time": "2025-05-02T02:33:11+00:00" }, { - "name": "consolidation/self-update", - "version": "1.1.3", + "name": "codeception/module-webdriver", + "version": "4.0.3", "source": { "type": "git", - "url": "https://github.com/consolidation/self-update.git", - "reference": "de33822f907e0beb0ffad24cf4b1b4fae5ada318" + "url": "https://github.com/Codeception/module-webdriver.git", + "reference": "551d214ddd57a9539acf1123d7c56ec82b1004df" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/consolidation/self-update/zipball/de33822f907e0beb0ffad24cf4b1b4fae5ada318", - "reference": "de33822f907e0beb0ffad24cf4b1b4fae5ada318", + "url": "https://api.github.com/repos/Codeception/module-webdriver/zipball/551d214ddd57a9539acf1123d7c56ec82b1004df", + "reference": "551d214ddd57a9539acf1123d7c56ec82b1004df", "shasum": "" }, "require": { - "php": ">=5.5.0", - "symfony/console": "^2.8|^3|^4", - "symfony/filesystem": "^2.5|^3|^4" + "codeception/codeception": "^5.0.8", + "codeception/lib-web": "^1.0.1", + "codeception/stub": "^4.0", + "ext-json": "*", + "ext-mbstring": "*", + "php": "^8.1", + "php-webdriver/webdriver": "^1.14.0", + "phpunit/phpunit": "^10.0 || ^11.0 || ^12.0" }, - "bin": [ - "scripts/release" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.x-dev" - } + "suggest": { + "codeception/phpbuiltinserver": "Start and stop PHP built-in web server for your tests" }, + "type": "library", "autoload": { - "psr-4": { - "SelfUpdate\\": "src" - } + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -625,74 +753,103 @@ ], "authors": [ { - "name": "Greg Anderson", - "email": "greg.1.anderson@greenknowe.org" + "name": "Michael Bodnarchuk" }, { - "name": "Alexander Menk", - "email": "menk@mestrona.net" + "name": "Gintautas Miselis" + }, + { + "name": "Zaahid Bateson" } ], - "description": "Provides a self:update command for Symfony Console applications.", - "time": "2018-08-24T17:01:46+00:00" + "description": "WebDriver module for Codeception", + "homepage": "https://codeception.com/", + "keywords": [ + "acceptance-testing", + "browser-testing", + "codeception" + ], + "support": { + "issues": "https://github.com/Codeception/module-webdriver/issues", + "source": "https://github.com/Codeception/module-webdriver/tree/4.0.3" + }, + "time": "2025-02-14T07:10:05+00:00" }, { - "name": "container-interop/container-interop", - "version": "1.2.0", + "name": "codeception/stub", + "version": "4.1.4", "source": { "type": "git", - "url": "https://github.com/container-interop/container-interop.git", - "reference": "79cbf1341c22ec75643d841642dd5d6acd83bdb8" + "url": "https://github.com/Codeception/Stub.git", + "reference": "6ce453073a0c220b254dd7f4383645615e4071c3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/container-interop/container-interop/zipball/79cbf1341c22ec75643d841642dd5d6acd83bdb8", - "reference": "79cbf1341c22ec75643d841642dd5d6acd83bdb8", + "url": "https://api.github.com/repos/Codeception/Stub/zipball/6ce453073a0c220b254dd7f4383645615e4071c3", + "reference": "6ce453073a0c220b254dd7f4383645615e4071c3", "shasum": "" }, "require": { - "psr/container": "^1.0" + "php": "^7.4 | ^8.0", + "phpunit/phpunit": "^8.4 | ^9.0 | ^10.0 | ^11 | ^12" + }, + "conflict": { + "codeception/codeception": "<5.0.6" + }, + "require-dev": { + "consolidation/robo": "^3.0" }, "type": "library", "autoload": { "psr-4": { - "Interop\\Container\\": "src/Interop/Container/" + "Codeception\\": "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" + "description": "Flexible Stub wrapper for PHPUnit's Mock Builder", + "support": { + "issues": "https://github.com/Codeception/Stub/issues", + "source": "https://github.com/Codeception/Stub/tree/4.1.4" + }, + "time": "2025-02-14T06:56:33+00:00" }, { - "name": "dflydev/dot-access-data", - "version": "v1.1.0", + "name": "composer/ca-bundle", + "version": "1.5.7", "source": { "type": "git", - "url": "https://github.com/dflydev/dflydev-dot-access-data.git", - "reference": "3fbd874921ab2c041e899d044585a2ab9795df8a" + "url": "https://github.com/composer/ca-bundle.git", + "reference": "d665d22c417056996c59019579f1967dfe5c1e82" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/dflydev/dflydev-dot-access-data/zipball/3fbd874921ab2c041e899d044585a2ab9795df8a", - "reference": "3fbd874921ab2c041e899d044585a2ab9795df8a", + "url": "https://api.github.com/repos/composer/ca-bundle/zipball/d665d22c417056996c59019579f1967dfe5c1e82", + "reference": "d665d22c417056996c59019579f1967dfe5c1e82", "shasum": "" }, "require": { - "php": ">=5.3.2" + "ext-openssl": "*", + "ext-pcre": "*", + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^8 || ^9", + "psr/log": "^1.0 || ^2.0 || ^3.0", + "symfony/process": "^4.0 || ^5.0 || ^6.0 || ^7.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-main": "1.x-dev" } }, "autoload": { - "psr-0": { - "Dflydev\\DotAccessData": "src" + "psr-4": { + "Composer\\CaBundle\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -701,62 +858,76 @@ ], "authors": [ { - "name": "Dragonfly Development Inc.", - "email": "info@dflydev.com", - "homepage": "http://dflydev.com" + "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.5.7" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" }, { - "name": "Beau Simensen", - "email": "beau@dflydev.com", - "homepage": "http://beausimensen.com" + "url": "https://github.com/composer", + "type": "github" }, { - "name": "Carlos Frutos", - "email": "carlos@kiwing.it", - "homepage": "https://github.com/cfrutos" + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "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": "2025-05-26T15:08:54+00:00" }, { - "name": "doctrine/annotations", - "version": "v1.4.0", + "name": "composer/class-map-generator", + "version": "1.6.1", "source": { "type": "git", - "url": "https://github.com/doctrine/annotations.git", - "reference": "54cacc9b81758b14e3ce750f205a393d52339e97" + "url": "https://github.com/composer/class-map-generator.git", + "reference": "134b705ddb0025d397d8318a75825fe3c9d1da34" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/annotations/zipball/54cacc9b81758b14e3ce750f205a393d52339e97", - "reference": "54cacc9b81758b14e3ce750f205a393d52339e97", + "url": "https://api.github.com/repos/composer/class-map-generator/zipball/134b705ddb0025d397d8318a75825fe3c9d1da34", + "reference": "134b705ddb0025d397d8318a75825fe3c9d1da34", "shasum": "" }, "require": { - "doctrine/lexer": "1.*", - "php": "^5.6 || ^7.0" + "composer/pcre": "^2.1 || ^3.1", + "php": "^7.2 || ^8.0", + "symfony/finder": "^4.4 || ^5.3 || ^6 || ^7" }, "require-dev": { - "doctrine/cache": "1.*", - "phpunit/phpunit": "^5.7" + "phpstan/phpstan": "^1.12 || ^2", + "phpstan/phpstan-deprecation-rules": "^1 || ^2", + "phpstan/phpstan-phpunit": "^1 || ^2", + "phpstan/phpstan-strict-rules": "^1.1 || ^2", + "phpunit/phpunit": "^8", + "symfony/filesystem": "^5.4 || ^6" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.4.x-dev" + "dev-main": "1.x-dev" } }, "autoload": { "psr-4": { - "Doctrine\\Common\\Annotations\\": "lib/Doctrine/Common/Annotations" + "Composer\\ClassMapGenerator\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -765,65 +936,102 @@ ], "authors": [ { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "https://seld.be" + } + ], + "description": "Utilities to scan PHP code and generate class maps.", + "keywords": [ + "classmap" + ], + "support": { + "issues": "https://github.com/composer/class-map-generator/issues", + "source": "https://github.com/composer/class-map-generator/tree/1.6.1" + }, + "funding": [ { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" + "url": "https://packagist.com", + "type": "custom" }, { - "name": "Jonathan Wage", - "email": "jonwage@gmail.com" + "url": "https://github.com/composer", + "type": "github" }, { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" } ], - "description": "Docblock Annotations Parser", - "homepage": "http://www.doctrine-project.org", - "keywords": [ - "annotations", - "docblock", - "parser" - ], - "time": "2017-02-24T16:22:25+00:00" + "time": "2025-03-24T13:50:44+00:00" }, { - "name": "doctrine/collections", - "version": "v1.4.0", + "name": "composer/composer", + "version": "2.8.9", "source": { "type": "git", - "url": "https://github.com/doctrine/collections.git", - "reference": "1a4fb7e902202c33cce8c55989b945612943c2ba" + "url": "https://github.com/composer/composer.git", + "reference": "b4e6bff2db7ce756ddb77ecee958a0f41f42bd9d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/collections/zipball/1a4fb7e902202c33cce8c55989b945612943c2ba", - "reference": "1a4fb7e902202c33cce8c55989b945612943c2ba", + "url": "https://api.github.com/repos/composer/composer/zipball/b4e6bff2db7ce756ddb77ecee958a0f41f42bd9d", + "reference": "b4e6bff2db7ce756ddb77ecee958a0f41f42bd9d", "shasum": "" }, "require": { - "php": "^5.6 || ^7.0" + "composer/ca-bundle": "^1.5", + "composer/class-map-generator": "^1.4.0", + "composer/metadata-minifier": "^1.0", + "composer/pcre": "^2.2 || ^3.2", + "composer/semver": "^3.3", + "composer/spdx-licenses": "^1.5.7", + "composer/xdebug-handler": "^2.0.2 || ^3.0.3", + "justinrainbow/json-schema": "^6.3.1", + "php": "^7.2.5 || ^8.0", + "psr/log": "^1.0 || ^2.0 || ^3.0", + "react/promise": "^2.11 || ^3.2", + "seld/jsonlint": "^1.4", + "seld/phar-utils": "^1.2", + "seld/signal-handler": "^2.0", + "symfony/console": "^5.4.35 || ^6.3.12 || ^7.0.3", + "symfony/filesystem": "^5.4.35 || ^6.3.12 || ^7.0.3", + "symfony/finder": "^5.4.35 || ^6.3.12 || ^7.0.3", + "symfony/polyfill-php73": "^1.24", + "symfony/polyfill-php80": "^1.24", + "symfony/polyfill-php81": "^1.24", + "symfony/process": "^5.4.35 || ^6.3.12 || ^7.0.3" }, "require-dev": { - "doctrine/coding-standard": "~0.1@dev", - "phpunit/phpunit": "^5.7" + "phpstan/phpstan": "^1.11.8", + "phpstan/phpstan-deprecation-rules": "^1.2.0", + "phpstan/phpstan-phpunit": "^1.4.0", + "phpstan/phpstan-strict-rules": "^1.6.0", + "phpstan/phpstan-symfony": "^1.4.0", + "symfony/phpunit-bridge": "^6.4.3 || ^7.0.1" + }, + "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" }, + "bin": [ + "bin/composer" + ], "type": "library", "extra": { + "phpstan": { + "includes": [ + "phpstan/rules.neon" + ] + }, "branch-alias": { - "dev-master": "1.3.x-dev" + "dev-main": "2.8-dev" } }, "autoload": { - "psr-0": { - "Doctrine\\Common\\Collections\\": "lib/" + "psr-4": { + "Composer\\": "src/Composer/" } }, "notification-url": "https://packagist.org/downloads/", @@ -832,68 +1040,76 @@ ], "authors": [ { - "name": "Roman Borschel", - "email": "roman@code-factory.org" + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "https://www.naderman.de" }, { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.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", + "security": "https://github.com/composer/composer/security/policy", + "source": "https://github.com/composer/composer/tree/2.8.9" + }, + "funding": [ { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" + "url": "https://packagist.com", + "type": "custom" }, { - "name": "Jonathan Wage", - "email": "jonwage@gmail.com" + "url": "https://github.com/composer", + "type": "github" }, { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" } ], - "description": "Collections Abstraction library", - "homepage": "http://www.doctrine-project.org", - "keywords": [ - "array", - "collections", - "iterator" - ], - "time": "2017-01-03T10:49:41+00:00" + "time": "2025-05-13T12:01:37+00:00" }, { - "name": "doctrine/instantiator", - "version": "1.0.5", + "name": "composer/metadata-minifier", + "version": "1.0.0", "source": { "type": "git", - "url": "https://github.com/doctrine/instantiator.git", - "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d" + "url": "https://github.com/composer/metadata-minifier.git", + "reference": "c549d23829536f0d0e984aaabbf02af91f443207" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/8e884e78f9f0eb1329e445619e04456e64d8051d", - "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d", + "url": "https://api.github.com/repos/composer/metadata-minifier/zipball/c549d23829536f0d0e984aaabbf02af91f443207", + "reference": "c549d23829536f0d0e984aaabbf02af91f443207", "shasum": "" }, "require": { - "php": ">=5.3,<8.0-DEV" + "php": "^5.3.2 || ^7.0 || ^8.0" }, "require-dev": { - "athletic/athletic": "~0.1.8", - "ext-pdo": "*", - "ext-phar": "*", - "phpunit/phpunit": "~4.0", - "squizlabs/php_codesniffer": "~2.0" + "composer/composer": "^2", + "phpstan/phpstan": "^0.12.55", + "symfony/phpunit-bridge": "^4.2 || ^5" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-main": "1.x-dev" } }, "autoload": { "psr-4": { - "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + "Composer\\MetadataMinifier\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -902,45 +1118,75 @@ ], "authors": [ { - "name": "Marco Pivetta", - "email": "ocramius@gmail.com", - "homepage": "http://ocramius.github.com/" + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" } ], - "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", - "homepage": "https://github.com/doctrine/instantiator", + "description": "Small utility library that handles metadata minification and expansion.", "keywords": [ - "constructor", - "instantiate" + "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" + } ], - "time": "2015-06-14T21:17:01+00:00" + "time": "2021-04-07T13:37:33+00:00" }, { - "name": "doctrine/lexer", - "version": "v1.0.1", + "name": "composer/pcre", + "version": "3.3.2", "source": { "type": "git", - "url": "https://github.com/doctrine/lexer.git", - "reference": "83893c552fd2045dd78aef794c31e694c37c0b8c" + "url": "https://github.com/composer/pcre.git", + "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/lexer/zipball/83893c552fd2045dd78aef794c31e694c37c0b8c", - "reference": "83893c552fd2045dd78aef794c31e694c37c0b8c", + "url": "https://api.github.com/repos/composer/pcre/zipball/b2bed4734f0cc156ee1fe9c0da2550420d99a21e", + "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e", "shasum": "" }, "require": { - "php": ">=5.3.2" + "php": "^7.4 || ^8.0" + }, + "conflict": { + "phpstan/phpstan": "<1.11.10" + }, + "require-dev": { + "phpstan/phpstan": "^1.12 || ^2", + "phpstan/phpstan-strict-rules": "^1 || ^2", + "phpunit/phpunit": "^8 || ^9" }, "type": "library", "extra": { + "phpstan": { + "includes": [ + "extension.neon" + ] + }, "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-main": "3.x-dev" } }, "autoload": { - "psr-0": { - "Doctrine\\Common\\Lexer\\": "lib/" + "psr-4": { + "Composer\\Pcre\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -949,57 +1195,68 @@ ], "authors": [ { - "name": "Roman Borschel", - "email": "roman@code-factory.org" + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "PCRE wrapping library that offers type-safe preg_* replacements.", + "keywords": [ + "PCRE", + "preg", + "regex", + "regular expression" + ], + "support": { + "issues": "https://github.com/composer/pcre/issues", + "source": "https://github.com/composer/pcre/tree/3.3.2" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" }, { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" + "url": "https://github.com/composer", + "type": "github" }, { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" } ], - "description": "Base library for a lexer that can be used in Top-Down, Recursive Descent Parsers.", - "homepage": "http://www.doctrine-project.org", - "keywords": [ - "lexer", - "parser" - ], - "time": "2014-09-09T13:34:57+00:00" + "time": "2024-11-12T16:29:46+00:00" }, { - "name": "epfremme/swagger-php", - "version": "v2.0.0", + "name": "composer/semver", + "version": "3.4.3", "source": { "type": "git", - "url": "https://github.com/epfremmer/swagger-php.git", - "reference": "eee28a442b7e6220391ec953d3c9b936354f23bc" + "url": "https://github.com/composer/semver.git", + "reference": "4313d26ada5e0c4edfbd1dc481a92ff7bff91f12" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/epfremmer/swagger-php/zipball/eee28a442b7e6220391ec953d3c9b936354f23bc", - "reference": "eee28a442b7e6220391ec953d3c9b936354f23bc", + "url": "https://api.github.com/repos/composer/semver/zipball/4313d26ada5e0c4edfbd1dc481a92ff7bff91f12", + "reference": "4313d26ada5e0c4edfbd1dc481a92ff7bff91f12", "shasum": "" }, "require": { - "doctrine/annotations": "^1.2", - "doctrine/collections": "^1.3", - "jms/serializer": "^1.1", - "php": ">=5.5", - "phpoption/phpoption": "^1.1", - "symfony/yaml": "^2.7|^3.1" + "php": "^5.3.2 || ^7.0 || ^8.0" }, "require-dev": { - "mockery/mockery": "^0.9.4", - "phpunit/phpunit": "~4.8|~5.0", - "satooshi/php-coveralls": "^1.0" + "phpstan/phpstan": "^1.11", + "symfony/phpunit-bridge": "^3 || ^7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } }, - "type": "package", "autoload": { "psr-4": { - "Epfremme\\Swagger\\": "src/" + "Composer\\Semver\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -1008,99 +1265,157 @@ ], "authors": [ { - "name": "Edward Pfremmer", - "email": "epfremme@nerdery.com" + "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": "Semver library that offers utilities, version constraint parsing and validation.", + "keywords": [ + "semantic", + "semver", + "validation", + "versioning" + ], + "support": { + "irc": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/semver/issues", + "source": "https://github.com/composer/semver/tree/3.4.3" + }, + "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": "Library for parsing swagger documentation into PHP entities for use in testing and code generation", - "time": "2016-09-26T17:24:17+00:00" + "time": "2024-09-19T14:15:21+00:00" }, { - "name": "facebook/webdriver", - "version": "1.6.0", + "name": "composer/spdx-licenses", + "version": "1.5.9", "source": { "type": "git", - "url": "https://github.com/facebook/php-webdriver.git", - "reference": "bd8c740097eb9f2fc3735250fc1912bc811a954e" + "url": "https://github.com/composer/spdx-licenses.git", + "reference": "edf364cefe8c43501e21e88110aac10b284c3c9f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/facebook/php-webdriver/zipball/bd8c740097eb9f2fc3735250fc1912bc811a954e", - "reference": "bd8c740097eb9f2fc3735250fc1912bc811a954e", + "url": "https://api.github.com/repos/composer/spdx-licenses/zipball/edf364cefe8c43501e21e88110aac10b284c3c9f", + "reference": "edf364cefe8c43501e21e88110aac10b284c3c9f", "shasum": "" }, "require": { - "ext-curl": "*", - "ext-json": "*", - "ext-mbstring": "*", - "ext-zip": "*", - "php": "^5.6 || ~7.0", - "symfony/process": "^2.8 || ^3.1 || ^4.0" + "php": "^5.3.2 || ^7.0 || ^8.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" - }, - "suggest": { - "ext-SimpleXML": "For Firefox profile creation" + "phpstan/phpstan": "^1.11", + "symfony/phpunit-bridge": "^3 || ^7" }, "type": "library", "extra": { "branch-alias": { - "dev-community": "1.5-dev" + "dev-main": "1.x-dev" } }, "autoload": { "psr-4": { - "Facebook\\WebDriver\\": "lib/" + "Composer\\Spdx\\": "src" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "Apache-2.0" + "MIT" + ], + "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": "Rob Bast", + "email": "rob.bast@gmail.com", + "homepage": "http://robbast.nl" + } ], - "description": "A PHP client for Selenium WebDriver", - "homepage": "https://github.com/facebook/php-webdriver", + "description": "SPDX licenses list and validation library.", "keywords": [ - "facebook", - "php", - "selenium", - "webdriver" + "license", + "spdx", + "validator" + ], + "support": { + "irc": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/spdx-licenses/issues", + "source": "https://github.com/composer/spdx-licenses/tree/1.5.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" + } ], - "time": "2018-05-16T17:37:13+00:00" + "time": "2025-05-12T21:07:07+00:00" }, { - "name": "flow/jsonpath", - "version": "0.4.0", + "name": "composer/xdebug-handler", + "version": "3.0.5", "source": { "type": "git", - "url": "https://github.com/FlowCommunications/JSONPath.git", - "reference": "f0222818d5c938e4ab668ab2e2c079bd51a27112" + "url": "https://github.com/composer/xdebug-handler.git", + "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/FlowCommunications/JSONPath/zipball/f0222818d5c938e4ab668ab2e2c079bd51a27112", - "reference": "f0222818d5c938e4ab668ab2e2c079bd51a27112", + "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/6c1925561632e83d60a44492e0b344cf48ab85ef", + "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef", "shasum": "" }, "require": { - "php": ">=5.4.0" + "composer/pcre": "^1 || ^2 || ^3", + "php": "^7.2.5 || ^8.0", + "psr/log": "^1 || ^2 || ^3" }, "require-dev": { - "peekmo/jsonpath": "dev-master", - "phpunit/phpunit": "^4.0" + "phpstan/phpstan": "^1.0", + "phpstan/phpstan-strict-rules": "^1.1", + "phpunit/phpunit": "^8.5 || ^9.6 || ^10.5" }, "type": "library", "autoload": { - "psr-0": { - "Flow\\JSONPath": "src/", - "Flow\\JSONPath\\Test": "tests/" + "psr-4": { + "Composer\\XdebugHandler\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -1109,44 +1424,75 @@ ], "authors": [ { - "name": "Stephen Frank", - "email": "stephen@flowsa.com" + "name": "John Stevenson", + "email": "john-stevenson@blueyonder.co.uk" + } + ], + "description": "Restarts a process without Xdebug.", + "keywords": [ + "Xdebug", + "performance" + ], + "support": { + "irc": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/xdebug-handler/issues", + "source": "https://github.com/composer/xdebug-handler/tree/3.0.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": "JSONPath implementation for parsing, searching and flattening arrays", - "time": "2018-03-04T16:39:47+00:00" + "time": "2024-05-06T16:37:16+00:00" }, { - "name": "fzaninotto/faker", - "version": "v1.8.0", + "name": "csharpru/vault-php", + "version": "4.4.1", "source": { "type": "git", - "url": "https://github.com/fzaninotto/Faker.git", - "reference": "f72816b43e74063c8b10357394b6bba8cb1c10de" + "url": "https://github.com/CSharpRU/vault-php.git", + "reference": "2064cd7a377b066c6d256eef1d7f4f4ecbb927f1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/fzaninotto/Faker/zipball/f72816b43e74063c8b10357394b6bba8cb1c10de", - "reference": "f72816b43e74063c8b10357394b6bba8cb1c10de", + "url": "https://api.github.com/repos/CSharpRU/vault-php/zipball/2064cd7a377b066c6d256eef1d7f4f4ecbb927f1", + "reference": "2064cd7a377b066c6d256eef1d7f4f4ecbb927f1", "shasum": "" }, "require": { - "php": "^5.3.3 || ^7.0" + "aws/aws-sdk-php": "^3.0", + "ext-json": "*", + "php": "^7.2 || ^8.0", + "psr/cache": "^1.0|^2.0|^3.0", + "psr/http-client": "^1.0", + "psr/http-factory": "^1.0", + "psr/log": "^1.0|^2.0|^3.0" }, "require-dev": { - "ext-intl": "*", - "phpunit/phpunit": "^4.8.35 || ^5.7", - "squizlabs/php_codesniffer": "^1.5" + "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 || ^3.0", + "php-vcr/php-vcr": "^1.5", + "symfony/event-dispatcher": "<5.0" }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.8-dev" - } + "suggest": { + "cache/array-adapter": "For usage with CachedClient class" }, + "type": "library", "autoload": { "psr-4": { - "Faker\\": "src/Faker/" + "Vault\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -1155,83 +1501,126 @@ ], "authors": [ { - "name": "François Zaninotto" + "name": "Yaroslav Lukyanov", + "email": "c_sharp@mail.ru" } ], - "description": "Faker is a PHP library that generates fake data for you.", + "description": "Best Vault client for PHP that you can find", "keywords": [ - "data", - "faker", - "fixtures" + "hashicorp", + "secrets", + "vault" ], - "time": "2018-07-12T10:23:15+00:00" + "support": { + "issues": "https://github.com/CSharpRU/vault-php/issues", + "source": "https://github.com/CSharpRU/vault-php/tree/4.4.1" + }, + "time": "2025-04-23T08:42:13+00:00" }, { - "name": "g1a/composer-test-scenarios", - "version": "2.2.0", + "name": "doctrine/annotations", + "version": "2.0.2", "source": { "type": "git", - "url": "https://github.com/g1a/composer-test-scenarios.git", - "reference": "a166fd15191aceab89f30c097e694b7cf3db4880" + "url": "https://github.com/doctrine/annotations.git", + "reference": "901c2ee5d26eb64ff43c47976e114bf00843acf7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/g1a/composer-test-scenarios/zipball/a166fd15191aceab89f30c097e694b7cf3db4880", - "reference": "a166fd15191aceab89f30c097e694b7cf3db4880", + "url": "https://api.github.com/repos/doctrine/annotations/zipball/901c2ee5d26eb64ff43c47976e114bf00843acf7", + "reference": "901c2ee5d26eb64ff43c47976e114bf00843acf7", "shasum": "" }, - "bin": [ - "scripts/create-scenario", - "scripts/dependency-licenses", - "scripts/install-scenario" - ], + "require": { + "doctrine/lexer": "^2 || ^3", + "ext-tokenizer": "*", + "php": "^7.2 || ^8.0", + "psr/cache": "^1 || ^2 || ^3" + }, + "require-dev": { + "doctrine/cache": "^2.0", + "doctrine/coding-standard": "^10", + "phpstan/phpstan": "^1.10.28", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "symfony/cache": "^5.4 || ^6.4 || ^7", + "vimeo/psalm": "^4.30 || ^5.14" + }, + "suggest": { + "php": "PHP 8.0 or higher comes with attributes, a native replacement for annotations" + }, "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Common\\Annotations\\": "lib/Doctrine/Common/Annotations" + } + }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "authors": [ { - "name": "Greg Anderson", - "email": "greg.1.anderson@greenknowe.org" + "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": "Useful scripts for testing multiple sets of Composer dependencies.", - "time": "2018-08-08T23:37:23+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/2.0.2" + }, + "time": "2024-09-05T10:17:24+00:00" }, { - "name": "grasmash/expander", - "version": "1.0.0", + "name": "doctrine/lexer", + "version": "3.0.1", "source": { "type": "git", - "url": "https://github.com/grasmash/expander.git", - "reference": "95d6037344a4be1dd5f8e0b0b2571a28c397578f" + "url": "https://github.com/doctrine/lexer.git", + "reference": "31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/grasmash/expander/zipball/95d6037344a4be1dd5f8e0b0b2571a28c397578f", - "reference": "95d6037344a4be1dd5f8e0b0b2571a28c397578f", + "url": "https://api.github.com/repos/doctrine/lexer/zipball/31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd", + "reference": "31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd", "shasum": "" }, "require": { - "dflydev/dot-access-data": "^1.1.0", - "php": ">=5.4" + "php": "^8.1" }, "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" + "doctrine/coding-standard": "^12", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^10.5", + "psalm/plugin-phpunit": "^0.18.3", + "vimeo/psalm": "^5.21" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.x-dev" - } - }, "autoload": { "psr-4": { - "Grasmash\\Expander\\": "src/" + "Doctrine\\Common\\Lexer\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -1240,91 +1629,90 @@ ], "authors": [ { - "name": "Matthew Grasmick" - } - ], - "description": "Expands internal property references in PHP arrays file.", - "time": "2017-12-21T22:14:55+00:00" - }, - { - "name": "grasmash/yaml-expander", - "version": "1.4.0", - "source": { - "type": "git", - "url": "https://github.com/grasmash/yaml-expander.git", - "reference": "3f0f6001ae707a24f4d9733958d77d92bf9693b1" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/grasmash/yaml-expander/zipball/3f0f6001ae707a24f4d9733958d77d92bf9693b1", - "reference": "3f0f6001ae707a24f4d9733958d77d92bf9693b1", - "shasum": "" - }, - "require": { - "dflydev/dot-access-data": "^1.1.0", - "php": ">=5.4", - "symfony/yaml": "^2.8.11|^3|^4" - }, - "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" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.x-dev" - } - }, - "autoload": { - "psr-4": { - "Grasmash\\YamlExpander\\": "src/" + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" ], - "authors": [ + "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", + "lexer", + "parser", + "php" + ], + "support": { + "issues": "https://github.com/doctrine/lexer/issues", + "source": "https://github.com/doctrine/lexer/tree/3.0.1" + }, + "funding": [ { - "name": "Matthew Grasmick" + "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" } ], - "description": "Expands internal property references in a yaml file.", - "time": "2017-12-16T16:06:03+00:00" + "time": "2024-02-05T11:56:58+00:00" }, { "name": "guzzlehttp/guzzle", - "version": "6.3.3", + "version": "7.9.3", "source": { "type": "git", "url": "https://github.com/guzzle/guzzle.git", - "reference": "407b0cb880ace85c9b63c5f9551db498cb2d50ba" + "reference": "7b2f29fe81dc4da0ca0ea7d42107a0845946ea77" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/407b0cb880ace85c9b63c5f9551db498cb2d50ba", - "reference": "407b0cb880ace85c9b63c5f9551db498cb2d50ba", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/7b2f29fe81dc4da0ca0ea7d42107a0845946ea77", + "reference": "7b2f29fe81dc4da0ca0ea7d42107a0845946ea77", "shasum": "" }, "require": { - "guzzlehttp/promises": "^1.0", - "guzzlehttp/psr7": "^1.4", - "php": ">=5.5" + "ext-json": "*", + "guzzlehttp/promises": "^1.5.3 || ^2.0.3", + "guzzlehttp/psr7": "^2.7.0", + "php": "^7.2.5 || ^8.0", + "psr/http-client": "^1.0", + "symfony/deprecation-contracts": "^2.2 || ^3.0" + }, + "provide": { + "psr/http-client-implementation": "1.0" }, "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", "ext-curl": "*", - "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.4 || ^7.0", - "psr/log": "^1.0" + "guzzle/client-integration-tests": "3.0.2", + "php-http/message-factory": "^1.1", + "phpunit/phpunit": "^8.5.39 || ^9.6.20", + "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": "6.3-dev" + "bamarni-bin": { + "bin-links": true, + "forward-command": false } }, "autoload": { @@ -1340,427 +1728,571 @@ "MIT" ], "authors": [ + { + "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": "Jeremy Lindblom", + "email": "jeremeamia@gmail.com", + "homepage": "https://github.com/jeremeamia" + }, + { + "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": "Guzzle is a PHP HTTP client library", - "homepage": "http://guzzlephp.org/", "keywords": [ "client", "curl", "framework", "http", "http client", + "psr-18", + "psr-7", "rest", "web service" ], - "time": "2018-04-22T15:46:56+00:00" + "support": { + "issues": "https://github.com/guzzle/guzzle/issues", + "source": "https://github.com/guzzle/guzzle/tree/7.9.3" + }, + "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": "2025-03-27T13:37:11+00:00" }, { "name": "guzzlehttp/promises", - "version": "v1.3.1", + "version": "2.2.0", "source": { "type": "git", "url": "https://github.com/guzzle/promises.git", - "reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646" + "reference": "7c69f28996b0a6920945dd20b3857e499d9ca96c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/promises/zipball/a59da6cf61d80060647ff4d3eb2c03a2bc694646", - "reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646", + "url": "https://api.github.com/repos/guzzle/promises/zipball/7c69f28996b0a6920945dd20b3857e499d9ca96c", + "reference": "7c69f28996b0a6920945dd20b3857e499d9ca96c", "shasum": "" }, "require": { - "php": ">=5.5.0" + "php": "^7.2.5 || ^8.0" }, "require-dev": { - "phpunit/phpunit": "^4.0" + "bamarni/composer-bin-plugin": "^1.8.2", + "phpunit/phpunit": "^8.5.39 || ^9.6.20" }, "type": "library", "extra": { - "branch-alias": { - "dev-master": "1.4-dev" + "bamarni-bin": { + "bin-links": true, + "forward-command": false } }, "autoload": { "psr-4": { "GuzzleHttp\\Promise\\": "src/" - }, - "files": [ - "src/functions_include.php" - ] + } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "authors": [ + { + "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": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" } ], "description": "Guzzle promises library", "keywords": [ "promise" ], - "time": "2016-12-20T10:07:11+00:00" + "support": { + "issues": "https://github.com/guzzle/promises/issues", + "source": "https://github.com/guzzle/promises/tree/2.0.4" + }, + "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": "2025-03-27T13:27:01+00:00" }, { "name": "guzzlehttp/psr7", - "version": "1.4.2", + "version": "2.7.1", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "f5b8a8512e2b58b0071a7280e39f14f72e05d87c" + "reference": "c2270caaabe631b3b44c85f99e5a04bbb8060d16" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/f5b8a8512e2b58b0071a7280e39f14f72e05d87c", - "reference": "f5b8a8512e2b58b0071a7280e39f14f72e05d87c", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/c2270caaabe631b3b44c85f99e5a04bbb8060d16", + "reference": "c2270caaabe631b3b44c85f99e5a04bbb8060d16", "shasum": "" }, "require": { - "php": ">=5.4.0", - "psr/http-message": "~1.0" + "php": "^7.2.5 || ^8.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.1 || ^2.0", + "ralouphie/getallheaders": "^3.0" }, "provide": { + "psr/http-factory-implementation": "1.0", "psr/http-message-implementation": "1.0" }, "require-dev": { - "phpunit/phpunit": "~4.0" + "bamarni/composer-bin-plugin": "^1.8.2", + "http-interop/http-factory-tests": "0.9.0", + "phpunit/phpunit": "^8.5.39 || ^9.6.20" + }, + "suggest": { + "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" }, "type": "library", "extra": { - "branch-alias": { - "dev-master": "1.4-dev" + "bamarni-bin": { + "bin-links": true, + "forward-command": false } }, "autoload": { "psr-4": { "GuzzleHttp\\Psr7\\": "src/" - }, - "files": [ - "src/functions_include.php" - ] + } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "authors": [ + { + "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" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://sagikazarmark.hu" } ], "description": "PSR-7 message implementation that also provides common utility methods", "keywords": [ "http", "message", + "psr-7", "request", "response", "stream", "uri", "url" ], - "time": "2017-03-20T17:10:46+00:00" + "support": { + "issues": "https://github.com/guzzle/psr7/issues", + "source": "https://github.com/guzzle/psr7/tree/2.7.1" + }, + "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": "2025-03-27T12:30:47+00:00" }, { - "name": "jms/metadata", - "version": "1.6.0", + "name": "justinrainbow/json-schema", + "version": "6.4.2", "source": { "type": "git", - "url": "https://github.com/schmittjoh/metadata.git", - "reference": "6a06970a10e0a532fb52d3959547123b84a3b3ab" + "url": "https://github.com/jsonrainbow/json-schema.git", + "reference": "ce1fd2d47799bb60668643bc6220f6278a4c1d02" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/schmittjoh/metadata/zipball/6a06970a10e0a532fb52d3959547123b84a3b3ab", - "reference": "6a06970a10e0a532fb52d3959547123b84a3b3ab", + "url": "https://api.github.com/repos/jsonrainbow/json-schema/zipball/ce1fd2d47799bb60668643bc6220f6278a4c1d02", + "reference": "ce1fd2d47799bb60668643bc6220f6278a4c1d02", "shasum": "" }, "require": { - "php": ">=5.3.0" + "ext-json": "*", + "marc-mabe/php-enum": "^4.0", + "php": "^7.2 || ^8.0" }, "require-dev": { - "doctrine/cache": "~1.0", - "symfony/cache": "~3.1" + "friendsofphp/php-cs-fixer": "3.3.0", + "json-schema/json-schema-test-suite": "1.2.0", + "marc-mabe/php-enum-phpstan": "^2.0", + "phpspec/prophecy": "^1.19", + "phpstan/phpstan": "^1.12", + "phpunit/phpunit": "^8.5" }, + "bin": [ + "bin/validate-json" + ], "type": "library", "extra": { "branch-alias": { - "dev-master": "1.5.x-dev" + "dev-master": "6.x-dev" } }, "autoload": { - "psr-0": { - "Metadata\\": "src/" + "psr-4": { + "JsonSchema\\": "src/JsonSchema/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "Apache-2.0" + "MIT" ], "authors": [ { - "name": "Johannes M. Schmitt", - "email": "schmittjoh@gmail.com" + "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": "Class/method/property metadata management in PHP", + "description": "A library to validate a json schema.", + "homepage": "https://github.com/jsonrainbow/json-schema", "keywords": [ - "annotations", - "metadata", - "xml", - "yaml" + "json", + "schema" ], - "time": "2016-12-05T10:18:33+00:00" - }, - { - "name": "jms/parser-lib", - "version": "1.0.0", - "source": { - "type": "git", - "url": "https://github.com/schmittjoh/parser-lib.git", - "reference": "c509473bc1b4866415627af0e1c6cc8ac97fa51d" + "support": { + "issues": "https://github.com/jsonrainbow/json-schema/issues", + "source": "https://github.com/jsonrainbow/json-schema/tree/6.4.2" }, - "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" - } - }, - "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": "2025-06-03T18:27:04+00:00" }, { - "name": "jms/serializer", - "version": "1.13.0", + "name": "laminas/laminas-diactoros", + "version": "3.6.0", "source": { "type": "git", - "url": "https://github.com/schmittjoh/serializer.git", - "reference": "00863e1d55b411cc33ad3e1de09a4c8d3aae793c" + "url": "https://github.com/laminas/laminas-diactoros.git", + "reference": "b068eac123f21c0e592de41deeb7403b88e0a89f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/schmittjoh/serializer/zipball/00863e1d55b411cc33ad3e1de09a4c8d3aae793c", - "reference": "00863e1d55b411cc33ad3e1de09a4c8d3aae793c", + "url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/b068eac123f21c0e592de41deeb7403b88e0a89f", + "reference": "b068eac123f21c0e592de41deeb7403b88e0a89f", "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" + "php": "~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0", + "psr/http-factory": "^1.1", + "psr/http-message": "^1.1 || ^2.0" }, "conflict": { - "twig/twig": "<1.12" + "amphp/amp": "<2.6.4" }, - "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" + "provide": { + "psr/http-factory-implementation": "^1.0", + "psr/http-message-implementation": "^1.1 || ^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." + "require-dev": { + "ext-curl": "*", + "ext-dom": "*", + "ext-gd": "*", + "ext-libxml": "*", + "http-interop/http-factory-tests": "^2.2.0", + "laminas/laminas-coding-standard": "~3.0.0", + "php-http/psr7-integration-tests": "^1.4.0", + "phpunit/phpunit": "^10.5.36", + "psalm/plugin-phpunit": "^0.19.0", + "vimeo/psalm": "^5.26.1" }, "type": "library", "extra": { - "branch-alias": { - "dev-1.x": "1.13-dev" + "laminas": { + "module": "Laminas\\Diactoros", + "config-provider": "Laminas\\Diactoros\\ConfigProvider" } }, "autoload": { - "psr-0": { - "JMS\\Serializer": "src/" + "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/normalize_server.php", + "src/functions/normalize_uploaded_files.php", + "src/functions/parse_cookie_header.php" + ], + "psr-4": { + "Laminas\\Diactoros\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], - "authors": [ - { - "name": "Asmir Mustafic", - "email": "goetas@gmail.com" - }, + "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": "Johannes M. Schmitt", - "email": "schmittjoh@gmail.com" + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" } ], - "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": "2018-07-25T13:58:54+00:00" + "time": "2025-05-05T16:03:34+00:00" }, { - "name": "league/container", - "version": "2.4.1", + "name": "marc-mabe/php-enum", + "version": "v4.7.1", "source": { "type": "git", - "url": "https://github.com/thephpleague/container.git", - "reference": "43f35abd03a12977a60ffd7095efd6a7808488c0" + "url": "https://github.com/marc-mabe/php-enum.git", + "reference": "7159809e5cfa041dca28e61f7f7ae58063aae8ed" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/container/zipball/43f35abd03a12977a60ffd7095efd6a7808488c0", - "reference": "43f35abd03a12977a60ffd7095efd6a7808488c0", + "url": "https://api.github.com/repos/marc-mabe/php-enum/zipball/7159809e5cfa041dca28e61f7f7ae58063aae8ed", + "reference": "7159809e5cfa041dca28e61f7f7ae58063aae8ed", "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" - }, - "replace": { - "orno/di": "~2.0" + "ext-reflection": "*", + "php": "^7.1 | ^8.0" }, "require-dev": { - "phpunit/phpunit": "4.*" + "phpbench/phpbench": "^0.16.10 || ^1.0.4", + "phpstan/phpstan": "^1.3.1", + "phpunit/phpunit": "^7.5.20 | ^8.5.22 | ^9.5.11", + "vimeo/psalm": "^4.17.0 | ^5.26.1" }, "type": "library", "extra": { "branch-alias": { - "dev-2.x": "2.x-dev", - "dev-1.x": "1.x-dev" + "dev-3.x": "3.2-dev", + "dev-master": "4.7-dev" } }, "autoload": { "psr-4": { - "League\\Container\\": "src" - } + "MabeEnum\\": "src/" + }, + "classmap": [ + "stubs/Stringable.php" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Phil Bennett", - "email": "philipobenito@gmail.com", - "homepage": "http://www.philipobenito.com", - "role": "Developer" + "name": "Marc Bennewitz", + "email": "dev@mabe.berlin", + "homepage": "https://mabe.berlin/", + "role": "Lead" } ], - "description": "A fast and intuitive dependency injection container.", - "homepage": "https://github.com/thephpleague/container", + "description": "Simple and fast implementation of enumerations with native PHP", + "homepage": "https://github.com/marc-mabe/php-enum", "keywords": [ - "container", - "dependency", - "di", - "injection", - "league", - "provider", - "service" + "enum", + "enum-map", + "enum-set", + "enumeration", + "enumerator", + "enummap", + "enumset", + "map", + "set", + "type", + "type-hint", + "typehint" ], - "time": "2017-05-10T09:20:27+00:00" + "support": { + "issues": "https://github.com/marc-mabe/php-enum/issues", + "source": "https://github.com/marc-mabe/php-enum/tree/v4.7.1" + }, + "time": "2024-11-28T04:54:44+00:00" }, { "name": "monolog/monolog", - "version": "1.23.0", + "version": "3.9.0", "source": { "type": "git", "url": "https://github.com/Seldaek/monolog.git", - "reference": "fd8c787753b3a2ad11bc60c063cff1358a32a3b4" + "reference": "10d85740180ecba7896c87e06a166e0c95a0e3b6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/monolog/zipball/fd8c787753b3a2ad11bc60c063cff1358a32a3b4", - "reference": "fd8c787753b3a2ad11bc60c063cff1358a32a3b4", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/10d85740180ecba7896c87e06a166e0c95a0e3b6", + "reference": "10d85740180ecba7896c87e06a166e0c95a0e3b6", "shasum": "" }, "require": { - "php": ">=5.3.0", - "psr/log": "~1.0" + "php": ">=8.1", + "psr/log": "^2.0 || ^3.0" }, "provide": { - "psr/log-implementation": "1.0.0" + "psr/log-implementation": "3.0.0" }, "require-dev": { - "aws/aws-sdk-php": "^2.4.9 || ^3.0", + "aws/aws-sdk-php": "^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" + "elasticsearch/elasticsearch": "^7 || ^8", + "ext-json": "*", + "graylog2/gelf-php": "^1.4.2 || ^2.0", + "guzzlehttp/guzzle": "^7.4.5", + "guzzlehttp/psr7": "^2.2", + "mongodb/mongodb": "^1.8", + "php-amqplib/php-amqplib": "~2.4 || ^3", + "php-console/php-console": "^3.1.8", + "phpstan/phpstan": "^2", + "phpstan/phpstan-deprecation-rules": "^2", + "phpstan/phpstan-strict-rules": "^2", + "phpunit/phpunit": "^10.5.17 || ^11.0.7", + "predis/predis": "^1.1 || ^2", + "rollbar/rollbar": "^4.0", + "ruflin/elastica": "^7 || ^8", + "symfony/mailer": "^5.4 || ^6", + "symfony/mime": "^5.4 || ^6" }, "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-mongo": "Allow sending log messages to a MongoDB server", + "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 PHP Driver", + "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", - "sentry/sentry": "Allow sending log messages to a Sentry server" + "ruflin/elastica": "Allow sending log messages to an Elastic Search server" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0.x-dev" + "dev-main": "3.x-dev" } }, "autoload": { @@ -1776,79 +2308,110 @@ { "name": "Jordi Boggiano", "email": "j.boggiano@seld.be", - "homepage": "http://seld.be" + "homepage": "https://seld.be" } ], "description": "Sends your logs to files, sockets, inboxes, databases and various web services", - "homepage": "http://github.com/Seldaek/monolog", + "homepage": "https://github.com/Seldaek/monolog", "keywords": [ "log", "logging", "psr-3" ], - "time": "2017-06-19T01:22:40+00:00" + "support": { + "issues": "https://github.com/Seldaek/monolog/issues", + "source": "https://github.com/Seldaek/monolog/tree/3.9.0" + }, + "funding": [ + { + "url": "https://github.com/Seldaek", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/monolog/monolog", + "type": "tidelift" + } + ], + "time": "2025-03-24T10:02:05+00:00" }, { - "name": "moontoast/math", - "version": "1.1.2", + "name": "mtdowling/jmespath.php", + "version": "2.8.0", "source": { "type": "git", - "url": "https://github.com/ramsey/moontoast-math.git", - "reference": "c2792a25df5cad4ff3d760dd37078fc5b6fccc79" + "url": "https://github.com/jmespath/jmespath.php.git", + "reference": "a2a865e05d5f420b50cc2f85bb78d565db12a6bc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ramsey/moontoast-math/zipball/c2792a25df5cad4ff3d760dd37078fc5b6fccc79", - "reference": "c2792a25df5cad4ff3d760dd37078fc5b6fccc79", + "url": "https://api.github.com/repos/jmespath/jmespath.php/zipball/a2a865e05d5f420b50cc2f85bb78d565db12a6bc", + "reference": "a2a865e05d5f420b50cc2f85bb78d565db12a6bc", "shasum": "" }, "require": { - "ext-bcmath": "*", - "php": ">=5.3.3" + "php": "^7.2.5 || ^8.0", + "symfony/polyfill-mbstring": "^1.17" }, "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" + "composer/xdebug-handler": "^3.0.3", + "phpunit/phpunit": "^8.5.33" }, + "bin": [ + "bin/jp.php" + ], "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, "autoload": { + "files": [ + "src/JmesPath.php" + ], "psr-4": { - "Moontoast\\Math\\": "src/Moontoast/Math/" + "JmesPath\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "Apache-2.0" + "MIT" ], "authors": [ { - "name": "Ben Ramsey", - "email": "ben@benramsey.com", - "homepage": "https://benramsey.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" } ], - "description": "A mathematics library, providing functionality for large numbers", - "homepage": "https://github.com/ramsey/moontoast-math", + "description": "Declaratively specify how to extract elements from a JSON document", "keywords": [ - "bcmath", - "math" + "json", + "jsonpath" ], - "time": "2017-02-16T16:54:46+00:00" + "support": { + "issues": "https://github.com/jmespath/jmespath.php/issues", + "source": "https://github.com/jmespath/jmespath.php/tree/2.8.0" + }, + "time": "2024-09-04T18:46:31+00:00" }, { "name": "mustache/mustache", - "version": "v2.12.0", + "version": "v2.14.2", "source": { "type": "git", "url": "https://github.com/bobthecow/mustache.php.git", - "reference": "fe8fe72e9d580591854de404cc59a1b83ca4d19e" + "reference": "e62b7c3849d22ec55f3ec425507bf7968193a6cb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/bobthecow/mustache.php/zipball/fe8fe72e9d580591854de404cc59a1b83ca4d19e", - "reference": "fe8fe72e9d580591854de404cc59a1b83ca4d19e", + "url": "https://api.github.com/repos/bobthecow/mustache.php/zipball/e62b7c3849d22ec55f3ec425507bf7968193a6cb", + "reference": "e62b7c3849d22ec55f3ec425507bf7968193a6cb", "shasum": "" }, "require": { @@ -1881,38 +2444,47 @@ "mustache", "templating" ], - "time": "2017-07-11T12:54:05+00:00" + "support": { + "issues": "https://github.com/bobthecow/mustache.php/issues", + "source": "https://github.com/bobthecow/mustache.php/tree/v2.14.2" + }, + "time": "2022-08-23T13:07:01+00:00" }, { "name": "myclabs/deep-copy", - "version": "1.7.0", + "version": "1.13.1", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e" + "reference": "1720ddd719e16cf0db4eb1c6eca108031636d46c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e", - "reference": "3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/1720ddd719e16cf0db4eb1c6eca108031636d46c", + "reference": "1720ddd719e16cf0db4eb1c6eca108031636d46c", "shasum": "" }, "require": { - "php": "^5.6 || ^7.0" + "php": "^7.1 || ^8.0" + }, + "conflict": { + "doctrine/collections": "<1.6.8", + "doctrine/common": "<2.13.3 || >=3 <3.2.2" }, "require-dev": { - "doctrine/collections": "^1.0", - "doctrine/common": "^2.6", - "phpunit/phpunit": "^4.1" + "doctrine/collections": "^1.6.8", + "doctrine/common": "^2.13.3 || ^3.2.2", + "phpspec/prophecy": "^1.10", + "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" }, "type": "library", "autoload": { - "psr-4": { - "DeepCopy\\": "src/DeepCopy/" - }, "files": [ "src/DeepCopy/deep_copy.php" - ] + ], + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -1926,33 +2498,103 @@ "object", "object graph" ], - "time": "2017-10-19T19:58:43+00:00" + "support": { + "issues": "https://github.com/myclabs/DeepCopy/issues", + "source": "https://github.com/myclabs/DeepCopy/tree/1.13.1" + }, + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", + "type": "tidelift" + } + ], + "time": "2025-04-29T12:36:36+00:00" }, { - "name": "paragonie/random_compat", - "version": "v9.99.99", + "name": "nikic/php-parser", + "version": "v5.5.0", "source": { "type": "git", - "url": "https://github.com/paragonie/random_compat.git", - "reference": "84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95" + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "ae59794362fe85e051a58ad36b289443f57be7a9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/paragonie/random_compat/zipball/84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95", - "reference": "84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/ae59794362fe85e051a58ad36b289443f57be7a9", + "reference": "ae59794362fe85e051a58ad36b289443f57be7a9", "shasum": "" }, "require": { - "php": "^7" + "ext-ctype": "*", + "ext-json": "*", + "ext-tokenizer": "*", + "php": ">=7.4" }, "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." + "ircmaxell/php-yacc": "^0.0.7", + "phpunit/phpunit": "^9.0" }, + "bin": [ + "bin/php-parse" + ], "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "autoload": { + "psr-4": { + "PhpParser\\": "lib/PhpParser" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov" + } + ], + "description": "A PHP parser written in PHP", + "keywords": [ + "parser", + "php" + ], + "support": { + "issues": "https://github.com/nikic/PHP-Parser/issues", + "source": "https://github.com/nikic/PHP-Parser/tree/v5.5.0" + }, + "time": "2025-05-31T08:24:38+00:00" + }, + { + "name": "paragonie/constant_time_encoding", + "version": "v3.0.0", + "source": { + "type": "git", + "url": "https://github.com/paragonie/constant_time_encoding.git", + "reference": "df1e7fde177501eee2037dd159cf04f5f301a512" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/df1e7fde177501eee2037dd159cf04f5f301a512", + "reference": "df1e7fde177501eee2037dd159cf04f5f301a512", + "shasum": "" + }, + "require": { + "php": "^8" + }, + "require-dev": { + "phpunit/phpunit": "^9", + "vimeo/psalm": "^4|^5" + }, + "type": "library", + "autoload": { + "psr-4": { + "ParagonIE\\ConstantTime\\": "src/" + } + }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" @@ -1961,42 +2603,64 @@ { "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": "2024-05-08T12:36:18+00:00" }, { "name": "phar-io/manifest", - "version": "1.0.1", + "version": "2.0.4", "source": { "type": "git", "url": "https://github.com/phar-io/manifest.git", - "reference": "2df402786ab5368a0169091f61a7c1e0eb6852d0" + "reference": "54750ef60c58e43759730615a392c31c80e23176" }, "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/54750ef60c58e43759730615a392c31c80e23176", + "reference": "54750ef60c58e43759730615a392c31c80e23176", "shasum": "" }, "require": { "ext-dom": "*", + "ext-libxml": "*", "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": { @@ -2026,24 +2690,34 @@ } ], "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.4" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2024-03-03T12:33:53+00:00" }, { "name": "phar-io/version", - "version": "1.0.1", + "version": "3.2.1", "source": { "type": "git", "url": "https://github.com/phar-io/version.git", - "reference": "a70c0ced4be299a63d32fa96d9281d03e94041df" + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" }, "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/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", "shasum": "" }, "require": { - "php": "^5.6 || ^7.0" + "php": "^7.2 || ^8.0" }, "type": "library", "autoload": { @@ -2073,360 +2747,457 @@ } ], "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.2.1" + }, + "time": "2022-02-21T01:04:05+00:00" }, { - "name": "phpcollection/phpcollection", - "version": "0.5.0", + "name": "php-webdriver/webdriver", + "version": "1.15.2", "source": { "type": "git", - "url": "https://github.com/schmittjoh/php-collection.git", - "reference": "f2bcff45c0da7c27991bbc1f90f47c4b7fb434a6" + "url": "https://github.com/php-webdriver/php-webdriver.git", + "reference": "998e499b786805568deaf8cbf06f4044f05d91bf" }, "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/998e499b786805568deaf8cbf06f4044f05d91bf", + "reference": "998e499b786805568deaf8cbf06f4044f05d91bf", "shasum": "" }, "require": { - "phpoption/phpoption": "1.*" + "ext-curl": "*", + "ext-json": "*", + "ext-zip": "*", + "php": "^7.3 || ^8.0", + "symfony/polyfill-mbstring": "^1.12", + "symfony/process": "^5.0 || ^6.0 || ^7.0" }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "0.4-dev" - } + "replace": { + "facebook/webdriver": "*" }, + "require-dev": { + "ergebnis/composer-normalize": "^2.20.0", + "ondram/ci-detector": "^4.0", + "php-coveralls/php-coveralls": "^2.4", + "php-mock/php-mock-phpunit": "^2.0", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpunit/phpunit": "^9.3", + "squizlabs/php_codesniffer": "^3.5", + "symfony/var-dumper": "^5.0 || ^6.0 || ^7.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.15.2" + }, + "time": "2024-11-21T15:12:59+00:00" }, { - "name": "phpdocumentor/reflection-common", - "version": "1.0.1", + "name": "phpunit/php-code-coverage", + "version": "10.1.16", "source": { "type": "git", - "url": "https://github.com/phpDocumentor/ReflectionCommon.git", - "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6" + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "7e308268858ed6baedc8704a304727d20bc07c77" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6", - "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/7e308268858ed6baedc8704a304727d20bc07c77", + "reference": "7e308268858ed6baedc8704a304727d20bc07c77", "shasum": "" }, "require": { - "php": ">=5.5" + "ext-dom": "*", + "ext-libxml": "*", + "ext-xmlwriter": "*", + "nikic/php-parser": "^4.19.1 || ^5.1.0", + "php": ">=8.1", + "phpunit/php-file-iterator": "^4.1.0", + "phpunit/php-text-template": "^3.0.1", + "sebastian/code-unit-reverse-lookup": "^3.0.0", + "sebastian/complexity": "^3.2.0", + "sebastian/environment": "^6.1.0", + "sebastian/lines-of-code": "^2.0.2", + "sebastian/version": "^4.0.1", + "theseer/tokenizer": "^1.2.3" }, "require-dev": { - "phpunit/phpunit": "^4.6" + "phpunit/phpunit": "^10.1" + }, + "suggest": { + "ext-pcov": "PHP extension that provides line coverage", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-main": "10.1.x-dev" } }, "autoload": { - "psr-4": { - "phpDocumentor\\Reflection\\": [ - "src" - ] - } + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Jaap van Otterdijk", - "email": "opensource@ijaap.nl" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" } ], - "description": "Common reflection classes used by phpdocumentor to reflect the code structure", - "homepage": "http://www.phpdoc.org", + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", "keywords": [ - "FQSEN", - "phpDocumentor", - "phpdoc", - "reflection", - "static analysis" + "coverage", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", + "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/10.1.16" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } ], - "time": "2017-09-11T18:02:19+00:00" + "time": "2024-08-22T04:31:57+00:00" }, { - "name": "phpdocumentor/reflection-docblock", - "version": "4.3.0", + "name": "phpunit/php-file-iterator", + "version": "4.1.0", "source": { "type": "git", - "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "94fd0001232e47129dd3504189fa1c7225010d08" + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "a95037b6d9e608ba092da1b23931e537cadc3c3c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/94fd0001232e47129dd3504189fa1c7225010d08", - "reference": "94fd0001232e47129dd3504189fa1c7225010d08", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/a95037b6d9e608ba092da1b23931e537cadc3c3c", + "reference": "a95037b6d9e608ba092da1b23931e537cadc3c3c", "shasum": "" }, "require": { - "php": "^7.0", - "phpdocumentor/reflection-common": "^1.0.0", - "phpdocumentor/type-resolver": "^0.4.0", - "webmozart/assert": "^1.0" + "php": ">=8.1" }, "require-dev": { - "doctrine/instantiator": "~1.0.5", - "mockery/mockery": "^1.0", - "phpunit/phpunit": "^6.4" + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.x-dev" + "dev-main": "4.0-dev" } }, "autoload": { - "psr-4": { - "phpDocumentor\\Reflection\\": [ - "src/" - ] - } + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Mike van Riel", - "email": "me@mikevanriel.com" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", + "security": "https://github.com/sebastianbergmann/php-file-iterator/security/policy", + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/4.1.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" } ], - "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" + "time": "2023-08-31T06:24:48+00:00" }, { - "name": "phpdocumentor/type-resolver", - "version": "0.4.0", + "name": "phpunit/php-invoker", + "version": "4.0.0", "source": { "type": "git", - "url": "https://github.com/phpDocumentor/TypeResolver.git", - "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7" + "url": "https://github.com/sebastianbergmann/php-invoker.git", + "reference": "f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/9c977708995954784726e25d0cd1dddf4e65b0f7", - "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7", + "reference": "f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7", "shasum": "" }, "require": { - "php": "^5.5 || ^7.0", - "phpdocumentor/reflection-common": "^1.0" + "php": ">=8.1" }, "require-dev": { - "mockery/mockery": "^0.9.4", - "phpunit/phpunit": "^5.2||^4.8.24" + "ext-pcntl": "*", + "phpunit/phpunit": "^10.0" + }, + "suggest": { + "ext-pcntl": "*" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-main": "4.0-dev" } }, "autoload": { - "psr-4": { - "phpDocumentor\\Reflection\\": [ - "src/" - ] - } + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Mike van Riel", - "email": "me@mikevanriel.com" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Invoke callables with a timeout", + "homepage": "https://github.com/sebastianbergmann/php-invoker/", + "keywords": [ + "process" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-invoker/issues", + "source": "https://github.com/sebastianbergmann/php-invoker/tree/4.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" } ], - "time": "2017-07-14T14:27:02+00:00" + "time": "2023-02-03T06:56:09+00:00" }, { - "name": "phpoption/phpoption", - "version": "1.5.0", + "name": "phpunit/php-text-template", + "version": "3.0.1", "source": { "type": "git", - "url": "https://github.com/schmittjoh/php-option.git", - "reference": "94e644f7d2051a5f0fcf77d81605f152eecff0ed" + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "0c7b06ff49e3d5072f057eb1fa59258bf287a748" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/94e644f7d2051a5f0fcf77d81605f152eecff0ed", - "reference": "94e644f7d2051a5f0fcf77d81605f152eecff0ed", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/0c7b06ff49e3d5072f057eb1fa59258bf287a748", + "reference": "0c7b06ff49e3d5072f057eb1fa59258bf287a748", "shasum": "" }, "require": { - "php": ">=5.3.0" + "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "4.7.*" + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.3-dev" + "dev-main": "3.0-dev" } }, "autoload": { - "psr-0": { - "PhpOption\\": "src/" - } + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "Apache2" + "BSD-3-Clause" ], "authors": [ { - "name": "Johannes M. Schmitt", - "email": "schmittjoh@gmail.com" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" } ], - "description": "Option Type for PHP", + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", "keywords": [ - "language", - "option", - "php", - "type" + "template" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-text-template/issues", + "security": "https://github.com/sebastianbergmann/php-text-template/security/policy", + "source": "https://github.com/sebastianbergmann/php-text-template/tree/3.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } ], - "time": "2015-07-25T16:39:46+00:00" + "time": "2023-08-31T14:07:24+00:00" }, { - "name": "phpspec/prophecy", - "version": "1.8.0", + "name": "phpunit/php-timer", + "version": "6.0.0", "source": { "type": "git", - "url": "https://github.com/phpspec/prophecy.git", - "reference": "4ba436b55987b4bf311cb7c6ba82aa528aac0a06" + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "e2a2d67966e740530f4a3343fe2e030ffdc1161d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/4ba436b55987b4bf311cb7c6ba82aa528aac0a06", - "reference": "4ba436b55987b4bf311cb7c6ba82aa528aac0a06", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/e2a2d67966e740530f4a3343fe2e030ffdc1161d", + "reference": "e2a2d67966e740530f4a3343fe2e030ffdc1161d", "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": ">=8.1" }, "require-dev": { - "phpspec/phpspec": "^2.5|^3.2", - "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5 || ^7.1" + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.8.x-dev" + "dev-main": "6.0-dev" } }, "autoload": { - "psr-0": { - "Prophecy\\": "src/" - } + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Konstantin Kudryashov", - "email": "ever.zet@gmail.com", - "homepage": "http://everzet.com" - }, - { - "name": "Marcello Duarte", - "email": "marcello.duarte@gmail.com" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" } ], - "description": "Highly opinionated mocking framework for PHP 5.3+", - "homepage": "https://github.com/phpspec/prophecy", + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", "keywords": [ - "Double", - "Dummy", - "fake", - "mock", - "spy", - "stub" - ], - "time": "2018-08-05T17:53:17+00:00" + "timer" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-timer/issues", + "source": "https://github.com/sebastianbergmann/php-timer/tree/6.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T06:57:52+00:00" }, { - "name": "phpunit/php-code-coverage", - "version": "5.3.2", + "name": "phpunit/phpunit", + "version": "10.5.46", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "c89677919c5dd6d3b3852f230a663118762218ac" + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "8080be387a5be380dda48c6f41cee4a13aadab3d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/c89677919c5dd6d3b3852f230a663118762218ac", - "reference": "c89677919c5dd6d3b3852f230a663118762218ac", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/8080be387a5be380dda48c6f41cee4a13aadab3d", + "reference": "8080be387a5be380dda48c6f41cee4a13aadab3d", "shasum": "" }, "require": { "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", "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" - }, - "require-dev": { - "phpunit/phpunit": "^6.0" + "myclabs/deep-copy": "^1.13.1", + "phar-io/manifest": "^2.0.4", + "phar-io/version": "^3.2.1", + "php": ">=8.1", + "phpunit/php-code-coverage": "^10.1.16", + "phpunit/php-file-iterator": "^4.1.0", + "phpunit/php-invoker": "^4.0.0", + "phpunit/php-text-template": "^3.0.1", + "phpunit/php-timer": "^6.0.0", + "sebastian/cli-parser": "^2.0.1", + "sebastian/code-unit": "^2.0.0", + "sebastian/comparator": "^5.0.3", + "sebastian/diff": "^5.1.1", + "sebastian/environment": "^6.1.0", + "sebastian/exporter": "^5.1.2", + "sebastian/global-state": "^6.0.2", + "sebastian/object-enumerator": "^5.0.0", + "sebastian/recursion-context": "^5.0.0", + "sebastian/type": "^4.0.0", + "sebastian/version": "^4.0.1" }, "suggest": { - "ext-xdebug": "^2.5.5" + "ext-soap": "To be able to generate mocks based on WSDL files" }, + "bin": [ + "phpunit" + ], "type": "library", "extra": { "branch-alias": { - "dev-master": "5.3.x-dev" + "dev-main": "10.5-dev" } }, "autoload": { + "files": [ + "src/Framework/Assert/Functions.php" + ], "classmap": [ "src/" ] @@ -2442,360 +3213,311 @@ "role": "lead" } ], - "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", - "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", "keywords": [ - "coverage", + "phpunit", "testing", "xunit" ], - "time": "2018-04-06T15:36:58+00:00" + "support": { + "issues": "https://github.com/sebastianbergmann/phpunit/issues", + "security": "https://github.com/sebastianbergmann/phpunit/security/policy", + "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.46" + }, + "funding": [ + { + "url": "https://phpunit.de/sponsors.html", + "type": "custom" + }, + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", + "type": "tidelift" + } + ], + "time": "2025-05-02T06:46:24+00:00" }, { - "name": "phpunit/php-file-iterator", - "version": "1.4.5", + "name": "psr/cache", + "version": "3.0.0", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4" + "url": "https://github.com/php-fig/cache.git", + "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/730b01bc3e867237eaac355e06a36b85dd93a8b4", - "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4", + "url": "https://api.github.com/repos/php-fig/cache/zipball/aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", + "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", "shasum": "" }, "require": { - "php": ">=5.3.3" + "php": ">=8.0.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.4.x-dev" + "dev-master": "1.0.x-dev" } }, "autoload": { - "classmap": [ - "src/" - ] + "psr-4": { + "Psr\\Cache\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sb@sebastian-bergmann.de", - "role": "lead" + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" } ], - "description": "FilterIterator implementation that filters files based on a list of suffixes.", - "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "description": "Common interface for caching libraries", "keywords": [ - "filesystem", - "iterator" + "cache", + "psr", + "psr-6" ], - "time": "2017-11-27T13:52:08+00:00" - }, - { - "name": "phpunit/php-text-template", - "version": "1.2.1", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-text-template.git", - "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686", - "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686", - "shasum": "" + "support": { + "source": "https://github.com/php-fig/cache/tree/3.0.0" }, - "require": { - "php": ">=5.3.3" - }, - "type": "library", - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Simple template engine.", - "homepage": "https://github.com/sebastianbergmann/php-text-template/", - "keywords": [ - "template" - ], - "time": "2015-06-21T13:50:34+00:00" + "time": "2021-02-03T23:26:27+00:00" }, { - "name": "phpunit/php-timer", - "version": "1.0.9", + "name": "psr/clock", + "version": "1.0.0", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/php-timer.git", - "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f" + "url": "https://github.com/php-fig/clock.git", + "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", - "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", + "url": "https://api.github.com/repos/php-fig/clock/zipball/e41a24703d4560fd0acb709162f73b8adfc3aa0d", + "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d", "shasum": "" }, "require": { - "php": "^5.3.3 || ^7.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" + "php": "^7.0 || ^8.0" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - } - }, "autoload": { - "classmap": [ - "src/" - ] + "psr-4": { + "Psr\\Clock\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sb@sebastian-bergmann.de", - "role": "lead" + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" } ], - "description": "Utility class for timing", - "homepage": "https://github.com/sebastianbergmann/php-timer/", + "description": "Common interface for reading the clock.", + "homepage": "https://github.com/php-fig/clock", "keywords": [ - "timer" + "clock", + "now", + "psr", + "psr-20", + "time" ], - "time": "2017-02-26T11:10:40+00:00" + "support": { + "issues": "https://github.com/php-fig/clock/issues", + "source": "https://github.com/php-fig/clock/tree/1.0.0" + }, + "time": "2022-11-25T14:36:26+00:00" }, { - "name": "phpunit/php-token-stream", + "name": "psr/container", "version": "2.0.2", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/php-token-stream.git", - "reference": "791198a2c6254db10131eecfe8c06670700904db" + "url": "https://github.com/php-fig/container.git", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/791198a2c6254db10131eecfe8c06670700904db", - "reference": "791198a2c6254db10131eecfe8c06670700904db", + "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", "shasum": "" }, "require": { - "ext-tokenizer": "*", - "php": "^7.0" - }, - "require-dev": { - "phpunit/phpunit": "^6.2.4" + "php": ">=7.4.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-master": "2.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" + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" } ], - "description": "Wrapper around PHP's tokenizer extension.", - "homepage": "https://github.com/sebastianbergmann/php-token-stream/", + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", "keywords": [ - "tokenizer" + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" ], - "time": "2017-11-27T05:48:46+00:00" + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/2.0.2" + }, + "time": "2021-11-05T16:47:00+00:00" }, { - "name": "phpunit/phpunit", - "version": "6.5.12", + "name": "psr/event-dispatcher", + "version": "1.0.0", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "24da433d7384824d65ea93fbb462e2f31bbb494e" + "url": "https://github.com/php-fig/event-dispatcher.git", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/24da433d7384824d65ea93fbb462e2f31bbb494e", - "reference": "24da433d7384824d65ea93fbb462e2f31bbb494e", + "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0", "shasum": "" }, "require": { - "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" - }, - "require-dev": { - "ext-pdo": "*" - }, - "suggest": { - "ext-xdebug": "*", - "phpunit/php-invoker": "^1.1" + "php": ">=7.2.0" }, - "bin": [ - "phpunit" - ], "type": "library", "extra": { "branch-alias": { - "dev-master": "6.5.x-dev" + "dev-master": "1.0.x-dev" } }, "autoload": { - "classmap": [ - "src/" - ] + "psr-4": { + "Psr\\EventDispatcher\\": "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": "http://www.php-fig.org/" } ], - "description": "The PHP Unit Testing framework.", - "homepage": "https://phpunit.de/", + "description": "Standard interfaces for event handling.", "keywords": [ - "phpunit", - "testing", - "xunit" + "events", + "psr", + "psr-14" ], - "time": "2018-08-22T06:32:48+00:00" + "support": { + "issues": "https://github.com/php-fig/event-dispatcher/issues", + "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0" + }, + "time": "2019-01-08T18:20:26+00:00" }, { - "name": "phpunit/phpunit-mock-objects", - "version": "5.0.10", + "name": "psr/http-client", + "version": "1.0.3", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", - "reference": "cd1cf05c553ecfec36b170070573e540b67d3f1f" + "url": "https://github.com/php-fig/http-client.git", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90" }, "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/http-client/zipball/bb5906edc1c324c9a05aa0873d40117941e5fa90", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90", "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.0 || ^8.0", + "psr/http-message": "^1.0 || ^2.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "5.0.x-dev" + "dev-master": "1.0.x-dev" } }, "autoload": { - "classmap": [ - "src/" - ] + "psr-4": { + "Psr\\Http\\Client\\": "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 interface for HTTP clients", + "homepage": "https://github.com/php-fig/http-client", "keywords": [ - "mock", - "xunit" + "http", + "http-client", + "psr", + "psr-18" ], - "time": "2018-08-09T05:50:03+00:00" + "support": { + "source": "https://github.com/php-fig/http-client" + }, + "time": "2023-09-23T14:17:50+00:00" }, { - "name": "psr/container", - "version": "1.0.0", + "name": "psr/http-factory", + "version": "1.1.0", "source": { "type": "git", - "url": "https://github.com/php-fig/container.git", - "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f" + "url": "https://github.com/php-fig/http-factory.git", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a" }, "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/2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a", "shasum": "" }, "require": { - "php": ">=5.3.0" + "php": ">=7.1", + "psr/http-message": "^1.0 || ^2.0" }, "type": "library", "extra": { @@ -2805,7 +3527,7 @@ }, "autoload": { "psr-4": { - "Psr\\Container\\": "src/" + "Psr\\Http\\Message\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -2815,41 +3537,46 @@ "authors": [ { "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "homepage": "https://www.php-fig.org/" } ], - "description": "Common Container Interface (PHP FIG PSR-11)", - "homepage": "https://github.com/php-fig/container", + "description": "PSR-17: 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" + }, + "time": "2024-04-15T12:06:14+00:00" }, { "name": "psr/http-message", - "version": "1.0.1", + "version": "2.0", "source": { "type": "git", "url": "https://github.com/php-fig/http-message.git", - "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", - "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71", "shasum": "" }, "require": { - "php": ">=5.3.0" + "php": "^7.2 || ^8.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "2.0.x-dev" } }, "autoload": { @@ -2864,7 +3591,7 @@ "authors": [ { "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "homepage": "https://www.php-fig.org/" } ], "description": "Common interface for HTTP messages", @@ -2877,34 +3604,37 @@ "request", "response" ], - "time": "2016-08-06T14:39:51+00:00" + "support": { + "source": "https://github.com/php-fig/http-message/tree/2.0" + }, + "time": "2023-04-04T09:54:51+00:00" }, { "name": "psr/log", - "version": "1.0.2", + "version": "2.0.0", "source": { "type": "git", "url": "https://github.com/php-fig/log.git", - "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d" + "reference": "ef29f6d262798707a9edd554e2b82517ef3a9376" }, "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/ef29f6d262798707a9edd554e2b82517ef3a9376", + "reference": "ef29f6d262798707a9edd554e2b82517ef3a9376", "shasum": "" }, "require": { - "php": ">=5.3.0" + "php": ">=8.0.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "2.0.x-dev" } }, "autoload": { "psr-4": { - "Psr\\Log\\": "Psr/Log/" + "Psr\\Log\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -2914,7 +3644,7 @@ "authors": [ { "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "homepage": "https://www.php-fig.org/" } ], "description": "Common interface for logging libraries", @@ -2924,60 +3654,63 @@ "psr", "psr-3" ], - "time": "2016-10-10T12:19:37+00:00" + "support": { + "source": "https://github.com/php-fig/log/tree/2.0.0" + }, + "time": "2021-07-14T16:41:46+00:00" }, { - "name": "ramsey/uuid", - "version": "3.8.0", + "name": "psy/psysh", + "version": "v0.12.8", "source": { "type": "git", - "url": "https://github.com/ramsey/uuid.git", - "reference": "d09ea80159c1929d75b3f9c60504d613aeb4a1e3" + "url": "https://github.com/bobthecow/psysh.git", + "reference": "85057ceedee50c49d4f6ecaff73ee96adb3b3625" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ramsey/uuid/zipball/d09ea80159c1929d75b3f9c60504d613aeb4a1e3", - "reference": "d09ea80159c1929d75b3f9c60504d613aeb4a1e3", + "url": "https://api.github.com/repos/bobthecow/psysh/zipball/85057ceedee50c49d4f6ecaff73ee96adb3b3625", + "reference": "85057ceedee50c49d4f6ecaff73ee96adb3b3625", "shasum": "" }, "require": { - "paragonie/random_compat": "^1.0|^2.0|9.99.99", - "php": "^5.4 || ^7.0", - "symfony/polyfill-ctype": "^1.8" + "ext-json": "*", + "ext-tokenizer": "*", + "nikic/php-parser": "^5.0 || ^4.0", + "php": "^8.0 || ^7.4", + "symfony/console": "^7.0 || ^6.0 || ^5.0 || ^4.0 || ^3.4", + "symfony/var-dumper": "^7.0 || ^6.0 || ^5.0 || ^4.0 || ^3.4" }, - "replace": { - "rhumsaa/uuid": "self.version" + "conflict": { + "symfony/console": "4.4.37 || 5.3.14 || 5.3.15 || 5.4.3 || 5.4.4 || 6.0.3 || 6.0.4" }, "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" + "bamarni/composer-bin-plugin": "^1.2" }, "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." + "ext-pcntl": "Enabling the PCNTL extension makes PsySH a lot happier :)", + "ext-pdo-sqlite": "The doc command requires SQLite to work.", + "ext-posix": "If you have PCNTL, you'll want the POSIX extension as well." }, + "bin": [ + "bin/psysh" + ], "type": "library", "extra": { + "bamarni-bin": { + "bin-links": false, + "forward-command": false + }, "branch-alias": { - "dev-master": "3.x-dev" + "dev-main": "0.12.x-dev" } }, "autoload": { + "files": [ + "src/functions.php" + ], "psr-4": { - "Ramsey\\Uuid\\": "src/" + "Psy\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -2986,265 +3719,321 @@ ], "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": "Justin Hileman", + "email": "justin@justinhileman.info", + "homepage": "http://justinhileman.com" } ], - "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", + "description": "An interactive shell for modern PHP.", + "homepage": "http://psysh.org", "keywords": [ - "guid", - "identifier", - "uuid" + "REPL", + "console", + "interactive", + "shell" ], - "time": "2018-07-19T23:38:55+00:00" + "support": { + "issues": "https://github.com/bobthecow/psysh/issues", + "source": "https://github.com/bobthecow/psysh/tree/v0.12.8" + }, + "time": "2025-03-16T03:05:19+00:00" }, { - "name": "sebastian/code-unit-reverse-lookup", - "version": "1.0.1", + "name": "ralouphie/getallheaders", + "version": "3.0.3", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", - "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18" + "url": "https://github.com/ralouphie/getallheaders.git", + "reference": "120b605dfeb996808c31b6477290a714d356e822" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/4419fcdb5eabb9caa61a27c7a1db532a6b55dd18", - "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18", + "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", + "reference": "120b605dfeb996808c31b6477290a714d356e822", "shasum": "" }, "require": { - "php": "^5.6 || ^7.0" + "php": ">=5.6" }, "require-dev": { - "phpunit/phpunit": "^5.7 || ^6.0" + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^5 || ^6.5" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, "autoload": { - "classmap": [ - "src/" + "files": [ + "src/getallheaders.php" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" + "name": "Ralph Khattar", + "email": "ralph.khattar@gmail.com" } ], - "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" + "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": "sebastian/comparator", - "version": "2.1.3", + "name": "ramsey/collection", + "version": "2.1.1", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "34369daee48eafb2651bea869b4b15d75ccc35f9" + "url": "https://github.com/ramsey/collection.git", + "reference": "344572933ad0181accbf4ba763e85a0306a8c5e2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/34369daee48eafb2651bea869b4b15d75ccc35f9", - "reference": "34369daee48eafb2651bea869b4b15d75ccc35f9", + "url": "https://api.github.com/repos/ramsey/collection/zipball/344572933ad0181accbf4ba763e85a0306a8c5e2", + "reference": "344572933ad0181accbf4ba763e85a0306a8c5e2", "shasum": "" }, "require": { - "php": "^7.0", - "sebastian/diff": "^2.0 || ^3.0", - "sebastian/exporter": "^3.1" + "php": "^8.1" }, "require-dev": { - "phpunit/phpunit": "^6.4" + "captainhook/plugin-composer": "^5.3", + "ergebnis/composer-normalize": "^2.45", + "fakerphp/faker": "^1.24", + "hamcrest/hamcrest-php": "^2.0", + "jangregor/phpstan-prophecy": "^2.1", + "mockery/mockery": "^1.6", + "php-parallel-lint/php-console-highlighter": "^1.0", + "php-parallel-lint/php-parallel-lint": "^1.4", + "phpspec/prophecy-phpunit": "^2.3", + "phpstan/extension-installer": "^1.4", + "phpstan/phpstan": "^2.1", + "phpstan/phpstan-mockery": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpunit/phpunit": "^10.5", + "ramsey/coding-standard": "^2.3", + "ramsey/conventional-commits": "^1.6", + "roave/security-advisories": "dev-latest" }, "type": "library", "extra": { - "branch-alias": { - "dev-master": "2.1.x-dev" + "captainhook": { + "force-install": true + }, + "ramsey/conventional-commits": { + "configFile": "conventional-commits.json" } }, "autoload": { - "classmap": [ - "src/" - ] + "psr-4": { + "Ramsey\\Collection\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" - }, - { - "name": "Volker Dusch", - "email": "github@wallbash.com" - }, - { - "name": "Bernhard Schussek", - "email": "bschussek@2bepublished.at" - }, - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" + "name": "Ben Ramsey", + "email": "ben@benramsey.com", + "homepage": "https://benramsey.com" } ], - "description": "Provides the functionality to compare PHP values for equality", - "homepage": "https://github.com/sebastianbergmann/comparator", + "description": "A PHP library for representing and manipulating collections.", "keywords": [ - "comparator", - "compare", - "equality" + "array", + "collection", + "hash", + "map", + "queue", + "set" ], - "time": "2018-02-01T13:46:46+00:00" + "support": { + "issues": "https://github.com/ramsey/collection/issues", + "source": "https://github.com/ramsey/collection/tree/2.1.1" + }, + "time": "2025-03-22T05:38:12+00:00" }, { - "name": "sebastian/diff", - "version": "2.0.1", + "name": "ramsey/uuid", + "version": "4.8.1", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "347c1d8b49c5c3ee30c7040ea6fc446790e6bddd" + "url": "https://github.com/ramsey/uuid.git", + "reference": "fdf4dd4e2ff1813111bd0ad58d7a1ddbb5b56c28" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/347c1d8b49c5c3ee30c7040ea6fc446790e6bddd", - "reference": "347c1d8b49c5c3ee30c7040ea6fc446790e6bddd", + "url": "https://api.github.com/repos/ramsey/uuid/zipball/fdf4dd4e2ff1813111bd0ad58d7a1ddbb5b56c28", + "reference": "fdf4dd4e2ff1813111bd0ad58d7a1ddbb5b56c28", "shasum": "" }, "require": { - "php": "^7.0" + "brick/math": "^0.8.8 || ^0.9 || ^0.10 || ^0.11 || ^0.12 || ^0.13", + "ext-json": "*", + "php": "^8.0", + "ramsey/collection": "^1.2 || ^2.0" + }, + "replace": { + "rhumsaa/uuid": "self.version" }, "require-dev": { - "phpunit/phpunit": "^6.2" + "captainhook/captainhook": "^5.25", + "captainhook/plugin-composer": "^5.3", + "dealerdirect/phpcodesniffer-composer-installer": "^1.0", + "ergebnis/composer-normalize": "^2.47", + "mockery/mockery": "^1.6", + "paragonie/random-lib": "^2", + "php-mock/php-mock": "^2.6", + "php-mock/php-mock-mockery": "^1.5", + "php-parallel-lint/php-parallel-lint": "^1.4.0", + "phpbench/phpbench": "^1.2.14", + "phpstan/extension-installer": "^1.4", + "phpstan/phpstan": "^2.1", + "phpstan/phpstan-mockery": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpunit/phpunit": "^9.6", + "slevomat/coding-standard": "^8.18", + "squizlabs/php_codesniffer": "^3.13" + }, + "suggest": { + "ext-bcmath": "Enables faster math with arbitrary-precision integers using BCMath.", + "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": "2.0-dev" + "captainhook": { + "force-install": true } }, "autoload": { - "classmap": [ - "src/" - ] + "files": [ + "src/functions.php" + ], + "psr-4": { + "Ramsey\\Uuid\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Kore Nordmann", - "email": "mail@kore-nordmann.de" - }, - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } + "MIT" ], - "description": "Diff implementation", - "homepage": "https://github.com/sebastianbergmann/diff", + "description": "A PHP library for generating and working with universally unique identifiers (UUIDs).", "keywords": [ - "diff" + "guid", + "identifier", + "uuid" ], - "time": "2017-08-03T08:09:46+00:00" + "support": { + "issues": "https://github.com/ramsey/uuid/issues", + "source": "https://github.com/ramsey/uuid/tree/4.8.1" + }, + "time": "2025-06-01T06:28:46+00:00" }, { - "name": "sebastian/environment", - "version": "3.1.0", + "name": "react/promise", + "version": "v3.2.0", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "cd0871b3975fb7fc44d11314fd1ee20925fce4f5" + "url": "https://github.com/reactphp/promise.git", + "reference": "8a164643313c71354582dc850b42b33fa12a4b63" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/cd0871b3975fb7fc44d11314fd1ee20925fce4f5", - "reference": "cd0871b3975fb7fc44d11314fd1ee20925fce4f5", + "url": "https://api.github.com/repos/reactphp/promise/zipball/8a164643313c71354582dc850b42b33fa12a4b63", + "reference": "8a164643313c71354582dc850b42b33fa12a4b63", "shasum": "" }, "require": { - "php": "^7.0" + "php": ">=7.1.0" }, "require-dev": { - "phpunit/phpunit": "^6.1" + "phpstan/phpstan": "1.10.39 || 1.4.10", + "phpunit/phpunit": "^9.6 || ^7.5" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.1.x-dev" - } - }, "autoload": { - "classmap": [ - "src/" - ] + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "React\\Promise\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" } ], - "description": "Provides functionality to handle HHVM/PHP environments", - "homepage": "http://www.github.com/sebastianbergmann/environment", + "description": "A lightweight implementation of CommonJS Promises/A for PHP", "keywords": [ - "Xdebug", - "environment", - "hhvm" + "promise", + "promises" + ], + "support": { + "issues": "https://github.com/reactphp/promise/issues", + "source": "https://github.com/reactphp/promise/tree/v3.2.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } ], - "time": "2017-07-01T08:51:00+00:00" + "time": "2024-05-24T10:39:05+00:00" }, { - "name": "sebastian/exporter", - "version": "3.1.0", + "name": "sebastian/cli-parser", + "version": "2.0.1", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "234199f4528de6d12aaa58b612e98f7d36adb937" + "url": "https://github.com/sebastianbergmann/cli-parser.git", + "reference": "c34583b87e7b7a8055bf6c450c2c77ce32a24084" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/234199f4528de6d12aaa58b612e98f7d36adb937", - "reference": "234199f4528de6d12aaa58b612e98f7d36adb937", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/c34583b87e7b7a8055bf6c450c2c77ce32a24084", + "reference": "c34583b87e7b7a8055bf6c450c2c77ce32a24084", "shasum": "" }, "require": { - "php": "^7.0", - "sebastian/recursion-context": "^3.0" + "php": ">=8.1" }, "require-dev": { - "ext-mbstring": "*", - "phpunit/phpunit": "^6.0" + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.1.x-dev" + "dev-main": "2.0-dev" } }, "autoload": { @@ -3257,62 +4046,51 @@ "BSD-3-Clause" ], "authors": [ - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" - }, - { - "name": "Volker Dusch", - "email": "github@wallbash.com" - }, - { - "name": "Bernhard Schussek", - "email": "bschussek@2bepublished.at" - }, { "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Adam Harvey", - "email": "aharvey@php.net" + "email": "sebastian@phpunit.de", + "role": "lead" } ], - "description": "Provides the functionality to export PHP variables for visualization", - "homepage": "http://www.github.com/sebastianbergmann/exporter", - "keywords": [ - "export", - "exporter" + "description": "Library for parsing CLI options", + "homepage": "https://github.com/sebastianbergmann/cli-parser", + "support": { + "issues": "https://github.com/sebastianbergmann/cli-parser/issues", + "security": "https://github.com/sebastianbergmann/cli-parser/security/policy", + "source": "https://github.com/sebastianbergmann/cli-parser/tree/2.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } ], - "time": "2017-04-03T13:19:02+00:00" + "time": "2024-03-02T07:12:49+00:00" }, { - "name": "sebastian/global-state", + "name": "sebastian/code-unit", "version": "2.0.0", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4" + "url": "https://github.com/sebastianbergmann/code-unit.git", + "reference": "a81fee9eef0b7a76af11d121767abc44c104e503" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4", - "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/a81fee9eef0b7a76af11d121767abc44c104e503", + "reference": "a81fee9eef0b7a76af11d121767abc44c104e503", "shasum": "" }, "require": { - "php": "^7.0" + "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^6.0" - }, - "suggest": { - "ext-uopz": "*" + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-main": "2.0-dev" } }, "autoload": { @@ -3327,42 +4105,48 @@ "authors": [ { "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" + "email": "sebastian@phpunit.de", + "role": "lead" } ], - "description": "Snapshotting of global state", - "homepage": "http://www.github.com/sebastianbergmann/global-state", - "keywords": [ - "global state" + "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/2.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } ], - "time": "2017-04-27T15:39:26+00:00" + "time": "2023-02-03T06:58:43+00:00" }, { - "name": "sebastian/object-enumerator", - "version": "3.0.3", + "name": "sebastian/code-unit-reverse-lookup", + "version": "3.0.0", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/object-enumerator.git", - "reference": "7cfd9e65d11ffb5af41198476395774d4c8a84c5" + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "5e3a687f7d8ae33fb362c5c0743794bbb2420a1d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/7cfd9e65d11ffb5af41198476395774d4c8a84c5", - "reference": "7cfd9e65d11ffb5af41198476395774d4c8a84c5", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/5e3a687f7d8ae33fb362c5c0743794bbb2420a1d", + "reference": "5e3a687f7d8ae33fb362c5c0743794bbb2420a1d", "shasum": "" }, "require": { - "php": "^7.0", - "sebastian/object-reflector": "^1.1.1", - "sebastian/recursion-context": "^3.0" + "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^6.0" + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0.x-dev" + "dev-main": "3.0-dev" } }, "autoload": { @@ -3380,34 +4164,48 @@ "email": "sebastian@phpunit.de" } ], - "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" + "description": "Looks up which function or method a line of code belongs to", + "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", + "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/3.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T06:59:15+00:00" }, { - "name": "sebastian/object-reflector", - "version": "1.1.1", + "name": "sebastian/comparator", + "version": "5.0.3", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/object-reflector.git", - "reference": "773f97c67f28de00d397be301821b06708fca0be" + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "a18251eb0b7a2dcd2f7aa3d6078b18545ef0558e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/773f97c67f28de00d397be301821b06708fca0be", - "reference": "773f97c67f28de00d397be301821b06708fca0be", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/a18251eb0b7a2dcd2f7aa3d6078b18545ef0558e", + "reference": "a18251eb0b7a2dcd2f7aa3d6078b18545ef0558e", "shasum": "" }, "require": { - "php": "^7.0" + "ext-dom": "*", + "ext-mbstring": "*", + "php": ">=8.1", + "sebastian/diff": "^5.0", + "sebastian/exporter": "^5.0" }, "require-dev": { - "phpunit/phpunit": "^6.0" + "phpunit/phpunit": "^10.5" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.1-dev" + "dev-main": "5.0-dev" } }, "autoload": { @@ -3423,36 +4221,65 @@ { "name": "Sebastian Bergmann", "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" } ], - "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" + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "https://github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/comparator/issues", + "security": "https://github.com/sebastianbergmann/comparator/security/policy", + "source": "https://github.com/sebastianbergmann/comparator/tree/5.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-10-18T14:56:07+00:00" }, { - "name": "sebastian/recursion-context", - "version": "3.0.0", + "name": "sebastian/complexity", + "version": "3.2.0", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8" + "url": "https://github.com/sebastianbergmann/complexity.git", + "reference": "68ff824baeae169ec9f2137158ee529584553799" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8", - "reference": "5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/68ff824baeae169ec9f2137158ee529584553799", + "reference": "68ff824baeae169ec9f2137158ee529584553799", "shasum": "" }, "require": { - "php": "^7.0" + "nikic/php-parser": "^4.18 || ^5.0", + "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^6.0" + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0.x-dev" + "dev-main": "3.2-dev" } }, "autoload": { @@ -3465,44 +4292,52 @@ "BSD-3-Clause" ], "authors": [ - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" - }, { "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, + "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", + "security": "https://github.com/sebastianbergmann/complexity/security/policy", + "source": "https://github.com/sebastianbergmann/complexity/tree/3.2.0" + }, + "funding": [ { - "name": "Adam Harvey", - "email": "aharvey@php.net" + "url": "https://github.com/sebastianbergmann", + "type": "github" } ], - "description": "Provides functionality to recursively process PHP variables", - "homepage": "http://www.github.com/sebastianbergmann/recursion-context", - "time": "2017-03-03T06:23:57+00:00" + "time": "2023-12-21T08:37:17+00:00" }, { - "name": "sebastian/resource-operations", - "version": "1.0.0", + "name": "sebastian/diff", + "version": "5.1.1", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/resource-operations.git", - "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52" + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "c41e007b4b62af48218231d6c2275e4c9b975b2e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", - "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/c41e007b4b62af48218231d6c2275e4c9b975b2e", + "reference": "c41e007b4b62af48218231d6c2275e4c9b975b2e", "shasum": "" }, "require": { - "php": ">=5.6.0" + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0", + "symfony/process": "^6.4" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-main": "5.1-dev" } }, "autoload": { @@ -3518,33 +4353,60 @@ { "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", + "udiff", + "unidiff", + "unified diff" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/diff/issues", + "security": "https://github.com/sebastianbergmann/diff/security/policy", + "source": "https://github.com/sebastianbergmann/diff/tree/5.1.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" } ], - "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" + "time": "2024-03-02T07:15:17+00:00" }, { - "name": "sebastian/version", - "version": "2.0.1", + "name": "sebastian/environment", + "version": "6.1.0", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/version.git", - "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019" + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "8074dbcd93529b357029f5cc5058fd3e43666984" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/99732be0ddb3361e16ad77b68ba41efc8e979019", - "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/8074dbcd93529b357029f5cc5058fd3e43666984", + "reference": "8074dbcd93529b357029f5cc5058fd3e43666984", "shasum": "" }, "require": { - "php": ">=5.6" + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "suggest": { + "ext-posix": "*" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0.x-dev" + "dev-main": "6.1-dev" } }, "autoload": { @@ -3559,503 +4421,540 @@ "authors": [ { "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "email": "sebastian@phpunit.de" } ], - "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" + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "https://github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/environment/issues", + "security": "https://github.com/sebastianbergmann/environment/security/policy", + "source": "https://github.com/sebastianbergmann/environment/tree/6.1.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-23T08:47:14+00:00" }, { - "name": "symfony/browser-kit", - "version": "v3.4.15", + "name": "sebastian/exporter", + "version": "5.1.2", "source": { "type": "git", - "url": "https://github.com/symfony/browser-kit.git", - "reference": "f6668d1a6182d5a8dec65a1c863a4c1d963816c0" + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "955288482d97c19a372d3f31006ab3f37da47adf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/browser-kit/zipball/f6668d1a6182d5a8dec65a1c863a4c1d963816c0", - "reference": "f6668d1a6182d5a8dec65a1c863a4c1d963816c0", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/955288482d97c19a372d3f31006ab3f37da47adf", + "reference": "955288482d97c19a372d3f31006ab3f37da47adf", "shasum": "" }, "require": { - "php": "^5.5.9|>=7.0.8", - "symfony/dom-crawler": "~2.8|~3.0|~4.0" + "ext-mbstring": "*", + "php": ">=8.1", + "sebastian/recursion-context": "^5.0" }, "require-dev": { - "symfony/css-selector": "~2.8|~3.0|~4.0", - "symfony/process": "~2.8|~3.0|~4.0" - }, - "suggest": { - "symfony/process": "" + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.4-dev" + "dev-main": "5.1-dev" } }, "autoload": { - "psr-4": { - "Symfony\\Component\\BrowserKit\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" + "classmap": [ + "src/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" }, { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony BrowserKit Component", - "homepage": "https://symfony.com", - "time": "2018-07-26T09:06:28+00:00" - }, - { - "name": "symfony/console", - "version": "v3.4.15", - "source": { - "type": "git", - "url": "https://github.com/symfony/console.git", - "reference": "6b217594552b9323bcdcfc14f8a0ce126e84cd73" + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "https://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/exporter/issues", + "security": "https://github.com/sebastianbergmann/exporter/security/policy", + "source": "https://github.com/sebastianbergmann/exporter/tree/5.1.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-02T07:17:12+00:00" + }, + { + "name": "sebastian/global-state", + "version": "6.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "987bafff24ecc4c9ac418cab1145b96dd6e9cbd9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/6b217594552b9323bcdcfc14f8a0ce126e84cd73", - "reference": "6b217594552b9323bcdcfc14f8a0ce126e84cd73", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/987bafff24ecc4c9ac418cab1145b96dd6e9cbd9", + "reference": "987bafff24ecc4c9ac418cab1145b96dd6e9cbd9", "shasum": "" }, "require": { - "php": "^5.5.9|>=7.0.8", - "symfony/debug": "~2.8|~3.0|~4.0", - "symfony/polyfill-mbstring": "~1.0" - }, - "conflict": { - "symfony/dependency-injection": "<3.4", - "symfony/process": "<3.3" + "php": ">=8.1", + "sebastian/object-reflector": "^3.0", + "sebastian/recursion-context": "^5.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" - }, - "suggest": { - "psr/log-implementation": "For using the console logger", - "symfony/event-dispatcher": "", - "symfony/lock": "", - "symfony/process": "" + "ext-dom": "*", + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.4-dev" + "dev-main": "6.0-dev" } }, "autoload": { - "psr-4": { - "Symfony\\Component\\Console\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" + "classmap": [ + "src/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "https://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/global-state/issues", + "security": "https://github.com/sebastianbergmann/global-state/security/policy", + "source": "https://github.com/sebastianbergmann/global-state/tree/6.0.2" + }, + "funding": [ { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" + "url": "https://github.com/sebastianbergmann", + "type": "github" } ], - "description": "Symfony Console Component", - "homepage": "https://symfony.com", - "time": "2018-07-26T11:19:56+00:00" + "time": "2024-03-02T07:19:19+00:00" }, { - "name": "symfony/css-selector", - "version": "v3.4.15", + "name": "sebastian/lines-of-code", + "version": "2.0.2", "source": { "type": "git", - "url": "https://github.com/symfony/css-selector.git", - "reference": "edda5a6155000ff8c3a3f85ee5c421af93cca416" + "url": "https://github.com/sebastianbergmann/lines-of-code.git", + "reference": "856e7f6a75a84e339195d48c556f23be2ebf75d0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/edda5a6155000ff8c3a3f85ee5c421af93cca416", - "reference": "edda5a6155000ff8c3a3f85ee5c421af93cca416", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/856e7f6a75a84e339195d48c556f23be2ebf75d0", + "reference": "856e7f6a75a84e339195d48c556f23be2ebf75d0", "shasum": "" }, "require": { - "php": "^5.5.9|>=7.0.8" + "nikic/php-parser": "^4.18 || ^5.0", + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.4-dev" + "dev-main": "2.0-dev" } }, "autoload": { - "psr-4": { - "Symfony\\Component\\CssSelector\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" + "classmap": [ + "src/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Jean-François Simon", - "email": "jeanfrancois.simon@sensiolabs.com" - }, - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, + "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", + "security": "https://github.com/sebastianbergmann/lines-of-code/security/policy", + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/2.0.2" + }, + "funding": [ { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" + "url": "https://github.com/sebastianbergmann", + "type": "github" } ], - "description": "Symfony CssSelector Component", - "homepage": "https://symfony.com", - "time": "2018-07-26T09:06:28+00:00" + "time": "2023-12-21T08:38:20+00:00" }, { - "name": "symfony/debug", - "version": "v3.4.15", + "name": "sebastian/object-enumerator", + "version": "5.0.0", "source": { "type": "git", - "url": "https://github.com/symfony/debug.git", - "reference": "c4625e75341e4fb309ce0c049cbf7fb84b8897cd" + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "202d0e344a580d7f7d04b3fafce6933e59dae906" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/debug/zipball/c4625e75341e4fb309ce0c049cbf7fb84b8897cd", - "reference": "c4625e75341e4fb309ce0c049cbf7fb84b8897cd", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/202d0e344a580d7f7d04b3fafce6933e59dae906", + "reference": "202d0e344a580d7f7d04b3fafce6933e59dae906", "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" + "php": ">=8.1", + "sebastian/object-reflector": "^3.0", + "sebastian/recursion-context": "^5.0" }, "require-dev": { - "symfony/http-kernel": "~2.8|~3.0|~4.0" + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.4-dev" + "dev-main": "5.0-dev" } }, "autoload": { - "psr-4": { - "Symfony\\Component\\Debug\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" + "classmap": [ + "src/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/5.0.0" + }, + "funding": [ { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" + "url": "https://github.com/sebastianbergmann", + "type": "github" } ], - "description": "Symfony Debug Component", - "homepage": "https://symfony.com", - "time": "2018-08-03T10:42:44+00:00" + "time": "2023-02-03T07:08:32+00:00" }, { - "name": "symfony/dom-crawler", - "version": "v3.4.15", + "name": "sebastian/object-reflector", + "version": "3.0.0", "source": { "type": "git", - "url": "https://github.com/symfony/dom-crawler.git", - "reference": "452bfc854b60134438e3824b159b0d24a5892331" + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "24ed13d98130f0e7122df55d06c5c4942a577957" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/452bfc854b60134438e3824b159b0d24a5892331", - "reference": "452bfc854b60134438e3824b159b0d24a5892331", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/24ed13d98130f0e7122df55d06c5c4942a577957", + "reference": "24ed13d98130f0e7122df55d06c5c4942a577957", "shasum": "" }, "require": { - "php": "^5.5.9|>=7.0.8", - "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-mbstring": "~1.0" + "php": ">=8.1" }, "require-dev": { - "symfony/css-selector": "~2.8|~3.0|~4.0" - }, - "suggest": { - "symfony/css-selector": "" + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.4-dev" + "dev-main": "3.0-dev" } }, "autoload": { - "psr-4": { - "Symfony\\Component\\DomCrawler\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" + "classmap": [ + "src/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Allows reflection of object attributes, including inherited and non-public ones", + "homepage": "https://github.com/sebastianbergmann/object-reflector/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-reflector/issues", + "source": "https://github.com/sebastianbergmann/object-reflector/tree/3.0.0" + }, + "funding": [ { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" + "url": "https://github.com/sebastianbergmann", + "type": "github" } ], - "description": "Symfony DomCrawler Component", - "homepage": "https://symfony.com", - "time": "2018-07-26T10:03:52+00:00" + "time": "2023-02-03T07:06:18+00:00" }, { - "name": "symfony/event-dispatcher", - "version": "v3.4.15", + "name": "sebastian/recursion-context", + "version": "5.0.0", "source": { "type": "git", - "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "b2e1f19280c09a42dc64c0b72b80fe44dd6e88fb" + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "05909fb5bc7df4c52992396d0116aed689f93712" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/b2e1f19280c09a42dc64c0b72b80fe44dd6e88fb", - "reference": "b2e1f19280c09a42dc64c0b72b80fe44dd6e88fb", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/05909fb5bc7df4c52992396d0116aed689f93712", + "reference": "05909fb5bc7df4c52992396d0116aed689f93712", "shasum": "" }, "require": { - "php": "^5.5.9|>=7.0.8" - }, - "conflict": { - "symfony/dependency-injection": "<3.3" + "php": ">=8.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" - }, - "suggest": { - "symfony/dependency-injection": "", - "symfony/http-kernel": "" + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.4-dev" + "dev-main": "5.0-dev" } }, "autoload": { - "psr-4": { - "Symfony\\Component\\EventDispatcher\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" + "classmap": [ + "src/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" }, { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" } ], - "description": "Symfony EventDispatcher Component", - "homepage": "https://symfony.com", - "time": "2018-07-26T09:06:28+00:00" + "description": "Provides functionality to recursively process PHP variables", + "homepage": "https://github.com/sebastianbergmann/recursion-context", + "support": { + "issues": "https://github.com/sebastianbergmann/recursion-context/issues", + "source": "https://github.com/sebastianbergmann/recursion-context/tree/5.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T07:05:40+00:00" }, { - "name": "symfony/filesystem", - "version": "v3.4.15", + "name": "sebastian/type", + "version": "4.0.0", "source": { "type": "git", - "url": "https://github.com/symfony/filesystem.git", - "reference": "285ce5005cb01a0aeaa5b0cf590bd0cc40bb631c" + "url": "https://github.com/sebastianbergmann/type.git", + "reference": "462699a16464c3944eefc02ebdd77882bd3925bf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/285ce5005cb01a0aeaa5b0cf590bd0cc40bb631c", - "reference": "285ce5005cb01a0aeaa5b0cf590bd0cc40bb631c", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/462699a16464c3944eefc02ebdd77882bd3925bf", + "reference": "462699a16464c3944eefc02ebdd77882bd3925bf", "shasum": "" }, "require": { - "php": "^5.5.9|>=7.0.8", - "symfony/polyfill-ctype": "~1.8" + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.4-dev" + "dev-main": "4.0-dev" } }, "autoload": { - "psr-4": { - "Symfony\\Component\\Filesystem\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" + "classmap": [ + "src/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "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/4.0.0" + }, + "funding": [ { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" + "url": "https://github.com/sebastianbergmann", + "type": "github" } ], - "description": "Symfony Filesystem Component", - "homepage": "https://symfony.com", - "time": "2018-08-10T07:29:05+00:00" + "time": "2023-02-03T07:10:45+00:00" }, { - "name": "symfony/finder", - "version": "v3.4.15", + "name": "sebastian/version", + "version": "4.0.1", "source": { "type": "git", - "url": "https://github.com/symfony/finder.git", - "reference": "8a84fcb207451df0013b2c74cbbf1b62d47b999a" + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "c51fa83a5d8f43f1402e3f32a005e6262244ef17" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/8a84fcb207451df0013b2c74cbbf1b62d47b999a", - "reference": "8a84fcb207451df0013b2c74cbbf1b62d47b999a", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c51fa83a5d8f43f1402e3f32a005e6262244ef17", + "reference": "c51fa83a5d8f43f1402e3f32a005e6262244ef17", "shasum": "" }, "require": { - "php": "^5.5.9|>=7.0.8" + "php": ">=8.1" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.4-dev" + "dev-main": "4.0-dev" } }, "autoload": { - "psr-4": { - "Symfony\\Component\\Finder\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" + "classmap": [ + "src/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "support": { + "issues": "https://github.com/sebastianbergmann/version/issues", + "source": "https://github.com/sebastianbergmann/version/tree/4.0.1" + }, + "funding": [ { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" + "url": "https://github.com/sebastianbergmann", + "type": "github" } ], - "description": "Symfony Finder Component", - "homepage": "https://symfony.com", - "time": "2018-07-26T11:19:56+00:00" + "time": "2023-02-07T11:34:05+00:00" }, { - "name": "symfony/http-foundation", - "version": "v3.4.15", + "name": "seld/jsonlint", + "version": "1.11.0", "source": { "type": "git", - "url": "https://github.com/symfony/http-foundation.git", - "reference": "2fb33cb6eefe6e790e4023f7c534a9e4214252fc" + "url": "https://github.com/Seldaek/jsonlint.git", + "reference": "1748aaf847fc731cfad7725aec413ee46f0cc3a2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/2fb33cb6eefe6e790e4023f7c534a9e4214252fc", - "reference": "2fb33cb6eefe6e790e4023f7c534a9e4214252fc", + "url": "https://api.github.com/repos/Seldaek/jsonlint/zipball/1748aaf847fc731cfad7725aec413ee46f0cc3a2", + "reference": "1748aaf847fc731cfad7725aec413ee46f0cc3a2", "shasum": "" }, "require": { - "php": "^5.5.9|>=7.0.8", - "symfony/polyfill-mbstring": "~1.1", - "symfony/polyfill-php70": "~1.6" + "php": "^5.3 || ^7.0 || ^8.0" }, "require-dev": { - "symfony/expression-language": "~2.8|~3.0|~4.0" + "phpstan/phpstan": "^1.11", + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0 || ^8.5.13" }, + "bin": [ + "bin/jsonlint" + ], "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.4-dev" - } - }, "autoload": { "psr-4": { - "Symfony\\Component\\HttpFoundation\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] + "Seld\\JsonLint\\": "src/Seld/JsonLint/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -4063,51 +4962,61 @@ ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "https://seld.be" } ], - "description": "Symfony HttpFoundation Component", - "homepage": "https://symfony.com", - "time": "2018-08-27T17:45:33+00:00" - }, - { - "name": "symfony/polyfill-ctype", - "version": "v1.9.0", + "description": "JSON Linter", + "keywords": [ + "json", + "linter", + "parser", + "validator" + ], + "support": { + "issues": "https://github.com/Seldaek/jsonlint/issues", + "source": "https://github.com/Seldaek/jsonlint/tree/1.11.0" + }, + "funding": [ + { + "url": "https://github.com/Seldaek", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/seld/jsonlint", + "type": "tidelift" + } + ], + "time": "2024-07-11T14:55:45+00:00" + }, + { + "name": "seld/phar-utils", + "version": "1.2.1", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "e3d826245268269cd66f8326bd8bc066687b4a19" + "url": "https://github.com/Seldaek/phar-utils.git", + "reference": "ea2f4014f163c1be4c601b9b7bd6af81ba8d701c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/e3d826245268269cd66f8326bd8bc066687b4a19", - "reference": "e3d826245268269cd66f8326bd8bc066687b4a19", + "url": "https://api.github.com/repos/Seldaek/phar-utils/zipball/ea2f4014f163c1be4c601b9b7bd6af81ba8d701c", + "reference": "ea2f4014f163c1be4c601b9b7bd6af81ba8d701c", "shasum": "" }, "require": { - "php": ">=5.3.3" - }, - "suggest": { - "ext-ctype": "For best performance" + "php": ">=5.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.9-dev" + "dev-master": "1.x-dev" } }, "autoload": { "psr-4": { - "Symfony\\Polyfill\\Ctype\\": "" - }, - "files": [ - "bootstrap.php" - ] + "Seld\\PharUtils\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -4115,57 +5024,55 @@ ], "authors": [ { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - }, - { - "name": "Gert de Pagter", - "email": "BackEndTea@gmail.com" + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be" } ], - "description": "Symfony polyfill for ctype functions", - "homepage": "https://symfony.com", + "description": "PHAR file format utilities, for when PHP phars you up", "keywords": [ - "compatibility", - "ctype", - "polyfill", - "portable" + "phar" ], - "time": "2018-08-06T14:22:27+00:00" + "support": { + "issues": "https://github.com/Seldaek/phar-utils/issues", + "source": "https://github.com/Seldaek/phar-utils/tree/1.2.1" + }, + "time": "2022-08-31T10:31:18+00:00" }, { - "name": "symfony/polyfill-mbstring", - "version": "v1.9.0", + "name": "seld/signal-handler", + "version": "2.0.2", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "d0cd638f4634c16d8df4508e847f14e9e43168b8" + "url": "https://github.com/Seldaek/signal-handler.git", + "reference": "04a6112e883ad76c0ada8e4a9f7520bbfdb6bb98" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/d0cd638f4634c16d8df4508e847f14e9e43168b8", - "reference": "d0cd638f4634c16d8df4508e847f14e9e43168b8", + "url": "https://api.github.com/repos/Seldaek/signal-handler/zipball/04a6112e883ad76c0ada8e4a9f7520bbfdb6bb98", + "reference": "04a6112e883ad76c0ada8e4a9f7520bbfdb6bb98", "shasum": "" }, "require": { - "php": ">=5.3.3" + "php": ">=7.2.0" }, - "suggest": { - "ext-mbstring": "For best performance" + "require-dev": { + "phpstan/phpstan": "^1", + "phpstan/phpstan-deprecation-rules": "^1.0", + "phpstan/phpstan-phpunit": "^1", + "phpstan/phpstan-strict-rules": "^1.3", + "phpunit/phpunit": "^7.5.20 || ^8.5.23", + "psr/log": "^1 || ^2 || ^3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.9-dev" + "dev-main": "2.x-dev" } }, "autoload": { "psr-4": { - "Symfony\\Polyfill\\Mbstring\\": "" - }, - "files": [ - "bootstrap.php" - ] + "Seld\\Signal\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -4173,59 +5080,65 @@ ], "authors": [ { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" } ], - "description": "Symfony polyfill for the Mbstring extension", - "homepage": "https://symfony.com", + "description": "Simple unix signal handler that silently fails where signals are not supported for easy cross-platform development", "keywords": [ - "compatibility", - "mbstring", - "polyfill", - "portable", - "shim" + "posix", + "sigint", + "signal", + "sigterm", + "unix" ], - "time": "2018-08-06T14:22:27+00:00" + "support": { + "issues": "https://github.com/Seldaek/signal-handler/issues", + "source": "https://github.com/Seldaek/signal-handler/tree/2.0.2" + }, + "time": "2023-09-03T09:24:00+00:00" }, { - "name": "symfony/polyfill-php70", - "version": "v1.9.0", + "name": "spomky-labs/otphp", + "version": "11.3.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-php70.git", - "reference": "1e24b0c4a56d55aaf368763a06c6d1c7d3194934" + "url": "https://github.com/Spomky-Labs/otphp.git", + "reference": "2d8ccb5fc992b9cc65ef321fa4f00fefdb3f4b33" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php70/zipball/1e24b0c4a56d55aaf368763a06c6d1c7d3194934", - "reference": "1e24b0c4a56d55aaf368763a06c6d1c7d3194934", + "url": "https://api.github.com/repos/Spomky-Labs/otphp/zipball/2d8ccb5fc992b9cc65ef321fa4f00fefdb3f4b33", + "reference": "2d8ccb5fc992b9cc65ef321fa4f00fefdb3f4b33", "shasum": "" }, "require": { - "paragonie/random_compat": "~1.0|~2.0|~9.99", - "php": ">=5.3.3" + "ext-mbstring": "*", + "paragonie/constant_time_encoding": "^2.0 || ^3.0", + "php": ">=8.1", + "psr/clock": "^1.0", + "symfony/deprecation-contracts": "^3.2" }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.9-dev" - } + "require-dev": { + "ekino/phpstan-banned-code": "^1.0", + "infection/infection": "^0.26|^0.27|^0.28|^0.29", + "php-parallel-lint/php-parallel-lint": "^1.3", + "phpstan/phpstan": "^1.0", + "phpstan/phpstan-deprecation-rules": "^1.0", + "phpstan/phpstan-phpunit": "^1.0", + "phpstan/phpstan-strict-rules": "^1.0", + "phpunit/phpunit": "^9.5.26|^10.0|^11.0", + "qossmic/deptrac-shim": "^1.0", + "rector/rector": "^1.0", + "symfony/phpunit-bridge": "^6.1|^7.0", + "symplify/easy-coding-standard": "^12.0" }, + "type": "library", "autoload": { "psr-4": { - "Symfony\\Polyfill\\Php70\\": "" - }, - "files": [ - "bootstrap.php" - ], - "classmap": [ - "Resources/stubs" - ] + "OTPHP\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -4233,50 +5146,89 @@ ], "authors": [ { - "name": "Nicolas Grekas", - "email": "p@tchwork.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 polyfill backporting some PHP 7.0+ features to lower PHP versions", - "homepage": "https://symfony.com", + "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": [ - "compatibility", - "polyfill", - "portable", - "shim" + "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/11.3.0" + }, + "funding": [ + { + "url": "https://github.com/Spomky", + "type": "github" + }, + { + "url": "https://www.patreon.com/FlorentMorselli", + "type": "patreon" + } ], - "time": "2018-08-06T14:22:27+00:00" + "time": "2024-06-12T11:22:32+00:00" }, { - "name": "symfony/process", - "version": "v3.4.15", + "name": "symfony/console", + "version": "v6.4.22", "source": { "type": "git", - "url": "https://github.com/symfony/process.git", - "reference": "4d6b125d5293cbceedc2aa10f2c71617e76262e7" + "url": "https://github.com/symfony/console.git", + "reference": "7d29659bc3c9d8e9a34e2c3414ef9e9e003e6cf3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/4d6b125d5293cbceedc2aa10f2c71617e76262e7", - "reference": "4d6b125d5293cbceedc2aa10f2c71617e76262e7", + "url": "https://api.github.com/repos/symfony/console/zipball/7d29659bc3c9d8e9a34e2c3414ef9e9e003e6cf3", + "reference": "7d29659bc3c9d8e9a34e2c3414ef9e9e003e6cf3", "shasum": "" }, "require": { - "php": "^5.5.9|>=7.0.8" + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/string": "^5.4|^6.0|^7.0" }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.4-dev" - } + "conflict": { + "symfony/dependency-injection": "<5.4", + "symfony/dotenv": "<5.4", + "symfony/event-dispatcher": "<5.4", + "symfony/lock": "<5.4", + "symfony/process": "<5.4" + }, + "provide": { + "psr/log-implementation": "1.0|2.0|3.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^5.4|^6.0|^7.0", + "symfony/dependency-injection": "^5.4|^6.0|^7.0", + "symfony/event-dispatcher": "^5.4|^6.0|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/lock": "^5.4|^6.0|^7.0", + "symfony/messenger": "^5.4|^6.0|^7.0", + "symfony/process": "^5.4|^6.0|^7.0", + "symfony/stopwatch": "^5.4|^6.0|^7.0", + "symfony/var-dumper": "^5.4|^6.0|^7.0" }, + "type": "library", "autoload": { "psr-4": { - "Symfony\\Component\\Process\\": "" + "Symfony\\Component\\Console\\": "" }, "exclude-from-classmap": [ "/Tests/" @@ -4296,46 +5248,54 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony Process Component", + "description": "Eases the creation of beautiful and testable command line interfaces", "homepage": "https://symfony.com", - "time": "2018-08-03T10:42:44+00:00" + "keywords": [ + "cli", + "command-line", + "console", + "terminal" + ], + "support": { + "source": "https://github.com/symfony/console/tree/v6.4.22" + }, + "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": "2025-05-07T07:05:04+00:00" }, { - "name": "symfony/yaml", - "version": "v3.4.15", + "name": "symfony/css-selector", + "version": "v7.3.0", "source": { "type": "git", - "url": "https://github.com/symfony/yaml.git", - "reference": "c2f4812ead9f847cb69e90917ca7502e6892d6b8" + "url": "https://github.com/symfony/css-selector.git", + "reference": "601a5ce9aaad7bf10797e3663faefce9e26c24e2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/c2f4812ead9f847cb69e90917ca7502e6892d6b8", - "reference": "c2f4812ead9f847cb69e90917ca7502e6892d6b8", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/601a5ce9aaad7bf10797e3663faefce9e26c24e2", + "reference": "601a5ce9aaad7bf10797e3663faefce9e26c24e2", "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": ">=8.2" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.4-dev" - } - }, "autoload": { "psr-4": { - "Symfony\\Component\\Yaml\\": "" + "Symfony\\Component\\CssSelector\\": "" }, "exclude-from-classmap": [ "/Tests/" @@ -4350,136 +5310,221 @@ "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 Yaml Component", + "description": "Converts CSS selectors to XPath expressions", "homepage": "https://symfony.com", - "time": "2018-08-10T07:34:36+00:00" + "support": { + "source": "https://github.com/symfony/css-selector/tree/v7.3.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": "2024-09-25T14:21:43+00:00" }, { - "name": "theseer/tokenizer", - "version": "1.1.0", + "name": "symfony/deprecation-contracts", + "version": "v3.6.0", "source": { "type": "git", - "url": "https://github.com/theseer/tokenizer.git", - "reference": "cb2f008f3f05af2893a87208fe6a6c4985483f8b" + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theseer/tokenizer/zipball/cb2f008f3f05af2893a87208fe6a6c4985483f8b", - "reference": "cb2f008f3f05af2893a87208fe6a6c4985483f8b", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/63afe740e99a13ba87ec199bb07bbdee937a5b62", + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62", "shasum": "" }, "require": { - "ext-dom": "*", - "ext-tokenizer": "*", - "ext-xmlwriter": "*", - "php": "^7.0" + "php": ">=8.1" }, "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-dev" + } + }, "autoload": { - "classmap": [ - "src/" + "files": [ + "function.php" ] }, "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": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.6.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": "2024-09-25T14:21:43+00:00" }, { - "name": "vlucas/phpdotenv", - "version": "v2.5.1", + "name": "symfony/dotenv", + "version": "v6.4.16", "source": { "type": "git", - "url": "https://github.com/vlucas/phpdotenv.git", - "reference": "8abb4f9aa89ddea9d52112c65bbe8d0125e2fa8e" + "url": "https://github.com/symfony/dotenv.git", + "reference": "1ac5e7e7e862d4d574258daf08bd569ba926e4a5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/8abb4f9aa89ddea9d52112c65bbe8d0125e2fa8e", - "reference": "8abb4f9aa89ddea9d52112c65bbe8d0125e2fa8e", + "url": "https://api.github.com/repos/symfony/dotenv/zipball/1ac5e7e7e862d4d574258daf08bd569ba926e4a5", + "reference": "1ac5e7e7e862d4d574258daf08bd569ba926e4a5", "shasum": "" }, "require": { - "php": ">=5.3.9" + "php": ">=8.1" + }, + "conflict": { + "symfony/console": "<5.4", + "symfony/process": "<5.4" }, "require-dev": { - "phpunit/phpunit": "^4.8.35 || ^5.0" + "symfony/console": "^5.4|^6.0|^7.0", + "symfony/process": "^5.4|^6.0|^7.0" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.5-dev" - } - }, "autoload": { "psr-4": { - "Dotenv\\": "src/" - } + "Symfony\\Component\\Dotenv\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] }, "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": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "Loads environment variables from `.env` to `getenv()`, `$_ENV` and `$_SERVER` automagically.", + "description": "Registers environment variables from a .env file", + "homepage": "https://symfony.com", "keywords": [ "dotenv", "env", "environment" ], - "time": "2018-07-29T20:33:41+00:00" + "support": { + "source": "https://github.com/symfony/dotenv/tree/v6.4.16" + }, + "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": "2024-11-27T11:08:19+00:00" }, { - "name": "webmozart/assert", - "version": "1.3.0", + "name": "symfony/event-dispatcher", + "version": "v7.3.0", "source": { "type": "git", - "url": "https://github.com/webmozart/assert.git", - "reference": "0df1908962e7a3071564e857d86874dad1ef204a" + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "497f73ac996a598c92409b44ac43b6690c4f666d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webmozart/assert/zipball/0df1908962e7a3071564e857d86874dad1ef204a", - "reference": "0df1908962e7a3071564e857d86874dad1ef204a", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/497f73ac996a598c92409b44ac43b6690c4f666d", + "reference": "497f73ac996a598c92409b44ac43b6690c4f666d", "shasum": "" }, "require": { - "php": "^5.3.3 || ^7.0" + "php": ">=8.2", + "symfony/event-dispatcher-contracts": "^2.5|^3" + }, + "conflict": { + "symfony/dependency-injection": "<6.4", + "symfony/service-contracts": "<2.5" + }, + "provide": { + "psr/event-dispatcher-implementation": "1.0", + "symfony/event-dispatcher-implementation": "2.0|3.0" }, "require-dev": { - "phpunit/phpunit": "^4.6", - "sebastian/version": "^1.0.1" + "psr/log": "^1|^2|^3", + "symfony/config": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/error-handler": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/stopwatch": "^6.4|^7.0" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.3-dev" - } - }, "autoload": { "psr-4": { - "Webmozart\\Assert\\": "src/" - } + "Symfony\\Component\\EventDispatcher\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -4487,59 +5532,67 @@ ], "authors": [ { - "name": "Bernhard Schussek", - "email": "bschussek@gmail.com" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "Assertions to validate method input/output with nice error messages.", - "keywords": [ - "assert", - "check", - "validate" + "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/event-dispatcher/tree/v7.3.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" - } - ], - "packages-dev": [ + "time": "2025-04-22T09:11:45+00:00" + }, { - "name": "brainmaestro/composer-git-hooks", - "version": "v2.5.0", + "name": "symfony/event-dispatcher-contracts", + "version": "v3.6.0", "source": { "type": "git", - "url": "https://github.com/BrainMaestro/composer-git-hooks.git", - "reference": "5b2feb35fa8d460b14fc71792aca57f97d349430" + "url": "https://github.com/symfony/event-dispatcher-contracts.git", + "reference": "59eb412e93815df44f05f342958efa9f46b1e586" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/BrainMaestro/composer-git-hooks/zipball/5b2feb35fa8d460b14fc71792aca57f97d349430", - "reference": "5b2feb35fa8d460b14fc71792aca57f97d349430", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/59eb412e93815df44f05f342958efa9f46b1e586", + "reference": "59eb412e93815df44f05f342958efa9f46b1e586", "shasum": "" }, "require": { - "php": "^5.6 || >=7.0 <7.3", - "symfony/console": "^3.2 || ^4.0" + "php": ">=8.1", + "psr/event-dispatcher": "^1" }, - "require-dev": { - "friendsofphp/php-cs-fixer": "^2.9", - "phpunit/phpunit": "^5.7|^7.0" - }, - "bin": [ - "cghooks" - ], "type": "library", "extra": { - "hooks": { - "pre-commit": "composer check-style", - "pre-push": "composer test" + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-dev" } }, "autoload": { "psr-4": { - "BrainMaestro\\GitHooks\\": "src/" - }, - "files": [ - "src/helpers.php" - ] + "Symfony\\Contracts\\EventDispatcher\\": "" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -4547,47 +5600,72 @@ ], "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 dispatching event", + "homepage": "https://symfony.com", "keywords": [ - "HOOK", - "composer", - "git" + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.6.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-09-02T01:27:40+00:00" + "time": "2024-09-25T14:21:43+00:00" }, { - "name": "codacy/coverage", - "version": "1.4.2", + "name": "symfony/filesystem", + "version": "v7.3.0", "source": { "type": "git", - "url": "https://github.com/codacy/php-codacy-coverage.git", - "reference": "4988cd098db4d578681bfd3176071931ad475150" + "url": "https://github.com/symfony/filesystem.git", + "reference": "b8dce482de9d7c9fe2891155035a7248ab5c7fdb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/codacy/php-codacy-coverage/zipball/4988cd098db4d578681bfd3176071931ad475150", - "reference": "4988cd098db4d578681bfd3176071931ad475150", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/b8dce482de9d7c9fe2891155035a7248ab5c7fdb", + "reference": "b8dce482de9d7c9fe2891155035a7248ab5c7fdb", "shasum": "" }, "require": { - "gitonomy/gitlib": ">=1.0", - "php": ">=5.3.3", - "symfony/console": "~2.5|~3.0|~4.0" + "php": ">=8.2", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.8" }, "require-dev": { - "phpunit/phpunit": "~6.5" + "symfony/process": "^6.4|^7.0" }, - "bin": [ - "bin/codacycoverage" - ], "type": "library", "autoload": { - "classmap": [ - "src/" + "psr-4": { + "Symfony\\Component\\Filesystem\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" ] }, "notification-url": "https://packagist.org/downloads/", @@ -4596,44 +5674,63 @@ ], "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": "Provides basic utilities for the filesystem", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/filesystem/tree/v7.3.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" } ], - "description": "Sends PHP test coverage information to Codacy.", - "homepage": "https://github.com/codacy/php-codacy-coverage", - "time": "2018-03-22T16:43:39+00:00" + "time": "2024-10-25T15:15:23+00:00" }, { - "name": "codeception/aspect-mock", - "version": "3.0.1", + "name": "symfony/finder", + "version": "v6.4.17", "source": { "type": "git", - "url": "https://github.com/Codeception/AspectMock.git", - "reference": "061386697d2f47c4d3c695e28ee23a68a2383199" + "url": "https://github.com/symfony/finder.git", + "reference": "1d0e8266248c5d9ab6a87e3789e6dc482af3c9c7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Codeception/AspectMock/zipball/061386697d2f47c4d3c695e28ee23a68a2383199", - "reference": "061386697d2f47c4d3c695e28ee23a68a2383199", + "url": "https://api.github.com/repos/symfony/finder/zipball/1d0e8266248c5d9ab6a87e3789e6dc482af3c9c7", + "reference": "1d0e8266248c5d9ab6a87e3789e6dc482af3c9c7", "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": ">=8.1" }, "require-dev": { - "codeception/base": "^2.4", - "codeception/specify": "~0.3", - "codeception/verify": "~0.2" + "symfony/filesystem": "^6.0|^7.0" }, "type": "library", "autoload": { - "psr-0": { - "AspectMock": "src/" - } + "psr-4": { + "Symfony\\Component\\Finder\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -4641,48 +5738,80 @@ ], "authors": [ { - "name": "Michael Bodnarchuk", - "email": "davert@codeception.com" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Finds files and directories via an intuitive fluent interface", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/finder/tree/v6.4.17" + }, + "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" } ], - "description": "Experimental Mocking Framework powered by Aspects", - "time": "2018-07-31T20:44:39+00:00" + "time": "2024-12-29T13:51:37+00:00" }, { - "name": "doctrine/cache", - "version": "v1.6.2", + "name": "symfony/mime", + "version": "v6.4.21", "source": { "type": "git", - "url": "https://github.com/doctrine/cache.git", - "reference": "eb152c5100571c7a45470ff2a35095ab3f3b900b" + "url": "https://github.com/symfony/mime.git", + "reference": "fec8aa5231f3904754955fad33c2db50594d22d1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/cache/zipball/eb152c5100571c7a45470ff2a35095ab3f3b900b", - "reference": "eb152c5100571c7a45470ff2a35095ab3f3b900b", + "url": "https://api.github.com/repos/symfony/mime/zipball/fec8aa5231f3904754955fad33c2db50594d22d1", + "reference": "fec8aa5231f3904754955fad33c2db50594d22d1", "shasum": "" }, "require": { - "php": "~5.5|~7.0" + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-intl-idn": "^1.10", + "symfony/polyfill-mbstring": "^1.0" }, "conflict": { - "doctrine/common": ">2.2,<2.4" + "egulias/email-validator": "~3.0.0", + "phpdocumentor/reflection-docblock": "<3.2.2", + "phpdocumentor/type-resolver": "<1.4.0", + "symfony/mailer": "<5.4", + "symfony/serializer": "<6.4.3|>7.0,<7.0.3" }, "require-dev": { - "phpunit/phpunit": "~4.8|~5.0", - "predis/predis": "~1.0", - "satooshi/php-coveralls": "~0.6" + "egulias/email-validator": "^2.1.10|^3.1|^4", + "league/html-to-markdown": "^5.0", + "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", + "symfony/dependency-injection": "^5.4|^6.0|^7.0", + "symfony/process": "^5.4|^6.4|^7.0", + "symfony/property-access": "^5.4|^6.0|^7.0", + "symfony/property-info": "^5.4|^6.0|^7.0", + "symfony/serializer": "^6.4.3|^7.0.3" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.6.x-dev" - } - }, "autoload": { "psr-4": { - "Doctrine\\Common\\Cache\\": "lib/Doctrine/Common/Cache" - } + "Symfony\\Component\\Mime\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -4690,68 +5819,75 @@ ], "authors": [ { - "name": "Roman Borschel", - "email": "roman@code-factory.org" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" }, { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Allows manipulating MIME messages", + "homepage": "https://symfony.com", + "keywords": [ + "mime", + "mime-type" + ], + "support": { + "source": "https://github.com/symfony/mime/tree/v6.4.21" + }, + "funding": [ { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" + "url": "https://symfony.com/sponsor", + "type": "custom" }, { - "name": "Jonathan Wage", - "email": "jonwage@gmail.com" + "url": "https://github.com/fabpot", + "type": "github" }, { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" } ], - "description": "Caching library offering an object-oriented API for many cache backends", - "homepage": "http://www.doctrine-project.org", - "keywords": [ - "cache", - "caching" - ], - "time": "2017-07-22T12:49:21+00:00" + "time": "2025-04-27T13:27:38+00:00" }, { - "name": "gitonomy/gitlib", - "version": "v1.0.4", + "name": "symfony/polyfill-ctype", + "version": "v1.32.0", "source": { "type": "git", - "url": "https://github.com/gitonomy/gitlib.git", - "reference": "932a960221ae3484a3e82553b3be478e56beb68d" + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/gitonomy/gitlib/zipball/932a960221ae3484a3e82553b3be478e56beb68d", - "reference": "932a960221ae3484a3e82553b3be478e56beb68d", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638", "shasum": "" }, "require": { - "php": "^5.3 || ^7.0", - "symfony/process": "^2.3|^3.0|^4.0" + "php": ">=7.2" }, - "require-dev": { - "phpunit/phpunit": "^4.8.35|^5.7", - "psr/log": "^1.0" + "provide": { + "ext-ctype": "*" }, "suggest": { - "psr/log": "Add some log" + "ext-ctype": "For best performance" }, "type": "library", "extra": { - "branch-alias": { - "dev-master": "1.0-dev" + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { + "files": [ + "bootstrap.php" + ], "psr-4": { - "Gitonomy\\Git\\": "src/Gitonomy/Git/" + "Symfony\\Polyfill\\Ctype\\": "" } }, "notification-url": "https://packagist.org/downloads/", @@ -4760,64 +5896,74 @@ ], "authors": [ { - "name": "Alexandre Salomé", - "email": "alexandre.salome@gmail.com", - "homepage": "http://alexandre-salome.fr" + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.32.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" }, { - "name": "Julien DIDIER", - "email": "genzo.wm@gmail.com", - "homepage": "http://www.jdidier.net" + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" } ], - "description": "Library for accessing git", - "homepage": "http://gitonomy.com", - "time": "2018-04-22T19:55:36+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { - "name": "goaop/framework", - "version": "2.2.0", + "name": "symfony/polyfill-intl-grapheme", + "version": "v1.32.0", "source": { "type": "git", - "url": "https://github.com/goaop/framework.git", - "reference": "152abbffffcba72d2d159b892deb40b0829d0f28" + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/goaop/framework/zipball/152abbffffcba72d2d159b892deb40b0829d0f28", - "reference": "152abbffffcba72d2d159b892deb40b0829d0f28", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", + "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", "shasum": "" }, "require": { - "doctrine/annotations": "^1.2.3", - "doctrine/cache": "^1.5", - "goaop/parser-reflection": "~1.4", - "jakubledl/dissect": "~1.0", - "php": ">=5.6.0" - }, - "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" + "php": ">=7.2" }, "suggest": { - "symfony/console": "Enables the usage of the command-line tool." + "ext-intl": "For best performance" }, - "bin": [ - "bin/aspect" - ], "type": "library", "extra": { - "branch-alias": { - "dev-master": "2.0-dev" + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { + "files": [ + "bootstrap.php" + ], "psr-4": { - "Go\\": "src/" + "Symfony\\Polyfill\\Intl\\Grapheme\\": "" } }, "notification-url": "https://packagist.org/downloads/", @@ -4826,57 +5972,78 @@ ], "authors": [ { - "name": "Lisachenko Alexander", - "homepage": "https://github.com/lisachenko" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "Framework for aspect-oriented programming in PHP.", - "homepage": "http://go.aopphp.com/", + "description": "Symfony polyfill for intl's grapheme_* functions", + "homepage": "https://symfony.com", "keywords": [ - "aop", - "aspect", - "library", - "php" + "compatibility", + "grapheme", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.32.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-05T23:07:51+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { - "name": "goaop/parser-reflection", - "version": "1.4.1", + "name": "symfony/polyfill-intl-idn", + "version": "v1.32.0", "source": { "type": "git", - "url": "https://github.com/goaop/parser-reflection.git", - "reference": "d9c1dcc7ce4a5284fe3530e011faf9c9c10e1166" + "url": "https://github.com/symfony/polyfill-intl-idn.git", + "reference": "9614ac4d8061dc257ecc64cba1b140873dce8ad3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/goaop/parser-reflection/zipball/d9c1dcc7ce4a5284fe3530e011faf9c9c10e1166", - "reference": "d9c1dcc7ce4a5284fe3530e011faf9c9c10e1166", + "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/9614ac4d8061dc257ecc64cba1b140873dce8ad3", + "reference": "9614ac4d8061dc257ecc64cba1b140873dce8ad3", "shasum": "" }, "require": { - "nikic/php-parser": "^1.2|^2.0|^3.0", - "php": ">=5.6.0" + "php": ">=7.2", + "symfony/polyfill-intl-normalizer": "^1.10" }, - "require-dev": { - "phpunit/phpunit": "~4.0" + "suggest": { + "ext-intl": "For best performance" }, "type": "library", "extra": { - "branch-alias": { - "dev-master": "1.x-dev" + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { - "psr-4": { - "Go\\ParserReflection\\": "src" - }, "files": [ - "src/bootstrap.php" + "bootstrap.php" ], - "exclude-from-classmap": [ - "/tests/" - ] + "psr-4": { + "Symfony\\Polyfill\\Intl\\Idn\\": "" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -4884,76 +6051,84 @@ ], "authors": [ { - "name": "Alexander Lisachenko", - "email": "lisachenko.it@gmail.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 intl's idn_to_ascii and idn_to_utf8 functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "idn", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.32.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" } ], - "description": "Provides reflection information, based on raw source", - "time": "2018-03-19T15:57:41+00:00" + "time": "2024-09-10T14:38:51+00:00" }, { - "name": "guzzle/guzzle", - "version": "v3.8.1", + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.32.0", "source": { "type": "git", - "url": "https://github.com/guzzle/guzzle.git", - "reference": "4de0618a01b34aa1c8c33a3f13f396dcd3882eba" + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "3833d7255cc303546435cb650316bff708a1c75c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/4de0618a01b34aa1c8c33a3f13f396dcd3882eba", - "reference": "4de0618a01b34aa1c8c33a3f13f396dcd3882eba", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/3833d7255cc303546435cb650316bff708a1c75c", + "reference": "3833d7255cc303546435cb650316bff708a1c75c", "shasum": "" }, "require": { - "ext-curl": "*", - "php": ">=5.3.3", - "symfony/event-dispatcher": ">=2.1" + "php": ">=7.2" }, - "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" - }, - "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" + "suggest": { + "ext-intl": "For best performance" }, "type": "library", "extra": { - "branch-alias": { - "dev-master": "3.8-dev" + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { - "psr-0": { - "Guzzle": "src/", - "Guzzle\\Tests": "tests/" - } + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + }, + "classmap": [ + "Resources/stubs" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -4961,163 +6136,944 @@ ], "authors": [ { - "name": "Michael Dowling", - "email": "mtdowling@gmail.com", - "homepage": "https://github.com/mtdowling" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" }, { - "name": "Guzzle Community", - "homepage": "https://github.com/guzzle/guzzle/contributors" + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "Guzzle is a PHP HTTP client library and framework for building RESTful web service clients", - "homepage": "http://guzzlephp.org/", + "description": "Symfony polyfill for intl's Normalizer class and related functions", + "homepage": "https://symfony.com", "keywords": [ - "client", - "curl", - "framework", - "http", - "http client", - "rest", - "web service" + "compatibility", + "intl", + "normalizer", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.32.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" + } ], - "abandoned": "guzzlehttp/guzzle", - "time": "2014-01-28T22:29:15+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { - "name": "jakubledl/dissect", - "version": "v1.0.1", + "name": "symfony/polyfill-mbstring", + "version": "v1.32.0", "source": { "type": "git", - "url": "https://github.com/jakubledl/dissect.git", - "reference": "d3a391de31e45a247e95cef6cf58a91c05af67c4" + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/jakubledl/dissect/zipball/d3a391de31e45a247e95cef6cf58a91c05af67c4", - "reference": "d3a391de31e45a247e95cef6cf58a91c05af67c4", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493", + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493", "shasum": "" }, "require": { - "php": ">=5.3.3" + "ext-iconv": "*", + "php": ">=7.2" }, - "require-dev": { - "symfony/console": "~2.1" + "provide": { + "ext-mbstring": "*" }, "suggest": { - "symfony/console": "for the command-line tool" + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "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": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.32.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": "2024-12-23T08:48:59+00:00" + }, + { + "name": "symfony/polyfill-php73", + "version": "v1.32.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php73.git", + "reference": "0f68c03565dcaaf25a890667542e8bd75fe7e5bb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/0f68c03565dcaaf25a890667542e8bd75fe7e5bb", + "reference": "0f68c03565dcaaf25a890667542e8bd75fe7e5bb", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php73\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "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": "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.32.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": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.32.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/0cc9dd0f17f61d8131e7df6b84bd344899fe2608", + "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.32.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": "2025-01-02T08:10:11+00:00" + }, + { + "name": "symfony/polyfill-php81", + "version": "v1.32.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php81.git", + "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c", + "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php81\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "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": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php81/tree/v1.32.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": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/process", + "version": "v6.4.20", + "source": { + "type": "git", + "url": "https://github.com/symfony/process.git", + "reference": "e2a61c16af36c9a07e5c9906498b73e091949a20" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/process/zipball/e2a61c16af36c9a07e5c9906498b73e091949a20", + "reference": "e2a61c16af36c9a07e5c9906498b73e091949a20", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Process\\": "" + }, + "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": "Executes commands in sub-processes", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/process/tree/v6.4.20" + }, + "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": "2025-03-10T17:11:00+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v3.6.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/f021b05a130d35510bd6b25fe9053c2a8a15d5d4", + "reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "conflict": { + "ext-psr": "<1.1|>=2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "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 writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/service-contracts/tree/v3.6.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": "2025-04-25T09:37:31+00:00" + }, + { + "name": "symfony/string", + "version": "v7.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/string.git", + "reference": "f3570b8c61ca887a9e2938e85cb6458515d2b125" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/string/zipball/f3570b8c61ca887a9e2938e85cb6458515d2b125", + "reference": "f3570b8c61ca887a9e2938e85cb6458515d2b125", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-intl-grapheme": "~1.0", + "symfony/polyfill-intl-normalizer": "~1.0", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/translation-contracts": "<2.5" + }, + "require-dev": { + "symfony/emoji": "^7.1", + "symfony/error-handler": "^6.4|^7.0", + "symfony/http-client": "^6.4|^7.0", + "symfony/intl": "^6.4|^7.0", + "symfony/translation-contracts": "^2.5|^3.0", + "symfony/var-exporter": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/functions.php" + ], + "psr-4": { + "Symfony\\Component\\String\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "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": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", + "homepage": "https://symfony.com", + "keywords": [ + "grapheme", + "i18n", + "string", + "unicode", + "utf-8", + "utf8" + ], + "support": { + "source": "https://github.com/symfony/string/tree/v7.3.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": "2025-04-20T20:19:01+00:00" + }, + { + "name": "symfony/var-dumper", + "version": "v7.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "548f6760c54197b1084e1e5c71f6d9d523f2f78e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/548f6760c54197b1084e1e5c71f6d9d523f2f78e", + "reference": "548f6760c54197b1084e1e5c71f6d9d523f2f78e", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/console": "<6.4" + }, + "require-dev": { + "ext-iconv": "*", + "symfony/console": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/process": "^6.4|^7.0", + "symfony/uid": "^6.4|^7.0", + "twig/twig": "^3.12" + }, + "bin": [ + "Resources/bin/var-dump-server" + ], + "type": "library", + "autoload": { + "files": [ + "Resources/functions/dump.php" + ], + "psr-4": { + "Symfony\\Component\\VarDumper\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "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": "Provides mechanisms for walking through any arbitrary PHP variable", + "homepage": "https://symfony.com", + "keywords": [ + "debug", + "dump" + ], + "support": { + "source": "https://github.com/symfony/var-dumper/tree/v7.3.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": "2025-04-27T18:39:23+00:00" + }, + { + "name": "symfony/yaml", + "version": "v7.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "cea40a48279d58dc3efee8112634cb90141156c2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/yaml/zipball/cea40a48279d58dc3efee8112634cb90141156c2", + "reference": "cea40a48279d58dc3efee8112634cb90141156c2", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3.0", + "symfony/polyfill-ctype": "^1.8" + }, + "conflict": { + "symfony/console": "<6.4" + }, + "require-dev": { + "symfony/console": "^6.4|^7.0" + }, + "bin": [ + "Resources/bin/yaml-lint" + ], + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Yaml\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Loads and dumps YAML files", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/yaml/tree/v7.3.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": "2025-04-04T10:10:33+00:00" + }, + { + "name": "theseer/tokenizer", + "version": "1.2.3", + "source": { + "type": "git", + "url": "https://github.com/theseer/tokenizer.git", + "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", + "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "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.3" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2024-03-03T12:36:25+00:00" + }, + { + "name": "weew/helpers-array", + "version": "v1.3.1", + "source": { + "type": "git", + "url": "https://github.com/weew/helpers-array.git", + "reference": "9bff63111f9765b4277750db8d276d92b3e16ed0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/weew/helpers-array/zipball/9bff63111f9765b4277750db8d276d92b3e16ed0", + "reference": "9bff63111f9765b4277750db8d276d92b3e16ed0", + "shasum": "" + }, + "require-dev": { + "phpunit/phpunit": "^4.7", + "satooshi/php-coveralls": "^0.6.1" }, - "bin": [ - "bin/dissect.php", - "bin/dissect" - ], "type": "library", "autoload": { - "psr-0": { - "Dissect": [ - "src/" - ] - } + "files": [ + "src/array.php" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "unlicense" + "MIT" ], "authors": [ { - "name": "Jakub Lédl", - "email": "jakubledl@gmail.com" + "name": "Maxim Kott", + "email": "maximkott@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": "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": "nikic/php-parser", - "version": "v3.1.5", + "name": "brainmaestro/composer-git-hooks", + "version": "v3.0.0", "source": { "type": "git", - "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "bb87e28e7d7b8d9a7fda231d37457c9210faf6ce" + "url": "https://github.com/BrainMaestro/composer-git-hooks.git", + "reference": "684dc85f480268baf5e13f39a3cc494eeb2536e8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/bb87e28e7d7b8d9a7fda231d37457c9210faf6ce", - "reference": "bb87e28e7d7b8d9a7fda231d37457c9210faf6ce", + "url": "https://api.github.com/repos/BrainMaestro/composer-git-hooks/zipball/684dc85f480268baf5e13f39a3cc494eeb2536e8", + "reference": "684dc85f480268baf5e13f39a3cc494eeb2536e8", "shasum": "" }, "require": { - "ext-tokenizer": "*", - "php": ">=5.5" + "php": "^8.0", + "symfony/console": "^5.0|^6.0|^7.0" }, "require-dev": { - "phpunit/phpunit": "~4.0|~5.0" + "ext-json": "*", + "friendsofphp/php-cs-fixer": "^3.0", + "phpunit/phpunit": "^9|^10|^11" }, "bin": [ - "bin/php-parse" + "cghooks" ], "type": "library", "extra": { - "branch-alias": { - "dev-master": "3.0-dev" + "hooks": { + "pre-push": [ + "composer test", + "appver=$(grep -o -E '[0-9]+\\.[0-9]+\\.[0-9]+(-alpha\\.[0-9]+)?' cghooks)", + "tag=$(git tag | tail -n 1)", + "tag=${tag#v}", + "if [ \"$tag\" != \"$appver\" ]; then", + "echo \"The most recent tag $tag does not match the application version $appver\\n\"", + "sed -i -E \"s/$appver/$tag/\" cghooks", + "exit 1", + "fi" + ], + "pre-commit": "composer check-style" } }, "autoload": { + "files": [ + "src/helpers.php" + ], "psr-4": { - "PhpParser\\": "lib/PhpParser" + "BrainMaestro\\GitHooks\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Nikita Popov" + "name": "Ezinwa Okpoechi", + "email": "brainmaestro@outlook.com" } ], - "description": "A PHP parser written in PHP", + "description": "Easily manage git hooks in your composer config", "keywords": [ - "parser", - "php" + "HOOK", + "composer", + "git" ], - "time": "2018-02-28T20:30:58+00:00" + "support": { + "issues": "https://github.com/BrainMaestro/composer-git-hooks/issues", + "source": "https://github.com/BrainMaestro/composer-git-hooks/tree/v3.0.0" + }, + "time": "2024-06-22T09:17:59+00:00" }, { "name": "pdepend/pdepend", - "version": "2.5.2", + "version": "2.16.2", "source": { "type": "git", "url": "https://github.com/pdepend/pdepend.git", - "reference": "9daf26d0368d4a12bed1cacae1a9f3a6f0adf239" + "reference": "f942b208dc2a0868454d01b29f0c75bbcfc6ed58" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/pdepend/pdepend/zipball/9daf26d0368d4a12bed1cacae1a9f3a6f0adf239", - "reference": "9daf26d0368d4a12bed1cacae1a9f3a6f0adf239", + "url": "https://api.github.com/repos/pdepend/pdepend/zipball/f942b208dc2a0868454d01b29f0c75bbcfc6ed58", + "reference": "f942b208dc2a0868454d01b29f0c75bbcfc6ed58", "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|^6.0|^7.0", + "symfony/dependency-injection": "^2.3.0|^3|^4|^5|^6.0|^7.0", + "symfony/filesystem": "^2.3.0|^3|^4|^5|^6.0|^7.0", + "symfony/polyfill-mbstring": "^1.19" }, "require-dev": { - "phpunit/phpunit": "^4.8|^5.7", + "easy-doc/easy-doc": "0.0.0|^1.2.3", + "gregwar/rst": "^1.0", "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" @@ -5128,46 +7084,64 @@ "BSD-3-Clause" ], "description": "Official version of pdepend to be handled with Composer", - "time": "2017-12-13T13:21:38+00:00" + "keywords": [ + "PHP Depend", + "PHP_Depend", + "dev", + "pdepend" + ], + "support": { + "issues": "https://github.com/pdepend/pdepend/issues", + "source": "https://github.com/pdepend/pdepend/tree/2.16.2" + }, + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/pdepend/pdepend", + "type": "tidelift" + } + ], + "time": "2023-12-17T18:09:59+00:00" }, { "name": "php-coveralls/php-coveralls", - "version": "v1.1.0", + "version": "v2.8.0", "source": { "type": "git", "url": "https://github.com/php-coveralls/php-coveralls.git", - "reference": "37f8f83fe22224eb9d9c6d593cdeb33eedd2a9ad" + "reference": "00b9fce4d785a98760ca02f305c197f5fcfb6004" }, "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/00b9fce4d785a98760ca02f305c197f5fcfb6004", + "reference": "00b9fce4d785a98760ca02f305c197f5fcfb6004", "shasum": "" }, "require": { "ext-json": "*", "ext-simplexml": "*", - "guzzle/guzzle": "^2.8 || ^3.0", - "php": "^5.3.3 || ^7.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" + "guzzlehttp/guzzle": "^6.0 || ^7.0", + "php": "^7.0 || ^8.0", + "psr/log": "^1.0 || ^2.0", + "symfony/config": "^2.1 || ^3.0 || ^4.0 || ^5.0 || ^6.0 || ^7.0", + "symfony/console": "^2.1 || ^3.0 || ^4.0 || ^5.0 || ^6.0 || ^7.0", + "symfony/stopwatch": "^2.0 || ^3.0 || ^4.0 || ^5.0 || ^6.0 || ^7.0", + "symfony/yaml": "^2.0.5 || ^3.0 || ^4.0 || ^5.0 || ^6.0 || ^7.0" }, "require-dev": { - "phpunit/phpunit": "^4.8.35 || ^5.4.3 || ^6.0" + "phpspec/prophecy-phpunit": "^1.1 || ^2.3", + "phpunit/phpunit": "^4.8.35 || ^5.4.3 || ^6.0 || ^7.0 || >=8.0 <8.5.29 || >=9.0 <9.5.23", + "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/", @@ -5178,7 +7152,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", @@ -5189,35 +7180,44 @@ "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.8.0" + }, + "time": "2025-05-12T08:35:27+00:00" }, { "name": "phpmd/phpmd", - "version": "2.6.0", + "version": "2.15.0", "source": { "type": "git", "url": "https://github.com/phpmd/phpmd.git", - "reference": "4e9924b2c157a3eb64395460fcf56b31badc8374" + "reference": "74a1f56e33afad4128b886e334093e98e1b5e7c0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpmd/phpmd/zipball/4e9924b2c157a3eb64395460fcf56b31badc8374", - "reference": "4e9924b2c157a3eb64395460fcf56b31badc8374", + "url": "https://api.github.com/repos/phpmd/phpmd/zipball/74a1f56e33afad4128b886e334093e98e1b5e7c0", + "reference": "74a1f56e33afad4128b886e334093e98e1b5e7c0", "shasum": "" }, "require": { + "composer/xdebug-handler": "^1.0 || ^2.0 || ^3.0", "ext-xml": "*", - "pdepend/pdepend": "^2.5", + "pdepend/pdepend": "^2.16.1", "php": ">=5.3.9" }, "require-dev": { - "phpunit/phpunit": "^4.0", - "squizlabs/php_codesniffer": "^2.0" + "easy-doc/easy-doc": "0.0.0 || ^1.3.2", + "ext-json": "*", + "ext-simplexml": "*", + "gregwar/rst": "^1.0", + "mikey179/vfsstream": "^1.6.8", + "squizlabs/php_codesniffer": "^2.9.2 || ^3.7.2" }, "bin": [ "src/bin/phpmd" ], - "type": "project", + "type": "library", "autoload": { "psr-0": { "PHPMD\\": "src/main/php" @@ -5234,172 +7234,53 @@ "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": [ + "dev", "mess detection", "mess detector", "pdepend", "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/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "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" - }, - { - "name": "sebastian/phpcpd", - "version": "3.0.1", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/phpcpd.git", - "reference": "dfed51c1288790fc957c9433e2f49ab152e8a564" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpcpd/zipball/dfed51c1288790fc957c9433e2f49ab152e8a564", - "reference": "dfed51c1288790fc957c9433e2f49ab152e8a564", - "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" - }, - "bin": [ - "phpcpd" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.0-dev" - } - }, - "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.15.0" }, - "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": "Copy/Paste Detector (CPD) for PHP code.", - "homepage": "https://github.com/sebastianbergmann/phpcpd", - "time": "2017-11-16T08:49:28+00:00" + "time": "2023-12-11T08:22:20+00:00" }, { "name": "squizlabs/php_codesniffer", - "version": "3.3.1", + "version": "3.10.3", "source": { "type": "git", - "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", - "reference": "628a481780561150481a9ec74709092b9759b3ec" + "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", + "reference": "62d32998e820bddc40f99f8251958aed187a5c9c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/628a481780561150481a9ec74709092b9759b3ec", - "reference": "628a481780561150481a9ec74709092b9759b3ec", + "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/62d32998e820bddc40f99f8251958aed187a5c9c", + "reference": "62d32998e820bddc40f99f8251958aed187a5c9c", "shasum": "" }, "require": { @@ -5409,11 +7290,11 @@ "php": ">=5.4.0" }, "require-dev": { - "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.3.4" }, "bin": [ - "bin/phpcs", - "bin/phpcbf" + "bin/phpcbf", + "bin/phpcs" ], "type": "library", "extra": { @@ -5428,55 +7309,78 @@ "authors": [ { "name": "Greg Sherwood", - "role": "lead" + "role": "Former lead" + }, + { + "name": "Juliette Reinders Folmer", + "role": "Current lead" + }, + { + "name": "Contributors", + "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer/graphs/contributors" } ], "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/PHPCSStandards/PHP_CodeSniffer", "keywords": [ "phpcs", - "standards" + "standards", + "static analysis" + ], + "support": { + "issues": "https://github.com/PHPCSStandards/PHP_CodeSniffer/issues", + "security": "https://github.com/PHPCSStandards/PHP_CodeSniffer/security/policy", + "source": "https://github.com/PHPCSStandards/PHP_CodeSniffer", + "wiki": "https://github.com/PHPCSStandards/PHP_CodeSniffer/wiki" + }, + "funding": [ + { + "url": "https://github.com/PHPCSStandards", + "type": "github" + }, + { + "url": "https://github.com/jrfnl", + "type": "github" + }, + { + "url": "https://opencollective.com/php_codesniffer", + "type": "open_collective" + } ], - "time": "2018-07-26T23:47:18+00:00" + "time": "2024-09-18T10:38:58+00:00" }, { "name": "symfony/config", - "version": "v3.4.15", + "version": "v7.3.0", "source": { "type": "git", "url": "https://github.com/symfony/config.git", - "reference": "7b08223b7f6abd859651c56bcabf900d1627d085" + "reference": "ba62ae565f1327c2f6366726312ed828c85853bc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/config/zipball/7b08223b7f6abd859651c56bcabf900d1627d085", - "reference": "7b08223b7f6abd859651c56bcabf900d1627d085", + "url": "https://api.github.com/repos/symfony/config/zipball/ba62ae565f1327c2f6366726312ed828c85853bc", + "reference": "ba62ae565f1327c2f6366726312ed828c85853bc", "shasum": "" }, "require": { - "php": "^5.5.9|>=7.0.8", - "symfony/filesystem": "~2.8|~3.0|~4.0", + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/filesystem": "^7.1", "symfony/polyfill-ctype": "~1.8" }, "conflict": { - "symfony/dependency-injection": "<3.3", - "symfony/finder": "<3.3" + "symfony/finder": "<6.4", + "symfony/service-contracts": "<2.5" }, "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" - }, - "suggest": { - "symfony/yaml": "To use the yaml reference dumper" + "symfony/event-dispatcher": "^6.4|^7.0", + "symfony/finder": "^6.4|^7.0", + "symfony/messenger": "^6.4|^7.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/yaml": "^6.4|^7.0" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.4-dev" - } - }, "autoload": { "psr-4": { "Symfony\\Component\\Config\\": "" @@ -5499,55 +7403,64 @@ "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-07-26T11:19:56+00:00" + "support": { + "source": "https://github.com/symfony/config/tree/v7.3.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": "2025-05-15T09:04:05+00:00" }, { "name": "symfony/dependency-injection", - "version": "v3.4.15", + "version": "v7.3.0", "source": { "type": "git", "url": "https://github.com/symfony/dependency-injection.git", - "reference": "09d7df7bf06c1393b6afc85875993cbdbdf897a0" + "reference": "f64a8f3fa7d4ad5e85de1b128a0e03faed02b732" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/09d7df7bf06c1393b6afc85875993cbdbdf897a0", - "reference": "09d7df7bf06c1393b6afc85875993cbdbdf897a0", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/f64a8f3fa7d4ad5e85de1b128a0e03faed02b732", + "reference": "f64a8f3fa7d4ad5e85de1b128a0e03faed02b732", "shasum": "" }, "require": { - "php": "^5.5.9|>=7.0.8", - "psr/container": "^1.0" + "php": ">=8.2", + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/service-contracts": "^3.5", + "symfony/var-exporter": "^6.4.20|^7.2.5" }, "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": "<6.4", + "symfony/finder": "<6.4", + "symfony/yaml": "<6.4" }, "provide": { - "psr/container-implementation": "1.0" + "psr/container-implementation": "1.1|2.0", + "symfony/service-implementation": "1.1|2.0|3.0" }, "require-dev": { - "symfony/config": "~3.3|~4.0", - "symfony/expression-language": "~2.8|~3.0|~4.0", - "symfony/yaml": "~3.4|~4.0" - }, - "suggest": { - "symfony/config": "", - "symfony/expression-language": "For using expressions in service container configuration", - "symfony/finder": "For using double-star glob patterns or when GLOB_BRACE portability is required", - "symfony/proxy-manager-bridge": "Generate service proxies to lazy load them", - "symfony/yaml": "" + "symfony/config": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/yaml": "^6.4|^7.0" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.4-dev" - } - }, "autoload": { "psr-4": { "Symfony\\Component\\DependencyInjection\\": "" @@ -5570,33 +7483,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-08-08T11:42:34+00:00" + "support": { + "source": "https://github.com/symfony/dependency-injection/tree/v7.3.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": "2025-05-19T13:28:56+00:00" }, { "name": "symfony/stopwatch", - "version": "v3.4.15", + "version": "v7.3.0", "source": { "type": "git", "url": "https://github.com/symfony/stopwatch.git", - "reference": "deda2765e8dab2fc38492e926ea690f2a681f59d" + "reference": "5a49289e2b308214c8b9c2fda4ea454d8b8ad7cd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/stopwatch/zipball/deda2765e8dab2fc38492e926ea690f2a681f59d", - "reference": "deda2765e8dab2fc38492e926ea690f2a681f59d", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/5a49289e2b308214c8b9c2fda4ea454d8b8ad7cd", + "reference": "5a49289e2b308214c8b9c2fda4ea454d8b8ad7cd", "shasum": "" }, "require": { - "php": "^5.5.9|>=7.0.8" + "php": ">=8.2", + "symfony/service-contracts": "^2.5|^3" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.4-dev" - } - }, "autoload": { "psr-4": { "Symfony\\Component\\Stopwatch\\": "" @@ -5619,49 +7545,103 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony Stopwatch Component", + "description": "Provides a way to profile code", "homepage": "https://symfony.com", - "time": "2018-07-26T10:03:52+00:00" + "support": { + "source": "https://github.com/symfony/stopwatch/tree/v7.3.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": "2025-02-24T10:49:57+00:00" }, { - "name": "theseer/fdomdocument", - "version": "1.6.6", + "name": "symfony/var-exporter", + "version": "v7.3.0", "source": { "type": "git", - "url": "https://github.com/theseer/fDOMDocument.git", - "reference": "6e8203e40a32a9c770bcb62fe37e68b948da6dca" + "url": "https://github.com/symfony/var-exporter.git", + "reference": "c9a1168891b5aaadfd6332ef44393330b3498c4c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theseer/fDOMDocument/zipball/6e8203e40a32a9c770bcb62fe37e68b948da6dca", - "reference": "6e8203e40a32a9c770bcb62fe37e68b948da6dca", + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/c9a1168891b5aaadfd6332ef44393330b3498c4c", + "reference": "c9a1168891b5aaadfd6332ef44393330b3498c4c", "shasum": "" }, "require": { - "ext-dom": "*", - "lib-libxml": "*", - "php": ">=5.3.3" + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "require-dev": { + "symfony/property-access": "^6.4|^7.0", + "symfony/serializer": "^6.4|^7.0", + "symfony/var-dumper": "^6.4|^7.0" }, "type": "library", "autoload": { - "classmap": [ - "src/" + "psr-4": { + "Symfony\\Component\\VarExporter\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Arne Blankerts", - "email": "arne@blankerts.de", - "role": "lead" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Allows exporting any serializable PHP data structure to plain PHP code", + "homepage": "https://symfony.com", + "keywords": [ + "clone", + "construct", + "export", + "hydrate", + "instantiate", + "lazy-loading", + "proxy", + "serialize" + ], + "support": { + "source": "https://github.com/symfony/var-exporter/tree/v7.3.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" } ], - "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": "2025-05-15T09:04:05+00:00" } ], "aliases": [], @@ -5670,7 +7650,14 @@ "prefer-stable": false, "prefer-lowest": false, "platform": { - "php": "7.0.2|7.0.4|~7.0.6|~7.1.0|~7.2.0" + "ext-curl": "*", + "ext-dom": "*", + "ext-iconv": "*", + "ext-intl": "*", + "ext-json": "*", + "ext-openssl": "*", + "php": ">=8.2" }, - "platform-dev": [] + "platform-dev": [], + "plugin-api-version": "2.2.0" } diff --git a/dev/tests/_bootstrap.php b/dev/tests/_bootstrap.php index b55f12c2b..677fd5114 100644 --- a/dev/tests/_bootstrap.php +++ b/dev/tests/_bootstrap.php @@ -1,7 +1,7 @@ init([ - 'debug' => true, - 'includePaths' => [PROJECT_ROOT . DIRECTORY_SEPARATOR . 'src'], - '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_DEFAULT, false ); @@ -41,16 +30,17 @@ '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) { $_ENV[$key] = $value; - putenv("{$key}=${value}"); + 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); @@ -60,6 +50,28 @@ defined('TESTS_BP') || define('TESTS_BP', __DIR__); defined('TESTS_MODULE_PATH') || define('TESTS_MODULE_PATH', TESTS_BP . $RELATIVE_TESTS_MODULE_PATH); defined('MAGENTO_BP') || define('MAGENTO_BP', __DIR__); +define('DOCS_OUTPUT_DIR', + FW_BP . + DIRECTORY_SEPARATOR . + "dev" . + DIRECTORY_SEPARATOR . + "tests" . + DIRECTORY_SEPARATOR . + "unit" . + DIRECTORY_SEPARATOR . + "_output" +); +define('RESOURCE_DIR', + FW_BP . + DIRECTORY_SEPARATOR . + "dev" . + DIRECTORY_SEPARATOR . + "tests" . + DIRECTORY_SEPARATOR . + "unit" . + DIRECTORY_SEPARATOR . + "Resources" +); $utilDir = DIRECTORY_SEPARATOR . 'Util'. DIRECTORY_SEPARATOR . '*.php'; @@ -75,27 +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' -]; - -// 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..192bf35db 100755 --- a/dev/tests/functional/standalone_bootstrap.php +++ b/dev/tests/functional/standalone_bootstrap.php @@ -1,7 +1,7 @@ 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..6d9ff81eb --- /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..bfc53da33 --- /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..fec33301a --- /dev/null +++ b/dev/tests/functional/tests/MFTF/DevDocs/Data/DeprecatedMessageData.xml @@ -0,0 +1,17 @@ + + + + + + Introduction to the 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..49d0b74be --- /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..3bffebb35 --- /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..025fad68e --- /dev/null +++ b/dev/tests/functional/tests/MFTF/DevDocs/Data/MessageData.xml @@ -0,0 +1,26 @@ + + + + + + Introduction to the 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..2a51a8b9b --- /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..76f16566b --- /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 2b706ed67..4ee1c1239 100644 --- a/dev/tests/functional/tests/MFTF/DevDocs/Page/MFTFDocPage.xml +++ b/dev/tests/functional/tests/MFTF/DevDocs/Page/MFTFDocPage.xml @@ -1,14 +1,14 @@ - + 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..db48a5c43 100644 --- a/dev/tests/functional/tests/MFTF/DevDocs/Section/ContentSection.xml +++ b/dev/tests/functional/tests/MFTF/DevDocs/Section/ContentSection.xml @@ -1,14 +1,18 @@ + + + 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..75c454e4d --- /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..e9c4a9b38 --- /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 eceeb3f58..2d856d2dc 100644 --- a/dev/tests/functional/tests/MFTF/DevDocs/Test/DevDocsTest.xml +++ b/dev/tests/functional/tests/MFTF/DevDocs/Test/DevDocsTest.xml @@ -1,15 +1,16 @@ <?xml version="1.0" encoding="UTF-8"?> <!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ +/** + * Copyright 2018 Adobe + * All Rights Reserved. + */ --> <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 --> <features value="DevDocs available"/> <stories value="MFTF DevDocs available"/> <title value="Magento Functional Testing Framework Documentation is available."/> @@ -18,7 +19,46 @@ <group value="mftf"/> </annotations> + <!-- 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..75380de97 --- /dev/null +++ b/dev/tests/functional/tests/MFTF/DevDocs/Test/FormatCurrencyTest.xml @@ -0,0 +1,52 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> + +<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..f5d8dc2b8 100644 --- a/dev/tests/phpunit.xml +++ b/dev/tests/phpunit.xml @@ -1,34 +1,32 @@ +<?xml version="1.0"?> <!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ +/** + * Copyright 2017 Adobe + * All Rights Reserved. + */ --> - -<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"> - <testsuites> - <testsuite name="verification"> - <directory>verification</directory> - </testsuite> - <testsuite name="unit"> - <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> +<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.4/phpunit.xsd" bootstrap="_bootstrap.php" backupGlobals="false" cacheDirectory=".phpunit.cache"> + <coverage> + <report> + <clover outputFile="build/logs/clover.xml"/> + </report> + </coverage> + <testsuites> + <testsuite name="verification"> + <directory>verification</directory> + </testsuite> + <testsuite name="unit"> + <directory>unit</directory> + </testsuite> + </testsuites> + <logging/> + <source> + <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> + </source> </phpunit> diff --git a/dev/tests/static/Magento/CodeMessDetector/Rule/Design/FinalImplementation.php b/dev/tests/static/Magento/CodeMessDetector/Rule/Design/FinalImplementation.php index 22c008b05..adb7fcdbf 100644 --- a/dev/tests/static/Magento/CodeMessDetector/Rule/Design/FinalImplementation.php +++ b/dev/tests/static/Magento/CodeMessDetector/Rule/Design/FinalImplementation.php @@ -1,7 +1,7 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ namespace Magento\CodeMessDetector\Rule\Design; diff --git a/dev/tests/static/Magento/CodeMessDetector/resources/rulesets/design.xml b/dev/tests/static/Magento/CodeMessDetector/resources/rulesets/design.xml index 61d4f7b3b..dae559a53 100644 --- a/dev/tests/static/Magento/CodeMessDetector/resources/rulesets/design.xml +++ b/dev/tests/static/Magento/CodeMessDetector/resources/rulesets/design.xml @@ -1,8 +1,8 @@ <?xml version="1.0"?> <!-- /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ --> <ruleset name="Magento Specific Design Rules" diff --git a/dev/tests/static/Magento/CodeMessDetector/ruleset.xml b/dev/tests/static/Magento/CodeMessDetector/ruleset.xml index 61cdbde95..af929d0f7 100644 --- a/dev/tests/static/Magento/CodeMessDetector/ruleset.xml +++ b/dev/tests/static/Magento/CodeMessDetector/ruleset.xml @@ -1,8 +1,8 @@ <?xml version='1.0' encoding="UTF-8"?> <!-- /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ --> <ruleset name="Magento PHPMD rule set" xmlns="http://pmd.sf.net/ruleset/1.0.0" diff --git a/dev/tests/static/Magento/Sniffs/Arrays/ShortArraySyntaxSniff.php b/dev/tests/static/Magento/Sniffs/Arrays/ShortArraySyntaxSniff.php index 9013f12b6..a13bb666e 100644 --- a/dev/tests/static/Magento/Sniffs/Arrays/ShortArraySyntaxSniff.php +++ b/dev/tests/static/Magento/Sniffs/Arrays/ShortArraySyntaxSniff.php @@ -1,8 +1,9 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ + namespace Magento\Sniffs\Arrays; use PHP_CodeSniffer\Sniffs\Sniff; diff --git a/dev/tests/static/Magento/Sniffs/Commenting/FunctionCommentSniff.php b/dev/tests/static/Magento/Sniffs/Commenting/FunctionCommentSniff.php index 2bd8be194..9ff75d33b 100644 --- a/dev/tests/static/Magento/Sniffs/Commenting/FunctionCommentSniff.php +++ b/dev/tests/static/Magento/Sniffs/Commenting/FunctionCommentSniff.php @@ -1,10 +1,7 @@ <?php /** - * Parses and verifies the doc comments for functions. - * - * @author Greg Sherwood <gsherwood@squiz.net> - * @copyright 2006-2015 Squiz Pty Ltd (ABN 77 084 670 600) - * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + * Copyright 2018 Adobe + * All Rights Reserved. */ namespace Magento\Sniffs\Commenting; @@ -68,7 +65,7 @@ protected function processReturn(File $phpcsFile, $stackPtr, $commentStart) $phpcsFile->addError($error, $return, 'MissingReturnType'); } else { // Support both a return type and a description. - $split = preg_match('`^((?:\|?(?:array\([^\)]*\)|[\\\\a-z0-9\[\]]+))*)( .*)?`i', $content, $returnParts); + preg_match('`^((?:\|?(?:array\([^\)]*\)|[\\\\a-z0-9\[\]]+))*)( .*)?`i', $content, $returnParts); if (isset($returnParts[1]) === false) { return; } @@ -78,7 +75,7 @@ protected function processReturn(File $phpcsFile, $stackPtr, $commentStart) // Check return type (can be multiple, separated by '|'). $typeNames = explode('|', $returnType); $suggestedNames = array(); - foreach ($typeNames as $i => $typeName) { + foreach ($typeNames as $typeName) { $suggestedName = Common::suggestType($typeName); if (in_array($suggestedName, $suggestedNames) === false) { $suggestedNames[] = $suggestedName; @@ -214,7 +211,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'); @@ -422,7 +419,9 @@ protected function processParams(File $phpcsFile, $stackPtr, $commentStart) $typeHint, $param['var'], ); - $phpcsFile->addError($error, $stackPtr, 'IncorrectTypeHint', $data); + if ($suggestedTypeHint != "null" && (ltrim($typeHint, '?') !== $suggestedTypeHint)) { + $phpcsFile->addError($error, $stackPtr, 'IncorrectTypeHint', $data); + } }//end if } else if ($suggestedTypeHint === '' && isset($realParams[$pos]) === true) { $typeHint = $realParams[$pos]['type_hint']; @@ -437,7 +436,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/Commenting/VariableCommentSniff.php b/dev/tests/static/Magento/Sniffs/Commenting/VariableCommentSniff.php index 2a3cb92b2..63f206364 100644 --- a/dev/tests/static/Magento/Sniffs/Commenting/VariableCommentSniff.php +++ b/dev/tests/static/Magento/Sniffs/Commenting/VariableCommentSniff.php @@ -1,10 +1,7 @@ <?php /** - * Parses and verifies the variable doc comment. - * - * @author Greg Sherwood <gsherwood@squiz.net> - * @copyright 2006-2015 Squiz Pty Ltd (ABN 77 084 670 600) - * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + * Copyright 2018 Adobe + * All Rights Reserved. */ namespace Magento\Sniffs\Commenting; diff --git a/dev/tests/static/Magento/Sniffs/Files/LineLengthSniff.php b/dev/tests/static/Magento/Sniffs/Files/LineLengthSniff.php index 2abcf0531..529c87900 100644 --- a/dev/tests/static/Magento/Sniffs/Files/LineLengthSniff.php +++ b/dev/tests/static/Magento/Sniffs/Files/LineLengthSniff.php @@ -1,8 +1,9 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ + namespace Magento\Sniffs\Files; use PHP_CodeSniffer\Standards\Generic\Sniffs\Files\LineLengthSniff as FilesLineLengthSniff; diff --git a/dev/tests/static/Magento/Sniffs/LiteralNamespaces/LiteralNamespacesSniff.php b/dev/tests/static/Magento/Sniffs/LiteralNamespaces/LiteralNamespacesSniff.php index b6af3f37c..ee3dbb5d7 100644 --- a/dev/tests/static/Magento/Sniffs/LiteralNamespaces/LiteralNamespacesSniff.php +++ b/dev/tests/static/Magento/Sniffs/LiteralNamespaces/LiteralNamespacesSniff.php @@ -1,8 +1,9 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ + namespace Magento\Sniffs\LiteralNamespaces; use PHP_CodeSniffer\Sniffs\Sniff; diff --git a/dev/tests/static/Magento/Sniffs/MicroOptimizations/IsNullSniff.php b/dev/tests/static/Magento/Sniffs/MicroOptimizations/IsNullSniff.php index 928fc3a0d..8c32a72fe 100644 --- a/dev/tests/static/Magento/Sniffs/MicroOptimizations/IsNullSniff.php +++ b/dev/tests/static/Magento/Sniffs/MicroOptimizations/IsNullSniff.php @@ -1,8 +1,9 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ + namespace Magento\Sniffs\MicroOptimizations; use PHP_CodeSniffer\Sniffs\Sniff; @@ -13,7 +14,7 @@ class IsNullSniff implements Sniff /** * @var string */ - protected $blacklist = 'is_null'; + protected $blocklist = 'is_null'; /** * @inheritdoc @@ -29,7 +30,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..b22b19e3c 100644 --- a/dev/tests/static/Magento/Sniffs/NamingConventions/InterfaceNameSniff.php +++ b/dev/tests/static/Magento/Sniffs/NamingConventions/InterfaceNameSniff.php @@ -1,8 +1,9 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ + namespace Magento\Sniffs\NamingConventions; use PHP_CodeSniffer\Sniffs\Sniff; @@ -29,9 +30,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..e93179ae4 100644 --- a/dev/tests/static/Magento/Sniffs/NamingConventions/ReservedWordsSniff.php +++ b/dev/tests/static/Magento/Sniffs/NamingConventions/ReservedWordsSniff.php @@ -1,8 +1,9 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ + namespace Magento\Sniffs\NamingConventions; use PHP_CodeSniffer\Sniffs\Sniff; @@ -32,6 +33,7 @@ class ReservedWordsSniff implements Sniff 'object' => '7', 'mixed' => '7', 'numeric' => '7', + 'match' => '8', ]; /** diff --git a/dev/tests/static/Magento/Sniffs/Whitespace/EmptyLineMissedSniff.php b/dev/tests/static/Magento/Sniffs/Whitespace/EmptyLineMissedSniff.php index de3cfc50b..f3175b9f8 100644 --- a/dev/tests/static/Magento/Sniffs/Whitespace/EmptyLineMissedSniff.php +++ b/dev/tests/static/Magento/Sniffs/Whitespace/EmptyLineMissedSniff.php @@ -1,8 +1,9 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ + namespace Magento\Sniffs\Whitespace; use PHP_CodeSniffer\Sniffs\Sniff; diff --git a/dev/tests/static/Magento/Sniffs/Whitespace/MultipleEmptyLinesSniff.php b/dev/tests/static/Magento/Sniffs/Whitespace/MultipleEmptyLinesSniff.php index f276426ef..ccdfb2868 100644 --- a/dev/tests/static/Magento/Sniffs/Whitespace/MultipleEmptyLinesSniff.php +++ b/dev/tests/static/Magento/Sniffs/Whitespace/MultipleEmptyLinesSniff.php @@ -1,8 +1,9 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ + namespace Magento\Sniffs\Whitespace; use PHP_CodeSniffer\Sniffs\Sniff; diff --git a/dev/tests/static/Magento/ruleset.xml b/dev/tests/static/Magento/ruleset.xml index 98b4d4241..d17e010ed 100644 --- a/dev/tests/static/Magento/ruleset.xml +++ b/dev/tests/static/Magento/ruleset.xml @@ -1,8 +1,8 @@ <?xml version="1.0"?> <!-- /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ --> <ruleset name="Magento2FunctionalTestingFramework"> diff --git a/dev/tests/unit/Magento/FunctionalTestFramework/Allure/AllureHelperTest.php b/dev/tests/unit/Magento/FunctionalTestFramework/Allure/AllureHelperTest.php new file mode 100644 index 000000000..e4162e0f4 --- /dev/null +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Allure/AllureHelperTest.php @@ -0,0 +1,137 @@ +<?php +/** + * Copyright 2019 Adobe + * All Rights Reserved. + */ + +declare(strict_types=1); + +namespace tests\unit\Magento\FunctionalTestFramework\Allure; + +use Magento\FunctionalTestingFramework\Allure\AllureHelper; +use PHPUnit\Framework\TestCase; +use Qameta\Allure\Allure; +use Qameta\Allure\Io\DataSourceFactory; +use Qameta\Allure\Model\AttachmentResult; +use Qameta\Allure\Model\ResultFactoryInterface; +use Qameta\Allure\Setup\LifecycleBuilderInterface; +use const STDOUT; + +/** + * @covers \Qameta\Allure\Allure + */ +class AllureHelperTest extends TestCase +{ + public function setUp(): void + { + Allure::reset(); + } + + /** + * @dataProvider providerAttachmentProperties + */ + public function testDoAddAttachmentMethod( + string $name, + $type, + ?string $fileExtension, + ): void { + $attachment = new AttachmentResult('a'); + Allure::setLifecycleBuilder( + $this->createLifecycleBuilder($this->createResultFactoryWithAttachment($attachment)), + ); + + AllureHelper::doAddAttachment( + DataSourceFactory::fromFile('test'), + 'nameOfTheFile', + 'typeOfTheFile', + $fileExtension + ); + self::assertSame('nameOfTheFile', $attachment->getName()); + self::assertSame('typeOfTheFile', $attachment->getType()); + } + + /** + * @dataProvider providerAttachmentProperties + */ + public function testAddAttachmentToStep( + string $name, + ?string $type, + ?string $fileExtension, + ): void { + $attachment = new AttachmentResult('a'); + Allure::setLifecycleBuilder( + $this->createLifecycleBuilder($this->createResultFactoryWithAttachment($attachment)), + ); + + Allure::attachment($name, 'nameOfTheFile', $type, $fileExtension); + self::assertSame($name, $attachment->getName()); + self::assertSame($type, $attachment->getType()); + self::assertSame($fileExtension, $attachment->getFileExtension()); + } + + /** + * @dataProvider providerAttachmentProperties + */ + public function testAddAttachmentFileToStep( + string $name, + ?string $type, + ?string $fileExtension, + ): void { + $attachment = new AttachmentResult('a'); + Allure::setLifecycleBuilder( + $this->createLifecycleBuilder($this->createResultFactoryWithAttachment($attachment)), + ); + + Allure::attachmentFile($name, 'b', $type, '.html'); + self::assertSame('c', $attachment->getName()); + self::assertSame('.html', $attachment->getFileExtension()); + } + + /** + * @return iterable<string, array{string, string|null, string|null}> + */ + public static function providerAttachmentProperties(): iterable + { + return [ + 'Only name' => ['c', null, null], + 'Name and type' => ['c', 'd', null], + 'Name and file extension' => ['c', null, 'd'], + 'Name, type and file extension' => ['c', 'd', 'e'], + ]; + } + + private function createResultFactoryWithAttachment(AttachmentResult $attachment): ResultFactoryInterface + { + $resultFactory = $this->createStub(ResultFactoryInterface::class); + $resultFactory + ->method('createAttachment') + ->willReturn($attachment); + + return $resultFactory; + } + + private function createLifecycleBuilder( + ?ResultFactoryInterface $resultFactory = null, + ?AllureLifecycleInterface $lifecycle = null, + ?StatusDetectorInterface $statusDetector = null, + ): LifecycleBuilderInterface { + $builder = $this->createStub(LifecycleBuilderInterface::class); + if (isset($resultFactory)) { + $builder + ->method('getResultFactory') + ->willReturn($resultFactory); + } + if (isset($lifecycle)) { + $builder + ->method('createLifecycle') + ->willReturn($lifecycle); + } + if (isset($statusDetector)) { + $builder + ->method('getStatusDetector') + ->willReturn($statusDetector); + } + + return $builder; + } +} diff --git a/dev/tests/unit/Magento/FunctionalTestFramework/Composer/ComposerInstallTest.php b/dev/tests/unit/Magento/FunctionalTestFramework/Composer/ComposerInstallTest.php new file mode 100644 index 000000000..45cb66e90 --- /dev/null +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Composer/ComposerInstallTest.php @@ -0,0 +1,62 @@ +<?php +/** + * Copyright 2019 Adobe + * All Rights Reserved. + */ + +namespace tests\unit\Magento\FunctionalTestFramework\Composer; + +use Magento\FunctionalTestingFramework\Composer\ComposerInstall; +use tests\unit\Util\MagentoTestCase; + +class ComposerInstallTest extends MagentoTestCase +{ + /** + * ComposerInstall instance to be tested + * + * @var ComposerInstall + */ + private $composer; + + public function setUp(): void + { + $composerJson = dirname(__DIR__) . DIRECTORY_SEPARATOR . 'Composer' . DIRECTORY_SEPARATOR . '_files' + . DIRECTORY_SEPARATOR . 'dir1' . DIRECTORY_SEPARATOR . 'dir2' . DIRECTORY_SEPARATOR . 'composer.json'; + + $this->composer = new ComposerInstall($composerJson); + } + + /** + * Test isMftfTestPackage() + */ + public function testIsMftfTestPackage() + { + $this->assertTrue($this->composer->isMftfTestPackage('magento/module2-functional-test')); + } + + /** + * Test isMagentoPackage() + */ + public function testIsMagentoPackage() + { + $this->assertTrue($this->composer->isMagentoPackage('magento/module-authorization')); + } + + /** + * Test isInstalledPackageOfType() + */ + public function testIsInstalledPackageOfType() + { + $this->assertTrue($this->composer->isInstalledPackageOfType('composer/composer', 'library')); + } + + /** + * Test getInstalledTestPackages() + */ + public function testGetInstalledTestPackages() + { + $output = $this->composer->getInstalledTestPackages(); + $this->assertCount(1, $output); + $this->assertArrayHasKey('magento/module2-functional-test', $output); + } +} diff --git a/dev/tests/unit/Magento/FunctionalTestFramework/Composer/ComposerPackageTest.php b/dev/tests/unit/Magento/FunctionalTestFramework/Composer/ComposerPackageTest.php new file mode 100644 index 000000000..94c9b14ad --- /dev/null +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Composer/ComposerPackageTest.php @@ -0,0 +1,170 @@ +<?php +/** + * Copyright 2019 Adobe + * All Rights Reserved. + */ + +namespace tests\unit\Magento\FunctionalTestFramework\Composer; + +use Magento\FunctionalTestingFramework\Composer\ComposerPackage; +use tests\unit\Util\MagentoTestCase; +use Composer\Package\RootPackage; + +class ComposerPackageTest extends MagentoTestCase +{ + /** + * ComposerPackage instance to be tested + * + * @var ComposerPackage + */ + private $composer; + + public function setUp(): void + { + $composerJson = dirname(__DIR__) . DIRECTORY_SEPARATOR . 'Composer' . DIRECTORY_SEPARATOR . '_files' + . DIRECTORY_SEPARATOR . 'dir1' . DIRECTORY_SEPARATOR . 'dir2' . DIRECTORY_SEPARATOR . 'composer.json'; + + $this->composer = new ComposerPackage($composerJson); + } + + /** + * Test getName() + */ + public function testGetName() + { + $expected = 'magento/module2-functional-test'; + $this->assertEquals($expected, $this->composer->getName()); + } + + /** + * Test getType() + */ + public function testGetType() + { + $expected = 'magento2-functional-test-module'; + $this->assertEquals($expected, $this->composer->getType()); + } + + /** + * Test getVersion() + */ + public function testGetVersion() + { + $expected = '1.0.0'; + $this->assertEquals($expected, $this->composer->getVersion()); + } + + /** + * Test getDescription() + */ + public function testGetDescription() + { + $expected = 'MFTF tests for magento'; + $this->assertEquals($expected, $this->composer->getDescription()); + } + + /** + * Test getRequires() + */ + public function testGetRequires() + { + $expected = 'magento/magento2-functional-testing-framework'; + $output = $this->composer->getRequires(); + $this->assertCount(1, $output); + $this->assertArrayHasKey($expected, $output); + } + + /** + * Test getDevRequires() + */ + public function testGetDevRequires() + { + $expected = ['phpunit/phpunit']; + $this->assertEquals($expected, array_keys($this->composer->getDevRequires())); + } + + /** + * Test getSuggests() + */ + public function testGetSuggests() + { + $expected = [ + 'magento/module-one', + 'magento/module-module-x', + 'magento/module-two', + 'magento/module-module-y', + 'magento/module-module-z', + 'magento/module-three', + 'magento/module-four' + ]; + $this->assertEquals($expected, array_keys($this->composer->getSuggests())); + } + + /** + * Test getSuggestedMagentoModules() + */ + public function testGetSuggestedMagentoModules() + { + $expected = [ + 'Magento_ModuleX', + 'Magento_ModuleY', + 'Magento_ModuleZ' + ]; + $this->assertEquals($expected, $this->composer->getSuggestedMagentoModules()); + } + + /** + * Test isMftfTestPackage() + */ + public function testIsMftfTestPackage() + { + $this->assertTrue($this->composer->isMftfTestPackage()); + } + + /** + * Test getRequiresForPackage() + */ + public function testGetRequiresForPackage() + { + $expected = [ + 'php', + 'ext-curl', + 'allure-framework/allure-codeception', + 'codeception/codeception', + 'consolidation/robo', + 'csharpru/vault-php', + 'csharpru/vault-php-guzzle6-transport', + 'flow/jsonpath', + 'fzaninotto/faker', + 'monolog/monolog', + 'mustache/mustache', + 'symfony/process', + 'vlucas/phpdotenv' + ]; + $this->assertEquals( + $expected, + array_keys($this->composer->getRequiresForPackage('magento/magento2-functional-testing-framework', '2.5.0')) + ); + } + + /** + * Test isPackageRequiredInComposerJson() + */ + public function testIsPackageRequiredInComposerJson() + { + $this->assertTrue( + $this->composer->isPackageRequiredInComposerJson('magento/magento2-functional-testing-framework') + ); + } + + /** + * Test getRootPackage() + */ + public function testGetRootPackage() + { + $this->assertInstanceOf( + RootPackage::class, + $this->composer->getRootPackage() + ); + } +} diff --git a/dev/tests/unit/Magento/FunctionalTestFramework/Composer/_files/dir1/dir2/composer.json b/dev/tests/unit/Magento/FunctionalTestFramework/Composer/_files/dir1/dir2/composer.json new file mode 100644 index 000000000..c21401af7 --- /dev/null +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Composer/_files/dir1/dir2/composer.json @@ -0,0 +1,21 @@ +{ + "name": "magento/module2-functional-test", + "type": "magento2-functional-test-module", + "description": "MFTF tests for magento", + "version": "1.0.0", + "require": { + "magento/magento2-functional-testing-framework": "^2.5.0" + }, + "require-dev": { + "phpunit/phpunit": "~6.5.0 || ~7.0.0" + }, + "suggest": { + "magento/module-one": "name: Magento_ModuleY, version: ~100.0.0", + "magento/module-module-x": " type: magento2-module ,name: Magento_ModuleX, version: ~100.0.0", + "magento/module-two": "*", + "magento/module-module-y": "type: magento2-module, name:Magento_ModuleY,version:~100.0.0", + "magento/module-module-z": " type:magento2-module, name: Magento_ModuleZ , version: ~100.0.0", + "magento/module-three": "type: magento2-module, ~100.0.0", + "magento/module-four": "some suggestions" + } +} \ No newline at end of file diff --git a/dev/tests/unit/Magento/FunctionalTestFramework/Composer/_files/dir1/dir2/composer.lock b/dev/tests/unit/Magento/FunctionalTestFramework/Composer/_files/dir1/dir2/composer.lock new file mode 100644 index 000000000..4493b5615 --- /dev/null +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Composer/_files/dir1/dir2/composer.lock @@ -0,0 +1,149 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "f40a2fa7e19322bd961f212ad270b0d7", + "packages": [ + { + "name": "magento/module-authorization", + "version": "100.3.2", + "dist": { + "type": "zip", + "url": "https://repo.magento.com/archives/magento/module-authorization/magento-module-authorization-100.3.2.0.zip", + "shasum": "c17ed45c3bffca3bf704d2f4031069ed36b70273" + }, + "require": { + "magento/framework": "102.0.*", + "magento/module-backend": "101.0.*", + "php": "~7.1.3||~7.2.0" + }, + "type": "magento2-module", + "autoload": { + "files": [ + "registration.php" + ], + "psr-4": { + "Magento\\Authorization\\": "" + } + }, + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "description": "Authorization module provides access to Magento ACL functionality." + }, + { + "name": "composer/composer", + "version": "1.9.0", + "source": { + "type": "git", + "url": "https://github.com/composer/composer.git", + "reference": "314aa57fdcfc942065996f59fb73a8b3f74f3fa5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/composer/zipball/314aa57fdcfc942065996f59fb73a8b3f74f3fa5", + "reference": "314aa57fdcfc942065996f59fb73a8b3f74f3fa5", + "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" + }, + "bin": [ + "bin/composer" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.9-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\": "src/Composer" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "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" + } + ], + "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" + ], + "time": "2019-08-02T18:55:33+00:00" + }, + { + "name": "magento/module2-functional-test", + "require": { + "composer/composer": "*" + }, + "type": "magento2-functional-test-module", + "version": "1.0.0", + "suggest": { + "magento/module-one": "name: Magento_ModuleY, version: ~100.0.0", + "magento/module-module-x": " type: magento2-module ,name: Magento_ModuleX, version: ~100.0.0", + "magento/module-two": "*", + "magento/module-module-y": "type: magento2-module, name:Magento_ModuleY,version:~100.0.0", + "magento/module-module-z": " type:magento2-module, name: Magento_ModuleZ , version: ~100.0.0", + "magento/module-three": "type: magento2-module, ~100.0.0", + "magento/module-four": "some suggestions" + }, + "description": "magento module2-functional-test", + "keywords": [ + "mftf", + "test" + ], + "time": "2019-03-19T17:25:45+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [] +} diff --git a/dev/tests/unit/Magento/FunctionalTestFramework/Composer/_files/dir1/dir2/dir31/composer.json b/dev/tests/unit/Magento/FunctionalTestFramework/Composer/_files/dir1/dir2/dir31/composer.json new file mode 100644 index 000000000..49e952ee9 --- /dev/null +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Composer/_files/dir1/dir2/dir31/composer.json @@ -0,0 +1,10 @@ +{ + "name": "magento/module-31-functional-test", + "type": "magento2-functional-test-module", + "require": { + "magento/magento2-functional-testing-framework": "*" + }, + "suggest": { + "magento/module-module-a": "type: magento2-module, name: Magento_ModuleA, version: ~100.0.0" + } +} \ No newline at end of file diff --git a/dev/tests/unit/Magento/FunctionalTestFramework/Composer/_files/dir1/dir2/dir31/dir41/composer.json b/dev/tests/unit/Magento/FunctionalTestFramework/Composer/_files/dir1/dir2/dir31/dir41/composer.json new file mode 100644 index 000000000..97737fca6 --- /dev/null +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Composer/_files/dir1/dir2/dir31/dir41/composer.json @@ -0,0 +1,11 @@ +{ + "name": "magento/module-3141-functional-test", + "type": "magento2-functional-test-module", + "require": { + "magento/magento2-functional-testing-framework": "*" + }, + "suggest": { + "magento/module-module-e": "type: magento2-module, name: Magento_ModuleE, version: ~100.0.0", + "magento/module-module-f": "type: magento2-module, name: Magento_ModuleF, version: ~100.0.0" + } +} \ No newline at end of file diff --git a/dev/tests/unit/Magento/FunctionalTestFramework/Composer/_files/dir1/dir2/dir32/composer.json b/dev/tests/unit/Magento/FunctionalTestFramework/Composer/_files/dir1/dir2/dir32/composer.json new file mode 100644 index 000000000..dffbb3bee --- /dev/null +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Composer/_files/dir1/dir2/dir32/composer.json @@ -0,0 +1,11 @@ +{ + "name": "magento/module-32-functional-test", + "type": "magento2-functional-test-module", + "require": { + "magento/magento2-functional-testing-framework": "*" + }, + "suggest": { + "magento/module-module-b": "type: magento2-module, name: Magento_ModuleB, version: ~100.0.0", + "magento/module-module-c": "type: magento2-module, name: Magento_ModuleC, version: ~100.0.0" + } +} \ No newline at end of file diff --git a/dev/tests/unit/Magento/FunctionalTestFramework/Composer/_files/dir1/dir2/dir32/dir41/composer.json b/dev/tests/unit/Magento/FunctionalTestFramework/Composer/_files/dir1/dir2/dir32/dir41/composer.json new file mode 100644 index 000000000..f766c226c --- /dev/null +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Composer/_files/dir1/dir2/dir32/dir41/composer.json @@ -0,0 +1,10 @@ +{ + "name": "magento/module-3241-functional-test", + "type": "magento2-functional-test-module", + "require": { + "magento/magento2-functional-testing-framework": "*" + }, + "suggest": { + "magento/module-module-g": "type: magento2-module, name: Magento_ModuleG, version: ~100.0.0" + } +} \ No newline at end of file diff --git a/dev/tests/unit/Magento/FunctionalTestFramework/Composer/_files/dir1/dir2/dir32/dir42/composer.json b/dev/tests/unit/Magento/FunctionalTestFramework/Composer/_files/dir1/dir2/dir32/dir42/composer.json new file mode 100644 index 000000000..e51f36515 --- /dev/null +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Composer/_files/dir1/dir2/dir32/dir42/composer.json @@ -0,0 +1,10 @@ +{ + "name": "magento/module-3242-functional-test", + "type": "magento2-functional-test-module", + "require": { + "magento/magento2-functional-testing-framework": "*" + }, + "suggest": { + "magento/module-module-h": "type: magento2-module, name: Magento_ModuleH, version: ~100.0.0" + } +} \ No newline at end of file diff --git a/dev/tests/unit/Magento/FunctionalTestFramework/Config/Reader/FilesystemTest.php b/dev/tests/unit/Magento/FunctionalTestFramework/Config/Reader/FilesystemTest.php index b59858e7f..debf88285 100644 --- a/dev/tests/unit/Magento/FunctionalTestFramework/Config/Reader/FilesystemTest.php +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Config/Reader/FilesystemTest.php @@ -1,106 +1,111 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2018 Adobe + * All Rights Reserved. */ -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..9de72fffb --- /dev/null +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Console/BaseGenerateCommandTest.php @@ -0,0 +1,281 @@ +<?php +/** + * Copyright 2019 Adobe + * All Rights Reserved. + */ + +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(null, $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(null, $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(null, $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(null, $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..8967296da --- /dev/null +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Console/GenerateTestFailedCommandTest.php @@ -0,0 +1,167 @@ +<?php +/** + * Copyright 2021 Adobe + * All Rights Reserved. + */ + +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..f2e79de12 --- /dev/null +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Console/GenerateTestsCommandTest.php @@ -0,0 +1,82 @@ +<?php +/** + * Copyright 2021 Adobe + * All Rights Reserved. + */ + +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 static 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..03abb1226 --- /dev/null +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Console/RunTestFailedCommandTest.php @@ -0,0 +1,132 @@ +<?php +/** + * Copyright 2021 Adobe + * All Rights Reserved. + */ + +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/CredentialStoreTest.php b/dev/tests/unit/Magento/FunctionalTestFramework/DataGenerator/Handlers/CredentialStoreTest.php deleted file mode 100644 index a451f8dc9..000000000 --- a/dev/tests/unit/Magento/FunctionalTestFramework/DataGenerator/Handlers/CredentialStoreTest.php +++ /dev/null @@ -1,38 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ - -namespace tests\unit\Magento\FunctionalTestFramework\DataGenerator\Handlers; - -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\CredentialStore; -use Magento\FunctionalTestingFramework\Util\MagentoTestCase; -use AspectMock\Test as AspectMock; - -class CredentialStoreTest extends MagentoTestCase -{ - - /** - * Test basic encryption/decryption functionality in CredentialStore class. - */ - public function testBasicEncryptDecrypt() - { - $testKey = 'myKey'; - $testValue = 'myValue'; - - AspectMock::double(CredentialStore::class, [ - 'readInCredentialsFile' => ["$testKey=$testValue"] - ]); - - $encryptedCred = CredentialStore::getInstance()->getSecret($testKey); - - // assert the value we've gotten is in fact not identical to our test value - $this->assertNotEquals($testValue, $encryptedCred); - - $actualValue = CredentialStore::getInstance()->decryptSecretValue($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/DataObjectHandlerTest.php b/dev/tests/unit/Magento/FunctionalTestFramework/DataGenerator/Handlers/DataObjectHandlerTest.php index b77659aa2..663021bde 100644 --- a/dev/tests/unit/Magento/FunctionalTestFramework/DataGenerator/Handlers/DataObjectHandlerTest.php +++ b/dev/tests/unit/Magento/FunctionalTestFramework/DataGenerator/Handlers/DataObjectHandlerTest.php @@ -1,24 +1,37 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ +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' => [ @@ -30,67 +43,337 @@ class DataObjectHandlerTest extends MagentoTestCase 'value' => 'testValue' ] ] - ] + ], + 'EntityTwo' => [ + 'type' => 'testType', + 'extends' => 'EntityOne', + 'data' => [ + 0 => [ + 'key' => 'testKeyTwo', + 'value' => 'testValueTwo' + ] + ] + ], ] ]; - /** - * 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 - */ - public static function setUpBeforeClass() - { - $mockDataProfileSchemaParser = AspectMock::double(DataProfileSchemaParser::class, [ - 'readDataProfiles' => self::PARSER_OUTPUT - ])->make(); + const PARSER_OUTPUT_DEPRECATED = [ + 'entity' => [ + 'EntityOne' => [ + 'type' => 'testType', + 'data' => [ + 0 => [ + 'key' => 'testKey', + 'value' => 'testValue' + ] + ], + 'deprecated' => "deprecation message", + 'filename' => "filename.xml" + ], + ] + ]; - $mockObjectManager = AspectMock::double(ObjectManager::class, [ - 'create' => $mockDataProfileSchemaParser - ])->make(); + const PARSER_OUTPUT_WITH_EXTEND = [ + 'entity' => [ + 'EntityOne' => [ + 'name' => 'EntityOne', + 'type' => 'testType', + 'data' => [ + 0 => [ + 'key' => 'testKey', + 'value' => 'testValue' + ] + ] + ], + 'EntityTwo' => [ + 'name' => 'EntityTwo', + 'type' => 'testType', + 'extends' => 'EntityOne', + 'data' => [ + 0 => [ + 'key' => 'testKeyTwo', + 'value' => 'testValueTwo' + ] + ], + ], + 'EntityThree' => [ + 'name' => 'EntityThree', + 'type' => 'testType', + 'extends' => 'EntityOne', + 'data' => [ + 0 => [ + 'key' => 'testKeyThree', + 'value' => 'testValueThree' + ] + ], + ] + ] + ]; - AspectMock::double(ObjectManagerFactory::class, [ - 'getObjectManager' => $mockObjectManager - ]); - } + const PARSER_OUTPUT_WITH_EXTEND_INVALID = [ + 'entity' => [ + 'EntityOne' => [ + 'name' => 'EntityOne', + 'type' => 'testType', + 'extends' => 'EntityOne', + 'data' => [ + 0 => [ + 'key' => 'testKey', + 'value' => 'testValue' + ] + ] + ], + 'EntityTwo' => [ + 'name' => 'EntityTwo', + 'type' => 'testType', + 'data' => [ + 0 => [ + 'key' => 'testKeyTwo', + 'value' => 'testValueTwo' + ] + ], + ], + 'EntityThree' => [ + 'name' => 'EntityThree', + 'type' => 'testType', + 'extends' => 'EntityThree', + 'data' => [ + 0 => [ + 'key' => 'testKeyThree', + 'value' => 'testValueThree' + ] + ], + ] + ] + ]; /** - * 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 { - // Call the method under test + $this->mockDataObjectHandlerWithData(self::PARSER_OUTPUT); + // Call the method under test $actual = DataObjectHandler::getInstance()->getAllObjects(); // Assert - $expected = new EntityDataObject('EntityOne', 'testType', ['testkey' => 'testValue'], [], null, []); $this->assertArrayHasKey('EntityOne', $actual); $this->assertEquals($expected, $actual['EntityOne']); } /** - * getObject should return the expected data object if it exists + * Validate test deprecated data object. + * + * @return void + * @throws Exception */ - public function testGetObject() + 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(): void + { + $this->mockDataObjectHandlerWithData(self::PARSER_OUTPUT); + + // Call the method under test $actual = DataObjectHandler::getInstance()->getObject('EntityOne'); // Assert - $expected = new EntityDataObject('EntityOne', 'testType', ['testkey' => 'testValue'], [], null, []); $this->assertEquals($expected, $actual); } /** - * getObject should return null if the data object does not exist + * Validate getAllObjects should return the expected data object if it exists. + * + * @return void + * @throws Exception */ - public function testGetObjectNull() + public function testGetObjectNull(): void { + $this->mockDataObjectHandlerWithData(self::PARSER_OUTPUT); + $actual = DataObjectHandler::getInstance()->getObject('h953u789h0g73t521'); // doesnt exist $this->assertNull($actual); } + + /** + * Validate getAllObjects should contain the expected data object with extends. + * + * @return void + * @throws Exception + */ + public function testGetAllObjectsWithDataExtends(): void + { + $this->mockDataObjectHandlerWithData(self::PARSER_OUTPUT_WITH_EXTEND); + + // Call the method under test + $actual = DataObjectHandler::getInstance()->getAllObjects(); + + // Assert + $expected = new EntityDataObject( + 'EntityTwo', + 'testType', + ['testkey' => 'testValue', 'testkeytwo' => 'testValueTwo'], + [], + null, + [], + 'EntityOne' + ); + $this->assertArrayHasKey('EntityTwo', $actual); + $this->assertEquals($expected, $actual['EntityTwo']); + } + + /** + * Validate getObject should return the expected data object with extended data if it exists. + * + * @return void + * @throws Exception + */ + public function testGetObjectWithDataExtends(): void + { + $this->mockDataObjectHandlerWithData(self::PARSER_OUTPUT_WITH_EXTEND); + + // Call the method under test + $actual = DataObjectHandler::getInstance()->getObject('EntityTwo'); + + // Assert + $expected = new EntityDataObject( + 'EntityTwo', + 'testType', + ['testkey' => 'testValue', 'testkeytwo' => 'testValueTwo'], + [], + null, + [], + 'EntityOne' + ); + $this->assertEquals($expected, $actual); + } + + /** + * Validate getAllObjects should throw TestFrameworkException exception if some data extends itself. + * + * @return void + * @throws Exception + */ + public function testGetAllObjectsWithDataExtendsItself(): void + { + $this->mockDataObjectHandlerWithData(self::PARSER_OUTPUT_WITH_EXTEND_INVALID); + + $this->expectException(TestFrameworkException::class); + $this->expectExceptionMessage( + 'Mftf Data can not extend from itself: ' + . self::PARSER_OUTPUT_WITH_EXTEND_INVALID['entity']['EntityOne']['name'] + ); + + // Call the method under test + DataObjectHandler::getInstance()->getAllObjects(); + } + + /** + * Validate getObject should throw TestFrameworkException exception if requested data extends itself. + * + * @return void + * @throws Exception + */ + public function testGetObjectWithDataExtendsItself(): void + { + $this->mockDataObjectHandlerWithData(self::PARSER_OUTPUT_WITH_EXTEND_INVALID); + + $this->expectException(TestFrameworkException::class); + $this->expectExceptionMessage( + 'Mftf Data can not extend from itself: ' + . self::PARSER_OUTPUT_WITH_EXTEND_INVALID['entity']['EntityOne']['name'] + ); + + // Call the method under test + DataObjectHandler::getInstance()->getObject( + self::PARSER_OUTPUT_WITH_EXTEND_INVALID['entity']['EntityOne']['name'] + ); + } + + /** + * Create mock data object handler with data. + * + * @param array $mockData + * + * @return void + */ + private function mockDataObjectHandlerWithData(array $mockData): void + { + $dataObjectHandlerProperty = new ReflectionProperty(DataObjectHandler::class, "INSTANCE"); + $dataObjectHandlerProperty->setAccessible(true); + $dataObjectHandlerProperty->setValue(null, 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, $mockObjectManagerInstance); + } + + /** + * @inheritDoc + */ + public static function tearDownAfterClass(): void + { + parent::tearDownAfterClass(); + + $dataObjectHandlerProperty = new ReflectionProperty(DataObjectHandler::class, "INSTANCE"); + $dataObjectHandlerProperty->setAccessible(true); + $dataObjectHandlerProperty->setValue(null, null); + + $objectManagerProperty = new ReflectionProperty(ObjectManager::class, 'instance'); + $objectManagerProperty->setAccessible(true); + $objectManagerProperty->setValue(null, null); + + 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..de97679e8 100644 --- a/dev/tests/unit/Magento/FunctionalTestFramework/DataGenerator/Handlers/OperationDefinitionObjectHandlerTest.php +++ b/dev/tests/unit/Magento/FunctionalTestFramework/DataGenerator/Handlers/OperationDefinitionObjectHandlerTest.php @@ -1,31 +1,49 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ +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 +55,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 +94,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 +198,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 +270,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 +316,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 +341,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 +389,9 @@ public function testObjectArrayCreation() OperationDefinitionObjectHandler::ENTITY_OPERATION_OBJECT, false, [], - [0 => $twoLevelNestedMetadata] + [ + 0 => $twoLevelNestedMetadata + ] ); $expectedOperation = new OperationElement( @@ -273,12 +399,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 +416,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 +439,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 +482,7 @@ public function testLooseJsonCreation() ); // Set up mocked data output - $this->setMockParserOutput($mockData); + $this->mockOperationHandlerWithData($mockData); // get Operations $operationDefinitionManager = OperationDefinitionObjectHandler::getInstance(); @@ -358,25 +494,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, 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(null, $mockObjectManagerInstance); + } + + /** + * @inheritDoc + */ + public static function tearDownAfterClass(): void + { + parent::tearDownAfterClass(); + + $operationDefinitionObjectHandlerProperty = new ReflectionProperty( + OperationDefinitionObjectHandler::class, + 'INSTANCE' + ); + $operationDefinitionObjectHandlerProperty->setAccessible(true); + $operationDefinitionObjectHandlerProperty->setValue(null, null); + + $objectManagerProperty = new ReflectionProperty(ObjectManager::class, 'instance'); + $objectManagerProperty->setAccessible(true); + $objectManagerProperty->setValue(null, 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 30dc3ec40..f38cf3805 100644 --- a/dev/tests/unit/Magento/FunctionalTestFramework/DataGenerator/Handlers/PersistedObjectHandlerTest.php +++ b/dev/tests/unit/Magento/FunctionalTestFramework/DataGenerator/Handlers/PersistedObjectHandlerTest.php @@ -1,32 +1,80 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2018 Adobe + * All Rights Reserved. */ +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; use Magento\FunctionalTestingFramework\DataGenerator\Persist\CurlHandler; +use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; +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; /** * Class PersistedObjectHandlerTest */ class PersistedObjectHandlerTest extends MagentoTestCase { - public function testCreateSimpleEntity() + /** + * @inheritDoc + */ + protected function setUp(): void + { + TestLoggingUtil::getInstance()->setMockLoggingUtil(); + } + + /** + * Validate testCreateEntityWithNonExistingName. + * + * @return void + * @throws TestReferenceException + */ + public function testCreateEntityWithNonExistingName(): void + { + // Test Data and Variables + $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"; + $entityName = 'EntityOne'; + $entityStepKey = 'StepKey'; + $dataKey = 'testKey'; + $dataValue = 'testValue'; $scope = PersistedObjectHandler::TEST_SCOPE; $parserOutput = [ 'entity' => [ @@ -41,15 +89,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 @@ -63,13 +109,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' => [ @@ -84,15 +136,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 @@ -111,13 +162,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' => [ @@ -132,15 +189,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 @@ -154,16 +210,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' => [ @@ -187,7 +249,7 @@ public function testUpdateSimpleEntity() ] ] ]; - $jsonResponse = " + $jsonResponse = " { \"" . strtolower($dataKey) . "\" : \"{$dataValue}\" } @@ -199,15 +261,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( @@ -220,21 +281,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' => [ @@ -267,7 +334,7 @@ public function testRetrieveEntityAcrossScopes() ] ] ]; - $jsonReponseOne = " + $jsonResponseOne = " { \"" . strtolower($dataKeyOne) . "\" : \"{$dataValueOne}\" } @@ -286,22 +353,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, @@ -331,47 +397,205 @@ public function testRetrieveEntityAcrossScopes() } /** - * Mocks DataObjectHandler to use given output to create - * @param $parserOutput - * @throws \Exception + * Validate testRetrieveEntityValidField. + * + * @param string $name + * @param string $key + * @param string $value + * @param string $type + * @param string $scope + * @param string $stepKey + * @dataProvider entityDataProvider + * + * @return void + * @throws TestReferenceException */ - public function mockDataHandlerWithOutput($parserOutput) + public function testRetrieveEntityValidField( + string $name, + string $key, + string $value, + string $type, + string $scope, + string $stepKey + ): void { + $parserOutputOne = [ + 'entity' => [ + $name => [ + 'type' => $type, + 'data' => [ + 0 => [ + 'key' => $key, + 'value' => $value + ] + ] + ] + ] + ]; + $jsonResponseOne = " + { + \"" . strtolower($key) . "\" : \"{$value}\" + } + "; + + // Mock Classes and Create Entities + $handler = PersistedObjectHandler::getInstance(); + $this->mockCurlHandler($jsonResponseOne, $parserOutputOne); + $handler->createEntity($stepKey, $scope, $name); + + // Call method + $retrieved = $handler->retrieveEntityField($stepKey, $key, $scope); + + $this->assertEquals($value, $retrieved); + } + + /** + * Validate testRetrieveEntityInValidField. + * + * @param string $name + * @param string $key + * @param string $value + * @param string $type + * @param string $scope + * @param string $stepKey + * @dataProvider entityDataProvider + * + * @return void + * @throws TestReferenceException|TestFrameworkException + */ + 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.'; + + $parserOutputOne = [ + 'entity' => [ + $name => [ + 'type' => $type, + 'data' => [ + 0 => [ + 'key' => $key, + 'value' => $value + ] + ] + ] + ] + ]; + $jsonResponseOne = " + { + \"" . strtolower($key) . "\" : \"{$value}\" + } + "; + + // Mock Classes and Create Entities + $handler = PersistedObjectHandler::getInstance(); + $this->mockCurlHandler($jsonResponseOne, $parserOutputOne); + $handler->createEntity($stepKey, $scope, $name); + + // Call method + $handler->retrieveEntityField($stepKey, $invalidDataKey, $scope); + + // validate log statement + TestLoggingUtil::getInstance()->validateMockLogStatement( + 'warning', + $warnMsg, + [] + ); + } + + /** + * Data provider for testRetrieveEntityField. + * + * @return array + */ + public static function entityDataProvider(): array { - // 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 - ]); + return [ + ['Entity1', 'testKey1', 'testValue1', 'testType', PersistedObjectHandler::HOOK_SCOPE, 'StepKey1'], + ['Entity2', 'testKey2', 'testValue2', 'testType', PersistedObjectHandler::SUITE_SCOPE, 'StepKey2'], + ['Entity3', 'testKey3', 'testValue3', 'testType', PersistedObjectHandler::TEST_SCOPE, 'StepKey3'] + ]; } - public function mockCurlHandler($response) + /** + * Create mock curl handler. + * + * @param string $response + * @param array $parserOutput + * + * @return void + */ + public function mockCurlHandler(string $response, array $parserOutput): void { - AspectMock::double(CurlHandler::class, [ - "__construct" => null, - "executeRequest" => $response, - "getRequestDataArray" => [], - "isContentTypeJson" => true - ]); + $dataObjectHandler = new ReflectionProperty(DataObjectHandler::class, 'INSTANCE'); + $dataObjectHandler->setAccessible(true); + $dataObjectHandler->setValue(null, 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(null, $objectManagerMockInstance); } - public function tearDown() + /** + * After class functionality. + * + * @return void + */ + public static function tearDownAfterClass(): void { + parent::tearDownAfterClass(); + // 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, null); + + $objectManagerProperty = new ReflectionProperty(ObjectManager::class, 'instance'); + $objectManagerProperty->setAccessible(true); + $objectManagerProperty->setValue(null, null); - parent::tearDown(); // TODO: Change the autogenerated stub + TestLoggingUtil::getInstance()->clearMockLoggingUtil(); } } 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..0be9e0398 --- /dev/null +++ b/dev/tests/unit/Magento/FunctionalTestFramework/DataGenerator/Handlers/SecretStorage/AwsSecretsManagerStorageTest.php @@ -0,0 +1,65 @@ +<?php +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ + +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() + ->onlyMethods(['__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 new file mode 100644 index 000000000..3a44b3ab6 --- /dev/null +++ b/dev/tests/unit/Magento/FunctionalTestFramework/DataGenerator/Handlers/SecretStorage/FileStorageTest.php @@ -0,0 +1,80 @@ +<?php +/** + * Copyright 2019 Adobe + * All Rights Reserved. + */ + +declare(strict_types=1); + +namespace tests\unit\Magento\FunctionalTestFramework\DataGenerator\Handlers\SecretStorage; + +use Magento\FunctionalTestingFramework\DataGenerator\Handlers\SecretStorage\FileStorage; +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(): void + { + $testKey = 'magento/myKey'; + $testValue = 'myValue'; + $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 + $this->assertNotEquals($testValue, $encryptedCred); + + $actualValue = $fileStorage->getDecryptedValue($encryptedCred); + + // 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 5b63f0c94..aaf41fa2f 100644 --- a/dev/tests/unit/Magento/FunctionalTestFramework/DataGenerator/Objects/EntityDataObjectTest.php +++ b/dev/tests/unit/Magento/FunctionalTestFramework/DataGenerator/Objects/EntityDataObjectTest.php @@ -1,12 +1,12 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ 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; @@ -20,12 +20,12 @@ function function_exists($val) return true; } -function msq($id = null) +function msq(?string $id = null) { return "msqUnique"; } -function msqs($id = null) +function msqs(?string $id = null) { return "msqsUnique"; } @@ -40,7 +40,7 @@ class EntityDataObjectTest extends MagentoTestCase * Before test functionality * @return void */ - public function setUp() + public function setUp(): void { TestLoggingUtil::getInstance()->setMockLoggingUtil(); } @@ -120,11 +120,28 @@ public function testGetLinkedEntities() $this->assertEquals("linkedEntity2", $dataObject->getLinkedEntitiesOfType("otherEntityType")[0]); } + public function testGetCamelCaseKeys() + { + $data = [ + "lowercasekey1" => "value1", + "camelCaseKey2" => "value2", + "lowercasekey3" => "value3", + "camelCaseKey4" => "value4" + ]; + + $dataObject = new EntityDataObject("name", "type", $data, null, null, null); + + $this->assertEquals("value1", $dataObject->getDataByName("lowercasekey1", 0)); + $this->assertEquals("value2", $dataObject->getDataByName("camelCaseKey2", 0)); + $this->assertEquals("value3", $dataObject->getDataByName("lowercasekey3", 0)); + $this->assertEquals("value4", $dataObject->getDataByName("camelCaseKey4", 0)); + } + /** * 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 9e425e020..77daf6e4c 100644 --- a/dev/tests/unit/Magento/FunctionalTestFramework/DataGenerator/Persist/OperationDataArrayResolverTest.php +++ b/dev/tests/unit/Magento/FunctionalTestFramework/DataGenerator/Persist/OperationDataArrayResolverTest.php @@ -1,15 +1,19 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ + +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\MagentoTestCase; +use ReflectionProperty; +use tests\unit\Util\MagentoTestCase; use tests\unit\Util\EntityDataObjectBuilder; use tests\unit\Util\OperationDefinitionBuilder; use tests\unit\Util\OperationElementBuilder; @@ -17,30 +21,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(); } @@ -53,8 +58,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(); @@ -73,11 +81,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); @@ -89,56 +97,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); @@ -152,45 +158,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); @@ -206,66 +214,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); @@ -279,44 +290,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([]) @@ -324,43 +338,247 @@ 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(): void + { + $entityDataObjBuilder = new EntityDataObjectBuilder(); + $parentDataObject = $entityDataObjBuilder + ->withName('parentObject') + ->withType('parentType') + ->withLinkedEntities(['child1Object' => 'childType1','child2Object' => 'childType2']) + ->build(); + + $child1DataObject = $entityDataObjBuilder + ->withName('child1Object') + ->withType('childType1') + ->withDataFields(['city' => 'Testcity','zip' => 12345]) + ->build(); + + $child2DataObject = $entityDataObjBuilder + ->withName('child2Object') + ->withType('childType2') + ->withDataFields(['city' => 'Testcity 2','zip' => 54321,'state' => 'Teststate']) + ->build(); + + $dataObjectCallback = function ($name) use ($child1DataObject, $child2DataObject) { + switch ($name) { + case 'child1Object': + return $child1DataObject; + case 'child2Object': + return $child2DataObject; + } + }; + $this->mockDataObjectHandler($dataObjectCallback); + + $operationDefinitionBuilder = new OperationDefinitionBuilder(); + $child1OperationDefinition = $operationDefinitionBuilder + ->withName('createchildType1') + ->withOperation('create') + ->withType('childType1') + ->withMetadata([ + 'city' => 'string', + 'zip' => 'integer' + ])->build(); + + $child2OperationDefinition = $operationDefinitionBuilder + ->withName('createchildType2') + ->withOperation('create') + ->withType('childType2') + ->withMetadata([ + 'city' => 'string', + 'zip' => 'integer', + 'state' => 'string' + ])->build(); + + $operationObjectCallback = function ($name) use ($child1OperationDefinition, $child2OperationDefinition) { + switch ($name) { + case 'createchildType1': + return $child1OperationDefinition; + case 'createchildType2': + return $child2OperationDefinition; + } + }; + $this->mockOperationDefinitionObjectHandler($operationObjectCallback); + + $arrayObElementBuilder = new OperationElementBuilder(); + $arrayElement = $arrayObElementBuilder + ->withKey('address') + ->withType(['childType1','childType2']) + ->withFields([]) + ->withElementType(OperationDefinitionObjectHandler::ENTITY_OPERATION_ARRAY) + //->withNestedElements(['childType1' => $child1Element, 'childType2' => $child2Element]) + ->build(); + + $parentOpElementBuilder = new OperationElementBuilder(); + $parentElement = $parentOpElementBuilder + ->withKey('parentType') + ->withType('parentType') + ->addElements(['address' => $arrayElement]) + ->build(); + + $operationResolver = new OperationDataArrayResolver(); + $result = $operationResolver->resolveOperationDataArray($parentDataObject, [$parentElement], 'create', false); + + $expectedResult = [ + 'parentType' => [ + 'address' => [ + [ + 'city' => 'Testcity', + 'zip' => '12345' + ], + [ + 'city' => 'Testcity 2', + 'zip' => '54321', + 'state' => 'Teststate' + ] + ], + 'name' => 'Hopper', + 'gpa' => '3.5678', + 'phone' => '5555555', + 'isPrimary' => '1' + ] + ]; + + $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(null, $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(null, $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..470317bbb 100644 --- a/dev/tests/unit/Magento/FunctionalTestFramework/DataGenerator/Util/DataExtensionUtilTest.php +++ b/dev/tests/unit/Magento/FunctionalTestFramework/DataGenerator/Util/DataExtensionUtilTest.php @@ -1,39 +1,31 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2018 Adobe + * All Rights Reserved. */ -namespace Magento\FunctionalTestingFramework\DataGenerator\Objects; +declare(strict_types=1); + +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 +38,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 +63,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 +90,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 +121,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); + $property->setValue(null, 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(null, $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..847d5d2b0 --- /dev/null +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Extension/BrowserLogUtilTest.php @@ -0,0 +1,76 @@ +<?php +/** + * Copyright 2019 Adobe + * All Rights Reserved. + */ + +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..3992852f4 --- /dev/null +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Module/Util/ModuleUtilTest.php @@ -0,0 +1,49 @@ +<?php +/** + * Copyright 2022 Adobe + * All Rights Reserved. + */ + +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 static 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..6d16cde3c 100644 --- a/dev/tests/unit/Magento/FunctionalTestFramework/Page/Handlers/PageObjectHandlerTest.php +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Page/Handlers/PageObjectHandlerTest.php @@ -1,89 +1,189 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ +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. * - * @param array $data + * @return void + * @throws XmlException */ - private function setMockParserOutput($data) + public function testDeprecatedPage(): void { - // clear section object handler value to inject parsed content - $property = new \ReflectionProperty(PageObjectHandler::class, 'INSTANCE'); + $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 + * + * @return void + */ + private function mockPageObjectHandlerWithData(array $mockData): void + { + $pageObjectHandlerProperty = new ReflectionProperty(PageObjectHandler::class, 'INSTANCE'); + $pageObjectHandlerProperty->setAccessible(true); + $pageObjectHandlerProperty->setValue(null, 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(null, $mockObjectManagerInstance); + } + + /** + * @inheritDoc + */ + public static function tearDownAfterClass(): void + { + parent::tearDownAfterClass(); + + $pageObjectHandlerProperty = new ReflectionProperty(PageObjectHandler::class, 'INSTANCE'); + $pageObjectHandlerProperty->setAccessible(true); + $pageObjectHandlerProperty->setValue(null, null); + + $objectManagerProperty = new ReflectionProperty(ObjectManager::class, 'instance'); + $objectManagerProperty->setAccessible(true); + $objectManagerProperty->setValue(null, 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..b52fcd884 100644 --- a/dev/tests/unit/Magento/FunctionalTestFramework/Page/Handlers/SectionObjectHandlerTest.php +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Page/Handlers/SectionObjectHandlerTest.php @@ -1,71 +1,174 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ +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. + * + * @return void + * @throws XmlException + */ + public function testDeprecatedSection(): void + { + $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 * - * @param array $data + * @return void */ - private function setMockParserOutput($data) + private function mockSectionObjectHandlerWithData(array $mockData): void { - // clear section object handler value to inject parsed content - $property = new \ReflectionProperty(SectionObjectHandler::class, "INSTANCE"); + $sectionObjectHandlerProperty = new ReflectionProperty(SectionObjectHandler::class, "INSTANCE"); + $sectionObjectHandlerProperty->setAccessible(true); + $sectionObjectHandlerProperty->setValue(null, 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(null, $mockObjectManagerInstance); + } + + /** + * @inheritDoc + */ + public static function tearDownAfterClass(): void + { + parent::tearDownAfterClass(); + + $sectionObjectHandlerProperty = new ReflectionProperty(SectionObjectHandler::class, "INSTANCE"); + $sectionObjectHandlerProperty->setAccessible(true); + $sectionObjectHandlerProperty->setValue(null, null); + + $objectManagerProperty = new ReflectionProperty(ObjectManager::class, 'instance'); + $objectManagerProperty->setAccessible(true); + $objectManagerProperty->setValue(null, 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..7abb88634 100644 --- a/dev/tests/unit/Magento/FunctionalTestFramework/Page/Objects/ElementObjectTest.php +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Page/Objects/ElementObjectTest.php @@ -1,14 +1,14 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ namespace tests\unit\Magento\FunctionalTestFramework\Page\Objects; 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..cb513017b 100644 --- a/dev/tests/unit/Magento/FunctionalTestFramework/Page/Objects/PageObjectTest.php +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Page/Objects/PageObjectTest.php @@ -1,13 +1,13 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ 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..e8ec6ca82 100644 --- a/dev/tests/unit/Magento/FunctionalTestFramework/Page/Objects/SectionObjectTest.php +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Page/Objects/SectionObjectTest.php @@ -1,14 +1,14 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ namespace tests\unit\Magento\FunctionalTestFramework\Page\Objects; 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..08ac13ca6 --- /dev/null +++ b/dev/tests/unit/Magento/FunctionalTestFramework/StaticCheck/AnnotationsCheckTest.php @@ -0,0 +1,185 @@ +<?php +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ + +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..4a20a2512 --- /dev/null +++ b/dev/tests/unit/Magento/FunctionalTestFramework/StaticCheck/DeprecatedEntityUsageCheckTest.php @@ -0,0 +1,369 @@ +<?php +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ + +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, 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, $mockObjectManagerInstance); + } + + /** + * @inheritDoc + */ + public static function tearDownAfterClass(): void + { + parent::tearDownAfterClass(); + + $operationDefinitionObjectHandlerProperty = new ReflectionProperty( + OperationDefinitionObjectHandler::class, + 'INSTANCE' + ); + $operationDefinitionObjectHandlerProperty->setAccessible(true); + $operationDefinitionObjectHandlerProperty->setValue(null, null); + + $objectManagerProperty = new ReflectionProperty(ObjectManager::class, 'instance'); + $objectManagerProperty->setAccessible(true); + $objectManagerProperty->setValue(null, 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 8133c82a3..213fbbc8b 100644 --- a/dev/tests/unit/Magento/FunctionalTestFramework/Suite/Handlers/SuiteObjectHandlerTest.php +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Suite/Handlers/SuiteObjectHandlerTest.php @@ -1,27 +1,33 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2018 Adobe + * All Rights Reserved. */ -namespace Tests\unit\Magento\FunctionalTestFramework\Suite\Handlers; -use AspectMock\Test as AspectMock; -use Magento\FunctionalTestingFramework\ObjectManager\ObjectManager; -use Magento\FunctionalTestingFramework\ObjectManagerFactory; +declare(strict_types=1); + +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; class SuiteObjectHandlerTest extends MagentoTestCase { /** - * 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 @@ -58,7 +64,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 @@ -75,35 +81,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); + $property->setValue(null, null); // clear suite object handler value to inject parsed content - $property = new \ReflectionProperty(SuiteObjectHandler::class, 'SUITE_OBJECT_HANLDER_INSTANCE'); + $property = new ReflectionProperty(SuiteObjectHandler::class, 'instance'); + $property->setAccessible(true); + $property->setValue(null, null); + + $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(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]); + $property->setValue(null, $instance); } } diff --git a/dev/tests/unit/Magento/FunctionalTestFramework/Suite/SuiteGeneratorTest.php b/dev/tests/unit/Magento/FunctionalTestFramework/Suite/SuiteGeneratorTest.php index 7bef700ab..909f1f9be 100644 --- a/dev/tests/unit/Magento/FunctionalTestFramework/Suite/SuiteGeneratorTest.php +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Suite/SuiteGeneratorTest.php @@ -1,13 +1,18 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2018 Adobe + * All Rights Reserved. */ -namespace Tests\unit\Magento\FunctionalTestFramework\Suite; -use AspectMock\Test as AspectMock; -use Magento\FunctionalTestingFramework\ObjectManager\ObjectManager; +declare(strict_types=1); + +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,40 +20,88 @@ 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; class SuiteGeneratorTest extends MagentoTestCase { - /** - * Setup entry append and clear for Suite Generator + * Before test functionality. + * + * @return void */ - public static function setUpBeforeClass() + protected function setUp(): void { - AspectMock::double(SuiteGenerator::class, [ - 'clearPreviousSessionConfigEntries' => null, - 'appendEntriesToConfig' => null - ]); + TestLoggingUtil::getInstance()->setMockLoggingUtil(); } /** - * Before test functionality + * Tests generating a suite given a set of parsed test data. + * * @return void + * @throws Exception */ - public function setUp() + public function testGenerateTestgroupmembership(): void { - TestLoggingUtil::getInstance()->setMockLoggingUtil(); + $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'] + ); } /** - * 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 @@ -65,26 +118,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 @@ -101,28 +156,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') @@ -130,82 +194,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(array $testData, array $suiteData): void + { + $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(null, $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(null, $objectManagerMockInstance); + } + + /** + * Function used to clear mock properties. + * + * @return void */ - private function setMockTestAndSuiteParserOutput($testData, $suiteData) + private function clearMockResolverProperties(): void { - $property = new \ReflectionProperty(SuiteGenerator::class, 'SUITE_GENERATOR_INSTANCE'); + $property = new ReflectionProperty(SuiteGenerator::class, 'instance'); $property->setAccessible(true); - $property->setValue(null); + $property->setValue(null, 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); + $property->setValue(null, null); // clear suite object handler value to inject parsed content - $property = new \ReflectionProperty(SuiteObjectHandler::class, 'SUITE_OBJECT_HANLDER_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 = new ReflectionProperty(SuiteObjectHandler::class, 'instance'); $property->setAccessible(true); - $property->setValue($instance, $instance); + $property->setValue(null, null); } /** - * clean up function runs after all tests + * @inheritDoc */ - public static function tearDownAfterClass() + protected function tearDown(): void + { + GenerationErrorHandler::getInstance()->reset(); + } + + /** + * @inheritDoc + */ + public static function tearDownAfterClass(): void { - TestLoggingUtil::getInstance()->clearMockLoggingUtil(); parent::tearDownAfterClass(); + + $objectManagerProperty = new ReflectionProperty(ObjectManager::class, 'instance'); + $objectManagerProperty->setAccessible(true); + $objectManagerProperty->setValue(null, null); + + $suiteGeneratorServiceProperty = new ReflectionProperty(SuiteGeneratorService::class, 'INSTANCE'); + $suiteGeneratorServiceProperty->setAccessible(true); + $suiteGeneratorServiceProperty->setValue(null, 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 5d1dd7878..2e9dd7a1a 100644 --- a/dev/tests/unit/Magento/FunctionalTestFramework/Test/Config/ActionGroupDomTest.php +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Test/Config/ActionGroupDomTest.php @@ -1,14 +1,15 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2018 Adobe + * All Rights Reserved. */ -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 { @@ -25,7 +26,7 @@ public function testActionGroupDomStepKeyValidation() </actionGroups>"; $exceptionCollector = new ExceptionCollector(); - $actionDom = new ActionGroupDom($sampleXml, 'dupeStepKeyActionGroup.xml', $exceptionCollector); + new ActionGroupDom($sampleXml, 'dupeStepKeyActionGroup.xml', $exceptionCollector); $this->expectException(\Exception::class); $exceptionCollector->throwException(); @@ -47,4 +48,25 @@ public function testActionGroupDomInvalidXmlValidation() $this->expectExceptionMessage("XML Parse Error: invalid.xml\n"); new ActionGroupDom($sampleXml, 'invalid.xml', $exceptionCollector); } + + /** + * Test detection of two ActionGroups with the same Name in the same file. + */ + public function testActionGroupDomDuplicateActionGroupsValidation() + { + $sampleXml = '<actionGroups> + <actionGroup name="actionGroupName"> + <wait time="1" stepKey="key1" /> + </actionGroup> + <actionGroup name="actionGroupName"> + <wait time="1" stepKey="key1" /> + </actionGroup> + </actionGroups>'; + + $exceptionCollector = new ExceptionCollector(); + new ActionGroupDom($sampleXml, 'dupeNameActionGroup.xml', $exceptionCollector); + $this->expectException(\Exception::class); + $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 new file mode 100644 index 000000000..2fa334599 --- /dev/null +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Test/Config/DomTest.php @@ -0,0 +1,56 @@ +<?php +/** + * Copyright 2018 Adobe + * All Rights Reserved. + */ + +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 tests\unit\Util\MagentoTestCase; + +class DomTest extends MagentoTestCase +{ + /** + * Test Test duplicate step key validation + */ + public function testTestStepKeyDuplicateValidation() + { + $sampleXml = '<tests> + <test name="testName"> + <comment userInput="step1" stepKey="key1"/> + <comment userInput="step2" stepKey="key1"/> + </test> + </tests>'; + + $exceptionCollector = new ExceptionCollector(); + new ActionGroupDom($sampleXml, 'dupeStepKeyTest.xml', $exceptionCollector); + + $this->expectException(\Exception::class); + $this->expectExceptionMessageMatches("/stepKey: key1 is used more than once. \(Parent: testName\)/"); + $exceptionCollector->throwException(); + } + + /** + * Test detection of two Tests with the same Name in the same file. + */ + public function testTestNameDuplicateValidation() + { + $sampleXml = '<tests> + <test name="testName"> + <comment userInput="step1" stepKey="key1"/> + </test> + <test name="testName"> + <comment userInput="step1" stepKey="key1"/> + </test> + </tests>'; + + $exceptionCollector = new ExceptionCollector(); + new ActionGroupDom($sampleXml, 'dupeTestsTest.xml', $exceptionCollector); + $this->expectException(\Exception::class); + $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 new file mode 100644 index 000000000..03463cac8 --- /dev/null +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Test/Handlers/ActionGroupObjectHandlerTest.php @@ -0,0 +1,153 @@ +<?php +/** + * Copyright 2019 Adobe + * All Rights Reserved. + */ + +declare(strict_types=1); + +namespace tests\unit\Magento\FunctionalTestFramework\Test\Handlers; + +use Exception; +use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; +use Magento\FunctionalTestingFramework\ObjectManager; +use Magento\FunctionalTestingFramework\ObjectManagerFactory; +use Magento\FunctionalTestingFramework\Test\Handlers\ActionGroupObjectHandler; +use Magento\FunctionalTestingFramework\Test\Parsers\ActionGroupDataParser; +use ReflectionProperty; +use tests\unit\Util\ActionGroupArrayBuilder; +use tests\unit\Util\MagentoTestCase; + +/** + * Class ActionGroupObjectHandlerTest + */ +class ActionGroupObjectHandlerTest extends MagentoTestCase +{ + /** + * Validate getObject should throw exception if test extends from itself. + * + * @return void + * @throws Exception + */ + public function testGetTestObjectWithInvalidExtends(): void + { + // Set up action group data + $nameOne = 'actionGroupOne'; + $actionGroupOne = (new ActionGroupArrayBuilder()) + ->withName($nameOne) + ->withExtendedAction($nameOne) + ->withAnnotations() + ->withFilename() + ->withActionObjects() + ->build(); + $this->mockActionGroupObjectHandlerWithData(['actionGroups' => $actionGroupOne]); + + $handler = ActionGroupObjectHandler::getInstance(); + + $this->expectException(TestFrameworkException::class); + $this->expectExceptionMessage('Mftf Action Group can not extend from itself: ' . $nameOne); + $handler->getObject('actionGroupOne'); + } + + /** + * Validate getAllObjects should throw exception if test extends from itself. + * + * @return void + * @throws Exception + */ + public function testGetAllTestObjectsWithInvalidExtends(): void + { + // Set up action group data + $nameOne = 'actionGroupOne'; + $nameTwo = 'actionGroupTwo'; + $actionGroupOne = (new ActionGroupArrayBuilder()) + ->withName($nameOne) + ->withExtendedAction($nameOne) + ->withAnnotations() + ->withFilename() + ->withActionObjects() + ->build(); + $actionGroupTwo = (new ActionGroupArrayBuilder()) + ->withName($nameTwo) + ->withExtendedAction() + ->withAnnotations() + ->withFilename() + ->withActionObjects() + ->build(); + + $this->mockActionGroupObjectHandlerWithData( + [ + 'actionGroups' => array_merge( + $actionGroupOne, + $actionGroupTwo + ) + ] + ); + + $handler = ActionGroupObjectHandler::getInstance(); + + $this->expectException(TestFrameworkException::class); + $this->expectExceptionMessage('Mftf Action Group can not extend from itself: ' . $nameOne); + $handler->getAllObjects(); + } + + /** + * Create mock action group object handler with data. + * + * @param array $mockData + * + * @return void + */ + private function mockActionGroupObjectHandlerWithData(array $mockData): void + { + $actionGroupObjectHandlerProperty = new ReflectionProperty(ActionGroupObjectHandler::class, 'instance'); + $actionGroupObjectHandlerProperty->setAccessible(true); + $actionGroupObjectHandlerProperty->setValue(null, 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, $mockObjectManagerInstance); + } + + /** + * @inheritDoc + */ + public static function tearDownAfterClass(): void + { + parent::tearDownAfterClass(); + + $actionGroupObjectHandlerProperty = new ReflectionProperty(ActionGroupObjectHandler::class, 'instance'); + $actionGroupObjectHandlerProperty->setAccessible(true); + $actionGroupObjectHandlerProperty->setValue(null, null); + + $objectManagerProperty = new ReflectionProperty(ObjectManager::class, 'instance'); + $objectManagerProperty->setAccessible(true); + $objectManagerProperty->setValue(null, null); + } +} diff --git a/dev/tests/unit/Magento/FunctionalTestFramework/Test/Handlers/TestObjectHandlerTest.php b/dev/tests/unit/Magento/FunctionalTestFramework/Test/Handlers/TestObjectHandlerTest.php index 1dbeb50db..b74cbf28a 100644 --- a/dev/tests/unit/Magento/FunctionalTestFramework/Test/Handlers/TestObjectHandlerTest.php +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Test/Handlers/TestObjectHandlerTest.php @@ -1,14 +1,16 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2018 Adobe + * All Rights Reserved. */ -namespace Tests\unit\Magento\FunctionalTestFramework\Test\Handlers; +declare(strict_types=1); -use AspectMock\Test as AspectMock; +namespace tests\unit\Magento\FunctionalTestFramework\Test\Handlers; -use Go\Aop\Aspect; +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,19 +18,36 @@ use Magento\FunctionalTestingFramework\Test\Objects\TestHookObject; use Magento\FunctionalTestingFramework\Test\Objects\TestObject; use Magento\FunctionalTestingFramework\Test\Parsers\TestDataParser; -use Magento\FunctionalTestingFramework\Test\Util\ActionObjectExtractor; 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\TestLoggingUtil; +use Magento\FunctionalTestingFramework\Filter\FilterList; +use Magento\FunctionalTestingFramework\Util\Script\TestDependencyUtil; 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(); @@ -40,7 +59,7 @@ public function testGetTestObject() ->withTestActions() ->build(); - $this->setMockParserOutput(['tests' => $mockData]); + $this->mockTestObjectHandler($mockData); // run object handler method $toh = TestObjectHandler::getInstance(); @@ -76,7 +95,7 @@ public function testGetTestObject() $expectedFailedHookObject = new TestHookObject( TestObjectExtractor::TEST_FAILED_HOOK, $testDataArrayBuilder->testName, - [$expectedFailedActionObject] + ["saveScreenshot" => $expectedFailedActionObject] ); $expectedTestActionObject = new ActionObject( @@ -89,7 +108,8 @@ public function testGetTestObject() ["testActionInTest" => $expectedTestActionObject], [ 'features' => ['NO MODULE DETECTED'], - 'group' => ['test'] + 'group' => ['test'], + 'description' => ['test_files' => '<h3>Test files</h3>', 'deprecated' => []] ], [ TestObjectExtractor::TEST_BEFORE_HOOK => $expectedBeforeHookObject, @@ -103,20 +123,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()) @@ -130,7 +152,7 @@ public function testGetTestsByGroup() ->withTestActions() ->build(); - $this->setMockParserOutput(['tests' => array_merge($includeTest, $excludeTest)]); + $this->mockTestObjectHandler(array_merge($includeTest, $excludeTest)); // execute test method $toh = TestObjectHandler::getInstance(); @@ -143,23 +165,28 @@ 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 = "SomeTestModule"; + $moduleExpected = "SomeModuleName"; + $moduleExpectedTest = $moduleExpected . "Test"; $filepath = DIRECTORY_SEPARATOR . - "user" . + "user" . DIRECTORY_SEPARATOR . "magento2ce" . DIRECTORY_SEPARATOR . "dev" . DIRECTORY_SEPARATOR . "tests" . DIRECTORY_SEPARATOR . "acceptance" . DIRECTORY_SEPARATOR . "tests" . DIRECTORY_SEPARATOR . - $moduleExpected . DIRECTORY_SEPARATOR . - "Tests" . DIRECTORY_SEPARATOR . + "functional" . DIRECTORY_SEPARATOR . + "Vendor" . DIRECTORY_SEPARATOR . + $moduleExpectedTest; + $file = $filepath . DIRECTORY_SEPARATOR . + "Test" . DIRECTORY_SEPARATOR . "text.xml"; // set up mock data $testDataArrayBuilder = new TestDataArrayBuilder(); @@ -169,9 +196,11 @@ public function testGetTestWithModuleName() ->withAfterHook() ->withBeforeHook() ->withTestActions() - ->withFileName($filepath) + ->withFileName($file) ->build(); - $this->setMockParserOutput(['tests' => $mockData]); + + $this->mockTestObjectHandler($mockData, ['Vendor_' . $moduleExpected => $filepath]); + // Execute Test Method $toh = TestObjectHandler::getInstance(); $actualTestObject = $toh->getObject($testDataArrayBuilder->testName); @@ -181,21 +210,295 @@ public function testGetTestWithModuleName() } /** - * Function used to set mock for parser return and force init method to run between tests. + * getObject should throw exception if test extends from itself. + * + * @return void + * @throws Exception + */ + public function testGetTestObjectWithInvalidExtends(): void + { + // set up Test Data + $testOne = (new TestDataArrayBuilder()) + ->withName('testOne') + ->withTestReference('testOne') + ->withAnnotations() + ->withFailedHook() + ->withAfterHook() + ->withBeforeHook() + ->withTestActions() + ->build(); + + $this->mockTestObjectHandler($testOne); + + $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. + * + * @return void + * @throws Exception + */ + public function testGetAllTestObjectsWithInvalidExtends(): void + { + // set up Test Data + $testOne = (new TestDataArrayBuilder()) + ->withName('testOne') + ->withTestReference('testOne') + ->withAnnotations() + ->withFailedHook() + ->withAfterHook() + ->withBeforeHook() + ->withTestActions() + ->build(); + $testTwo = (new TestDataArrayBuilder()) + ->withName('testTwo') + ->withAnnotations() + ->withFailedHook() + ->withAfterHook() + ->withBeforeHook() + ->withTestActions() + ->build(); + + $this->mockTestObjectHandler(array_merge($testOne, $testTwo)); + + $toh = TestObjectHandler::getInstance(); + $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); + } + + /** + * Validate test object when ENABLE_PAUSE is set to true. + * + * @return void + * @throws Exception + */ + public function testGetTestObjectWhenEnablePause(): void + { + // 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] + ); + + $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(); + } + + /** + * Mock test object handler. * * @param array $data - * @throws \Exception + * @param array|null $paths + * + * @return void */ - private function setMockParserOutput($data) + private function mockTestObjectHandler(array $data, ?array $paths = null): void { + 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 = new ReflectionProperty(TestObjectHandler::class, 'testObjectHandler'); + $property->setAccessible(true); + $property->setValue(null, 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(null, $objectManagerMockInstance); + + $resolver = ModuleResolver::getInstance(); + $property = new ReflectionProperty(ModuleResolver::class, 'enabledModuleNameAndPaths'); $property->setAccessible(true); - $property->setValue(null); + $property->setValue($resolver, $paths); + } + + /** + * Basic test for exclude group Filter + * + * @return void + * @throws Exception + */ + public function testGetFilteredTestNamesWhenExcludeGroupFilterIsApplied() + { + $fileList = new FilterList(['excludeGroup' => ['test']]); + $toh = TestObjectHandler::getInstance()->getAllObjects(); + $testDependencyUtil = new TestDependencyUtil(); + $result = $testDependencyUtil->getFilteredTestNames($toh, $fileList->getFilters()); + $this->assertIsArray($result); + $this->assertEquals(count($result), 0); + } - $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]); + /** + * Basic test for include group Filter + * + * @return void + * @throws Exception + */ + public function testGetFilteredTestNamesWhenIncludeGroupFilterIsApplied() + { + $fileList = new FilterList(['includeGroup' => ['test']]); + $toh = TestObjectHandler::getInstance()->getAllObjects(); + $testDependencyUtil = new TestDependencyUtil(); + $result = $testDependencyUtil->getFilteredTestNames($toh, $fileList->getFilters()); + $this->assertIsArray($result); + $this->assertEquals(count($result), 1); + $this->assertEquals($result['testTest'], 'testTest'); + } + + /** + * Basic test when no filter applied + * + * @return void + * @throws Exception + */ + public function testGetFilteredTestNamesWhenNoFilterIsApplied() + { + $fileList = new FilterList(); + $toh = TestObjectHandler::getInstance()->getAllObjects(); + $testDependencyUtil = new TestDependencyUtil(); + $result = $testDependencyUtil->getFilteredTestNames($toh, $fileList->getFilters()); + $this->assertIsArray($result); + $this->assertEquals(count($result), 1); + //returns all test Names + $this->assertEquals($result['testTest'], 'testTest'); } } diff --git a/dev/tests/unit/Magento/FunctionalTestFramework/Test/Objects/ActionGroupObjectTest.php b/dev/tests/unit/Magento/FunctionalTestFramework/Test/Objects/ActionGroupObjectTest.php index 796e8a28b..9df9eb42f 100644 --- a/dev/tests/unit/Magento/FunctionalTestFramework/Test/Objects/ActionGroupObjectTest.php +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Test/Objects/ActionGroupObjectTest.php @@ -1,12 +1,13 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ +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 +16,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 +27,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 +50,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(null, $sectionInstance); $actionGroupUnderTest = (new ActionGroupObjectBuilder()) ->withActionObjects( @@ -174,30 +241,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(null, $sectionInstance); $actionGroupUnderTest = (new ActionGroupObjectBuilder()) ->withActionObjects( @@ -208,21 +295,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 +333,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 +407,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 +438,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(null, $instance); } /** @@ -337,11 +466,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 +485,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..e68696c24 100644 --- a/dev/tests/unit/Magento/FunctionalTestFramework/Test/Objects/ActionObjectTest.php +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Test/Objects/ActionObjectTest.php @@ -1,23 +1,27 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ +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 +29,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 +84,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 +113,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 +148,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 +179,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 +213,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 +243,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(null, $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(null, $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 +354,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 +386,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 +420,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 +443,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(null, $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(null, $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 new file mode 100644 index 000000000..5093f6ea7 --- /dev/null +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Test/Util/ActionGroupAnnotationExtractorTest.php @@ -0,0 +1,60 @@ +<?php +/** + * Copyright 2019 Adobe + * All Rights Reserved. + */ + +declare(strict_types=1); + +namespace tests\unit\Magento\FunctionalTestFramework\Test\Util; + +use Exception; +use Magento\FunctionalTestingFramework\Test\Util\ActionGroupAnnotationExtractor; +use PHPUnit\Framework\TestCase; +use tests\unit\Util\TestLoggingUtil; + +/** + * Class ActionGroupAnnotationExtractorTest + */ +class ActionGroupAnnotationExtractorTest extends TestCase +{ + /** + * @inheritDoc + */ + protected function setUp(): void + { + TestLoggingUtil::getInstance()->setMockLoggingUtil(); + } + + /** + * Annotation extractor takes in raw array and condenses it to expected format. + * + * @return void + * @throws Exception + */ + public function testActionGroupExtractAnnotations(): void + { + // Test Data + $actionGroupAnnotations = [ + 'nodeName' => 'annotations', + 'description' => [ + 'nodeName' => 'description', + 'value' => 'someDescription' + ] + ]; + // Perform Test + $extractor = new ActionGroupAnnotationExtractor(); + $returnedAnnotations = $extractor->extractAnnotations($actionGroupAnnotations, 'fileName'); + + // Asserts + $this->assertEquals('someDescription', $returnedAnnotations['description']); + } + + /** + * @inheritDoc + */ + 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..31aa64266 100644 --- a/dev/tests/unit/Magento/FunctionalTestFramework/Test/Util/ActionGroupObjectExtractorTest.php +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Test/Util/ActionGroupObjectExtractorTest.php @@ -1,12 +1,13 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2018 Adobe + * All Rights Reserved. */ + 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 +18,7 @@ class ActionGroupObjectExtractorTest extends MagentoTestCase /** * Setup method */ - public function setUp() + public function setUp(): void { $this->testActionGroupObjectExtractor = new ActionGroupObjectExtractor(); TestLoggingUtil::getInstance()->setMockLoggingUtil(); @@ -34,23 +35,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 +88,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 0f367c226..cfd67885f 100644 --- a/dev/tests/unit/Magento/FunctionalTestFramework/Test/Util/ActionMergeUtilTest.php +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Test/Util/ActionMergeUtilTest.php @@ -1,27 +1,32 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ + +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; +use Magento\FunctionalTestingFramework\Exceptions\XmlException; 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(); } @@ -30,8 +35,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; @@ -45,7 +52,6 @@ public function testResolveActionStepOrdering() $stepKey = 'stepKey'. $i; $type = 'testType'; $actionAttributes = []; - $actions[] = new ActionObject($stepKey, $type, $actionAttributes); } @@ -92,15 +98,17 @@ 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'; $dataFieldName = 'myfield'; $dataFieldValue = 'myValue'; $userInputKey = "userInput"; - $userinputValue = "{{" . "${dataObjectName}.${dataFieldName}}}"; + $userInputValue = "{{" . "{$dataObjectName}.{$dataFieldName}}}"; $actionName = "myAction"; $actionType = "myCustomType"; @@ -109,18 +117,22 @@ 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]); + $this->assertEquals($userInputValue, $actions[$actionName]->getCustomActionAttributes()[$userInputKey]); $mergeUtil = new ActionMergeUtil("test", "TestCase"); $resolvedActions = $mergeUtil->resolveActionSteps($actions); - $this->assertEquals($dataFieldValue, $resolvedActions[$actionName]->getCustomActionAttributes()[$userInputKey]); } @@ -128,11 +140,12 @@ public function testResolveActionStepEntityData() * Verify that an XmlException is thrown when an action references a non-existant action. * * @return void + * @throws TestReferenceException + * @throws XmlException */ - public function testNoActionException() + public function testNoActionException(): void { $actionObjects = []; - $actionObjects[] = new ActionObject('actionKey1', 'bogusType', []); $actionObjects[] = new ActionObject( 'actionKey2', @@ -142,8 +155,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); } @@ -152,8 +164,10 @@ public function testNoActionException() * Verify that a <waitForPageLoad> action is added after actions that have a wait (timeout property). * * @return void + * @throws TestReferenceException + * @throws XmlException */ - public function testInsertWait() + public function testInsertWait(): void { $actionObjectOne = new ActionObject('actionKey1', 'bogusType', []); $actionObjectOne->setTimeout(42); @@ -174,11 +188,151 @@ public function testInsertWait() } /** - * After class functionality + * Verify that a <fillField> action is replaced by <fillSecretField> when secret _CREDS are referenced. + * * @return void + * @throws TestReferenceException + * @throws XmlException */ - public static function tearDownAfterClass() + public function testValidFillFieldSecretFunction(): void + { + $actionObjectOne = new ActionObject( + 'actionKey1', + 'fillField', + ['userInput' => '{{_CREDS.username}}', 'requiredCredentials' => 'username'] + ); + $actionObject = [$actionObjectOne]; + + $actionMergeUtil = new ActionMergeUtil('actionMergeUtilTest', 'TestCase'); + $result = $actionMergeUtil->resolveActionSteps($actionObject); + + $expectedValue = new ActionObject( + 'actionKey1', + 'fillSecretField', + ['userInput' => '{{_CREDS.username}}','requiredCredentials' => 'username'] + ); + $this->assertEquals($expectedValue, $result['actionKey1']); + } + + /** + * Verify that a <magentoCLI> action uses <magentoCLI> when secret _CREDS are referenced. + * + * @return void + * @throws TestReferenceException + * @throws XmlException + */ + public function testValidMagentoCLISecretFunction(): void + { + $actionObjectOne = new ActionObject( + 'actionKey1', + 'magentoCLI', + ['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}}', + 'requiredCredentials' => '' + ] + ); + $this->assertEquals($expectedValue, $result['actionKey1']); + } + + /** + * 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(): void + { + $actionObjectOne = new ActionObject( + 'actionKey1', + 'field', + ['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}}','requiredCredentials' => ''] + ); + $this->assertEquals($expectedValue, $result['actionKey1']); + } + + /** + * Verify that a <click> action throws an exception when secret _CREDS are referenced. + * + * @return void + * @throws TestReferenceException + * @throws XmlException + */ + public function testInvalidSecretFunctions(): void + { + $this->expectException(TestReferenceException::class); + $this->expectExceptionMessage( + 'You cannot reference secret data outside of the fillField, magentoCLI, seeInField and createData actions' + ); + + $actionObjectOne = new ActionObject( + 'actionKey1', + 'click', + ['userInput' => '{{_CREDS.username}}','requiredCredentials' => 'username'] + ); + $actionObject = [$actionObjectOne]; + + $actionMergeUtil = new ActionMergeUtil('actionMergeUtilTest', 'TestCase'); + $actionMergeUtil->resolveActionSteps($actionObject); + } + + /** + * After class functionality. + * + * @return void + */ + public static function tearDownAfterClass(): void { TestLoggingUtil::getInstance()->clearMockLoggingUtil(); } + + /** + * Verify that a <seeInField> action is replaced by <seeInSecretField> when secret _CREDS are referenced. + * + * @return void + * @throws TestReferenceException + * @throws XmlException + */ + public function testValidSeeInSecretFieldFunction(): void + { + $actionObjectOne = new ActionObject( + 'actionKey1', + 'seeInField', + ['userInput' => '{{_CREDS.username}}', 'requiredCredentials' => 'username'] + ); + $actionObject = [$actionObjectOne]; + + $actionMergeUtil = new ActionMergeUtil('actionMergeUtilTest', 'TestCase'); + $result = $actionMergeUtil->resolveActionSteps($actionObject); + + $expectedValue = new ActionObject( + 'actionKey1', + 'seeInSecretField', + ['userInput' => '{{_CREDS.username}}','requiredCredentials' => 'username'] + ); + $this->assertEquals($expectedValue, $result['actionKey1']); + } } diff --git a/dev/tests/unit/Magento/FunctionalTestFramework/Test/Util/ActionObjectExtractorTest.php b/dev/tests/unit/Magento/FunctionalTestFramework/Test/Util/ActionObjectExtractorTest.php index f584adea9..a746a0647 100644 --- a/dev/tests/unit/Magento/FunctionalTestFramework/Test/Util/ActionObjectExtractorTest.php +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Test/Util/ActionObjectExtractorTest.php @@ -1,13 +1,14 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2018 Adobe + * All Rights Reserved. */ + namespace tests\unit\Magento\FunctionalTestFramework\Test\Util; 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 +19,7 @@ class ActionObjectExtractorTest extends MagentoTestCase /** * Setup method */ - public function setUp() + public function setUp(): void { $this->testActionObjectExtractor = new ActionObjectExtractor(); TestLoggingUtil::getInstance()->setMockLoggingUtil(); @@ -130,7 +131,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..607fa3073 100644 --- a/dev/tests/unit/Magento/FunctionalTestFramework/Test/Util/AnnotationExtractorTest.php +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Test/Util/AnnotationExtractorTest.php @@ -1,115 +1,122 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2018 Adobe + * All Rights Reserved. */ + +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 + * Annotation extractor should throw warning when required annotations are missing. * - * @throws \Exception + * @return void + * @throws Exception */ - public function testMissingAnnotations() + public function testMissingAnnotations(): void { // Test Data, missing title, description, and severity $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' ] ], - "group" => [ + 'group' => [ [ - "nodeName" => "group", - "value" => "TestGroup" + 'nodeName' => 'group', + 'value' => 'TestGroup' ] ], ]; // Perform Test $extractor = new AnnotationExtractor(); - $returnedAnnotations = $extractor->extractAnnotations($testAnnotations, "testFileName"); + $extractor->extractAnnotations($testAnnotations, 'testFileName'); // Asserts TestLoggingUtil::getInstance()->validateMockLogStatement( @@ -117,99 +124,178 @@ public function testMissingAnnotations() 'DEPRECATION: Test testFileName is missing required annotations.', [ 'testName' => 'testFileName', - 'missingAnnotations' => "title, description, severity" + 'missingAnnotations' => 'title, description, severity' ] ); } - public function testTestCaseIdUniqueness() + /** + * Annotation extractor should throw warning when required annotations are empty. + * + * @return void + * @throws Exception + */ + public function testEmptyRequiredAnnotations(): void + { + // Test Data, missing title, description, and severity + $testAnnotations = [ + 'nodeName' => 'annotations', + 'features' => [ + [ + 'nodeName' => 'features', + 'value' => '' + ] + ], + 'stories' => [ + [ + 'nodeName' => 'stories', + 'value' => 'TestStories' + ] + ], + 'title' => [ + [ + 'nodeName' => 'title', + 'value' => ' ' + ] + ], + 'description' => [ + [ + 'nodeName' => 'description', + 'value' => "\t" + ] + ], + 'severity' => [ + [ + 'nodeName' => 'severity', + 'value' => '' + ] + ], + 'group' => [ + [ + 'nodeName' => 'group', + 'value' => 'TestGroup' + ] + ], + ]; + // Perform Test + $extractor = new AnnotationExtractor(); + $returnedAnnotations = $extractor->extractAnnotations($testAnnotations, 'testFileName'); + + // Asserts + TestLoggingUtil::getInstance()->validateMockLogStatement( + 'warning', + 'DEPRECATION: Test testFileName is missing required annotations.', + [ + 'testName' => 'testFileName', + 'missingAnnotations' => 'title, description, severity' + ] + ); + } + + /** + * 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 c3d418516..98d922c93 100644 --- a/dev/tests/unit/Magento/FunctionalTestFramework/Test/Util/ObjectExtensionUtilTest.php +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Test/Util/ObjectExtensionUtilTest.php @@ -1,60 +1,73 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2018 Adobe + * All Rights Reserved. */ + +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; class ObjectExtensionUtilTest extends TestCase { /** - * Before test functionality + * Before test functionality. + * * @return void + * @throws Exception */ - public function setUp() + protected function setUp(): void { TestLoggingUtil::getInstance()->setMockLoggingUtil(); } /** - * Tests generating a test that extends another test - * @throws \Exception + * After class functionality. + * + * @return void */ - public function testGenerateExtendedTest() + public static function tearDownAfterClass(): void + { + TestLoggingUtil::getInstance()->clearMockLoggingUtil(); + } + + /** + * Tests generating a test that extends another test. + * + * @return void + * @throws Exception + */ + public function testGenerateExtendedTest(): void { $mockActions = [ - "mockStep" => ["nodeName" => "mockNode", "stepKey" => "mockStep"] + 'mockStep' => ['nodeName' => 'mockNode', 'stepKey' => 'mockStep'] ]; $testDataArrayBuilder = new TestDataArrayBuilder(); $mockSimpleTest = $testDataArrayBuilder ->withName('simpleTest') - ->withAnnotations(['title'=>[['value' => 'simpleTest']]]) + ->withAnnotations(['title' => [['value' => 'simpleTest']]]) ->withTestActions($mockActions) ->build(); $mockExtendedTest = $testDataArrayBuilder ->withName('extendedTest') - ->withAnnotations(['title'=>[['value' => 'extendedTest']]]) - ->withTestReference("simpleTest") + ->withAnnotations(['title' => [['value' => 'extendedTest']]]) + ->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 @@ -68,38 +81,40 @@ 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(); $mockSimpleTest = $testDataArrayBuilder ->withName('simpleTest') - ->withAnnotations(['title'=>[['value' => 'simpleTest']]]) + ->withAnnotations(['title' => [['value' => 'simpleTest']]]) ->withBeforeHook($mockBeforeHooks) ->withAfterHook($mockAfterHooks) ->build(); $mockExtendedTest = $testDataArrayBuilder ->withName('extendedTest') - ->withAnnotations(['title'=>[['value' => 'extendedTest']]]) - ->withTestReference("simpleTest") + ->withAnnotations(['title' => [['value' => 'extendedTest']]]) + ->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 @@ -113,24 +128,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 @@ -139,16 +156,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 @@ -158,21 +177,21 @@ public function testExtendingExtendedTest() $mockSimpleTest = $testDataArrayBuilder ->withName('simpleTest') - ->withAnnotations(['title'=>[['value' => 'simpleTest']]]) + ->withAnnotations(['title' => [['value' => 'simpleTest']]]) ->withTestActions() - ->withTestReference("anotherTest") + ->withTestReference('anotherTest') ->build(); $mockExtendedTest = $testDataArrayBuilder ->withName('extendedTest') - ->withAnnotations(['title'=>[['value' => 'extendedTest']]]) - ->withTestReference("simpleTest") + ->withAnnotations(['title' => [['value' => 'extendedTest']]]) + ->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'); @@ -180,43 +199,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' ], ]; @@ -239,27 +260,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" => "mockSimpleActionGroup", - "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' ], ]; @@ -271,7 +294,7 @@ public function testGenerateExtendedActionGroupNoParent() $this->setMockTestOutput(null, $mockActionGroupData); $this->expectExceptionMessage( - "Parent Action Group mockSimpleActionGroup not defined for Test " . $mockExtendedActionGroup['extends'] + 'Parent Action Group mockSimpleActionGroup not defined for Test ' . $mockExtendedActionGroup['name'] ); // parse and generate test object with mocked data @@ -279,29 +302,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" => "mockSimpleActionGroup", - "filename" => "someFile", - "extends" => "mockSimpleActionGroup", + 'nodeName' => 'actionGroup', + 'name' => 'mockExtendedActionGroup', + 'filename' => 'someFile', + 'extends' => 'mockSimpleActionGroup' ]; $mockActionGroupData = [ @@ -314,72 +339,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' => $mockSimpleActionGroup['name']] + ['parent' => $mockSimpleActionGroup['name'], 'actionGroup' => $mockExtendedActionGroup['name']] ); - throw $e; + throw $exception; } } /** - * Function used to set mock for parser return and force init method to run between tests. + * Tests generating a test that extends a skipped parent test. * - * @param array $testData - * @throws \Exception + * @return void + * @throws Exception */ - private function setMockTestOutput($testData = null, $actionGroupData = null) + public function testExtendedTestSkippedParent(): void { - // clear test object handler value to inject parsed content - $property = new \ReflectionProperty(TestObjectHandler::class, 'testObjectHandler'); - $property->setAccessible(true); - $property->setValue(null); + $testDataArrayBuilder = new TestDataArrayBuilder(); + $mockParentTest = $testDataArrayBuilder + ->withName('baseTest') + ->withAnnotations([ + 'skip' => ['nodeName' => 'skip', 'issueId' => [['nodeName' => 'issueId', 'value' => 'someIssue']]] + ]) + ->build(); - // clear test object handler value to inject parsed content - $property = new \ReflectionProperty(ActionGroupObjectHandler::class, 'ACTION_GROUP_OBJECT_HANDLER'); - $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 ($clazz) use ( - $mockDataParser, - $mockActionGroupParser - ) { - if ($clazz == TestDataParser::class) { - return $mockDataParser; - } - if ($clazz == ActionGroupDataParser::class) { - return $mockActionGroupParser; - } - }] - )->make(); - // bypass the private constructor - AspectMock::double(ObjectManagerFactory::class, ['getObjectManager' => $instance]); + $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', + [] + ); } /** - * After class functionality + * Function used to set mock for parser return and force init method to run between tests. + * + * @param array|null $testData + * @param array|null $actionGroupData + * * @return void + * @throws Exception */ - public static function tearDownAfterClass() + private function setMockTestOutput(?array $testData = null, ?array $actionGroupData = null): void { - TestLoggingUtil::getInstance()->clearMockLoggingUtil(); + // clear test object handler value to inject parsed content + $property = new ReflectionProperty(TestObjectHandler::class, 'testObjectHandler'); + $property->setAccessible(true); + $property->setValue(null, null); + + // clear test object handler value to inject parsed content + $property = new ReflectionProperty(ActionGroupObjectHandler::class, 'instance'); + $property->setAccessible(true); + $property->setValue(null, null); + + $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; + } + ) + ); + // clear object manager value to inject expected instance + $property = new ReflectionProperty(ObjectManager::class, 'instance'); + $property->setAccessible(true); + $property->setValue(null, $instance); } } diff --git a/dev/tests/unit/Magento/FunctionalTestFramework/Util/ActionMergeUtilTest.php b/dev/tests/unit/Magento/FunctionalTestFramework/Util/ActionMergeUtilTest.php new file mode 100644 index 000000000..4c2b0bbf8 --- /dev/null +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Util/ActionMergeUtilTest.php @@ -0,0 +1,34 @@ +<?php +/** + * Copyright 2018 Adobe + * All Rights Reserved. + */ + +namespace tests\unit\Magento\FunctionalTestFramework\Util; + +use Magento\FunctionalTestingFramework\Test\Objects\ActionObject; +use Magento\FunctionalTestingFramework\Test\Util\ActionMergeUtil; +use tests\unit\Util\MagentoTestCase; + +class ActionMergeUtilTest extends MagentoTestCase +{ + /** + * Test Exception Handler for merging actions + * + * @throws \Exception + */ + public function testMergeActionsException() + { + $testActionMergeUtil = new ActionMergeUtil(null, null); + + $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\""); + + $testActionMergeUtil->resolveActionSteps(["merge123" => $actionObject]); + } +} diff --git a/dev/tests/unit/Magento/FunctionalTestFramework/Util/ClassFileNameCheckTest.php b/dev/tests/unit/Magento/FunctionalTestFramework/Util/ClassFileNameCheckTest.php new file mode 100644 index 000000000..6681dc349 --- /dev/null +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Util/ClassFileNameCheckTest.php @@ -0,0 +1,43 @@ +<?php +/** + * Copyright 2024 Adobe + * All Rights Reserved. + */ + +declare(strict_types=1); + +namespace tests\unit\Magento\FunctionalTestFramework\Util; + +use tests\unit\Util\MagentoTestCase; +use Magento\FunctionalTestingFramework\StaticCheck\ClassFileNamingCheck; +use Magento\FunctionalTestingFramework\Util\Script\ScriptUtil; + +class ClassFileNameCheckTest extends MagentoTestCase +{ + /** + * This Test checks if the file name is renamed to match the class name if mismatch found in class and file name + */ + public function testClassAndFileMismatchStaticCheckWhenViolationsFound() + { + $scriptUtil = new ScriptUtil(); + $modulePaths = $scriptUtil->getAllModulePaths(); + $testXmlFiles = $scriptUtil->getModuleXmlFilesByScope($modulePaths, "Test"); + $classFileNameCheck = new ClassFileNamingCheck(); + $result = $classFileNameCheck->findErrorsInFileSet($testXmlFiles, "test"); + $this->assertMatchesRegularExpression('/does not match with file name/', $result[array_keys($result)[0]][0]); + } + + /** + * This Test checks if the file name is renamed to match the class name if + * mismatch not found in class and file name + */ + public function testClassAndFileMismatchStaticCheckWhenViolationsNotFound() + { + $scriptUtil = new ScriptUtil(); + $modulePaths = $scriptUtil->getAllModulePaths(); + $testXmlFiles = $scriptUtil->getModuleXmlFilesByScope($modulePaths, "Page"); + $classFileNameCheck = new ClassFileNamingCheck(); + $result = $classFileNameCheck->findErrorsInFileSet($testXmlFiles, "page"); + $this->assertEquals(count($result), 0); + } +} diff --git a/dev/tests/unit/Magento/FunctionalTestFramework/Util/ComposerModuleResolverTest.php b/dev/tests/unit/Magento/FunctionalTestFramework/Util/ComposerModuleResolverTest.php new file mode 100644 index 000000000..6d948548b --- /dev/null +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Util/ComposerModuleResolverTest.php @@ -0,0 +1,162 @@ +<?php +/** + * Copyright 2019 Adobe + * All Rights Reserved. + */ + +namespace tests\unit\Magento\FunctionalTestFramework\Util; + +use ReflectionClass; +use Magento\FunctionalTestingFramework\Util\ComposerModuleResolver; +use tests\unit\Util\MagentoTestCase; + +class ComposerModuleResolverTest extends MagentoTestCase +{ + /** + * Test getComposerInstalledTestModules() + */ + public function testGetComposerInstalledTestModules() + { + $composerJson = dirname(__DIR__) . DIRECTORY_SEPARATOR . 'Composer' . DIRECTORY_SEPARATOR . '_files' + . DIRECTORY_SEPARATOR . 'dir1' . DIRECTORY_SEPARATOR . 'dir2' . DIRECTORY_SEPARATOR . 'composer.json'; + $expected = [ + 'Magento_ModuleX', + 'Magento_ModuleY', + 'Magento_ModuleZ' + ]; + + $composer = new ComposerModuleResolver(); + $output = $composer->getComposerInstalledTestModules($composerJson); + $this->assertCount(1, $output); + $this->assertEquals($expected, array_pop($output)); + } + + /** + * Test getTestModulesFromPaths() + */ + public function testGetTestModulesFromPaths() + { + $baseDir = dirname(__DIR__) . DIRECTORY_SEPARATOR . 'Composer' . DIRECTORY_SEPARATOR . '_files' + . DIRECTORY_SEPARATOR . 'dir1' . DIRECTORY_SEPARATOR . 'dir2'; + $expected = [ + $baseDir . DIRECTORY_SEPARATOR . 'dir31' . DIRECTORY_SEPARATOR . 'dir41' => [ + 'Magento_ModuleE', + 'Magento_ModuleF' + ], + $baseDir . DIRECTORY_SEPARATOR . 'dir32' . DIRECTORY_SEPARATOR . 'dir41' => [ + 'Magento_ModuleG' + ], + $baseDir . DIRECTORY_SEPARATOR . 'dir32' . DIRECTORY_SEPARATOR . 'dir42' => [ + 'Magento_ModuleH' + ], + ]; + + $composer = new ComposerModuleResolver(); + $output = $composer->getTestModulesFromPaths([$baseDir]); + $this->assertEquals($expected, $output); + } + + /** + * Test findComposerJsonFilesAtDepth() + * + * @dataProvider findComposerJsonFilesAtDepthDataProvider + * @param string $dir + * @param integer $depth + * @param array $expected + * @throws \ReflectionException + */ + public function testFindComposerJsonFilesAtDepth($dir, $depth, $expected) + { + $composer = new ComposerModuleResolver(); + $class = new ReflectionClass($composer); + $method = $class->getMethod('findComposerJsonFilesAtDepth'); + $method->setAccessible(true); + $output = $method->invoke($composer, $dir, $depth); + $this->assertEquals($expected, $output); + } + + /** + * Test findAllComposerJsonFiles() + * + * @dataProvider findAllComposerJsonFilesDataProvider + * @param string $dir + * @param array $expected + * @throws \ReflectionException + */ + public function testFindAllComposerJsonFiles($dir, $expected) + { + $composer = new ComposerModuleResolver(); + $class = new ReflectionClass($composer); + $method = $class->getMethod('findAllComposerJsonFiles'); + $method->setAccessible(true); + $output = $method->invoke($composer, $dir); + $this->assertEquals($expected, $output); + } + + /** + * Data provider for testFindComposerJsonFilesAtDepth() + * + * @return array + */ + public static function findComposerJsonFilesAtDepthDataProvider() + { + $baseDir = dirname(__DIR__) . DIRECTORY_SEPARATOR . 'Composer' . DIRECTORY_SEPARATOR . '_files' + . DIRECTORY_SEPARATOR . 'dir1' . DIRECTORY_SEPARATOR . 'dir2'; + return [ + [ + $baseDir, + 0, + [ + $baseDir . DIRECTORY_SEPARATOR . 'composer.json' + ] + ], + [ + $baseDir, + 1, + [ + $baseDir . DIRECTORY_SEPARATOR . 'dir31' . DIRECTORY_SEPARATOR . 'composer.json', + $baseDir . DIRECTORY_SEPARATOR . 'dir32' . DIRECTORY_SEPARATOR . 'composer.json', + ] + ], + [ + $baseDir, + 2, + [ + $baseDir . DIRECTORY_SEPARATOR . 'dir31' . DIRECTORY_SEPARATOR . 'dir41' . DIRECTORY_SEPARATOR + . 'composer.json', + $baseDir . DIRECTORY_SEPARATOR . 'dir32' . DIRECTORY_SEPARATOR . 'dir41' . DIRECTORY_SEPARATOR + . 'composer.json', + $baseDir . DIRECTORY_SEPARATOR . 'dir32' . DIRECTORY_SEPARATOR . 'dir42' . DIRECTORY_SEPARATOR + . 'composer.json', + ] + ] + ]; + } + + /** + * Data provider for testFindAllComposerJsonFiles() + * + * @return array + */ + public static function findAllComposerJsonFilesDataProvider() + { + $baseDir = dirname(__DIR__) . DIRECTORY_SEPARATOR . 'Composer' . DIRECTORY_SEPARATOR . '_files' + . DIRECTORY_SEPARATOR . 'dir1' . DIRECTORY_SEPARATOR . 'dir2'; + return [ + [ + $baseDir, + [ + $baseDir . DIRECTORY_SEPARATOR . 'dir31' . DIRECTORY_SEPARATOR . 'dir41' . DIRECTORY_SEPARATOR + . 'composer.json', + $baseDir . DIRECTORY_SEPARATOR . 'dir31' . DIRECTORY_SEPARATOR . 'composer.json', + $baseDir . DIRECTORY_SEPARATOR . 'dir32' . DIRECTORY_SEPARATOR . 'dir41' . DIRECTORY_SEPARATOR + . 'composer.json', + $baseDir . DIRECTORY_SEPARATOR . 'dir32' . DIRECTORY_SEPARATOR . 'dir42' . DIRECTORY_SEPARATOR + . 'composer.json', + $baseDir . DIRECTORY_SEPARATOR . 'dir32' . DIRECTORY_SEPARATOR . 'composer.json', + $baseDir . DIRECTORY_SEPARATOR . 'composer.json', + ] + ] + ]; + } +} 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..f607fd896 --- /dev/null +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Util/GenerationErrorHandlerTest.php @@ -0,0 +1,346 @@ +<?php +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ + +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 static 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, null); + } +} diff --git a/dev/tests/unit/Magento/FunctionalTestFramework/Util/ModulePathExtractorTest.php b/dev/tests/unit/Magento/FunctionalTestFramework/Util/ModulePathExtractorTest.php index 5efa6384b..222b30f4c 100644 --- a/dev/tests/unit/Magento/FunctionalTestFramework/Util/ModulePathExtractorTest.php +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Util/ModulePathExtractorTest.php @@ -1,109 +1,170 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2018 Adobe + * All Rights Reserved. */ -namespace tests\unit\Magento\FunctionalTestFramework\Test\Util; +declare(strict_types=1); +namespace tests\unit\Magento\FunctionalTestFramework\Util; + +use Magento\FunctionalTestingFramework\ObjectManager; use Magento\FunctionalTestingFramework\Util\ModulePathExtractor; -use PHPUnit\Framework\TestCase; +use Magento\FunctionalTestingFramework\Util\ModuleResolver; +use ReflectionProperty; +use tests\unit\Util\MagentoTestCase; -class ModulePathExtractorTest extends TestCase +class ModulePathExtractorTest extends MagentoTestCase { - const EXTENSION_PATH = "app" - . DIRECTORY_SEPARATOR - . "code" - . DIRECTORY_SEPARATOR - . "TestExtension" - . DIRECTORY_SEPARATOR - . "[Analytics]" - . DIRECTORY_SEPARATOR - . "Test" - . DIRECTORY_SEPARATOR - . "Mftf" - . DIRECTORY_SEPARATOR - . "Test" - . DIRECTORY_SEPARATOR - . "SomeText.xml"; - - const MAGENTO_PATH = "dev" - . DIRECTORY_SEPARATOR - . "tests" - . DIRECTORY_SEPARATOR - . "acceptance" - . DIRECTORY_SEPARATOR - . "tests" - . DIRECTORY_SEPARATOR - . "functional" - . DIRECTORY_SEPARATOR - . "Magento" - . DIRECTORY_SEPARATOR - . "FunctionalTest" - . DIRECTORY_SEPARATOR - . "[Analytics]" - . DIRECTORY_SEPARATOR - . "Test" - . DIRECTORY_SEPARATOR - . "SomeText.xml"; + /** + * Mock test module paths + * + * @var array + */ + private $mockTestModulePaths = [ + 'Magento_ModuleA' => '/base/path/app/code/Magento/ModuleA/Test/Mftf', + 'VendorB_ModuleB' => '/base/path/app/code/VendorB/ModuleB/Test/Mftf', + 'Magento_ModuleC' => '/base/path/dev/tests/acceptance/tests/functional/Magento/ModuleCTest', + 'VendorD_ModuleD' => '/base/path/dev/tests/acceptance/tests/functional/VendorD/ModuleDTest', + 'SomeModuleE' => '/base/path/dev/tests/acceptance/tests/functional/FunctionalTest/SomeModuleE', + 'Magento_ModuleF' => '/base/path/vendor/magento/module-modulef/Test/Mftf', + 'VendorG_ModuleG' => '/base/path/vendor/vendorg/module-moduleg-test', + ]; /** - * Validate correct module is returned for dev/tests path + * Validate module for app/code path + * * @throws \Exception */ - public function testGetMagentoModule() + public function testGetModuleAppCode() { - $modulePathExtractor = new ModulePathExtractor(); - $this->assertEquals( - '[Analytics]', - $modulePathExtractor->extractModuleName( - self::MAGENTO_PATH - ) - ); + $mockPath = '/base/path/app/code/Magento/ModuleA/Test/Mftf/Test/SomeTest.xml'; + + $this->mockModuleResolver($this->mockTestModulePaths); + $extractor = new ModulePathExtractor(); + $this->assertEquals('ModuleA', $extractor->extractModuleName($mockPath)); } /** - * Validate correct module is returned for extension path + * Validate vendor for app/code path + * * @throws \Exception */ - public function testGetExtensionModule() + public function testGetVendorAppCode() { - $modulePathExtractor = new ModulePathExtractor(); - $this->assertEquals( - '[Analytics]', - $modulePathExtractor->extractModuleName( - self::EXTENSION_PATH - ) - ); + $mockPath = '/base/path/app/code/VendorB/ModuleB/Test/Mftf/Test/SomeTest.xml'; + + $this->mockModuleResolver($this->mockTestModulePaths); + $extractor = new ModulePathExtractor(); + $this->assertEquals('VendorB', $extractor->getExtensionPath($mockPath)); } /** - * Validate Magento is returned for dev/tests/acceptance + * Validate module for dev/tests path + * * @throws \Exception */ - public function testMagentoModulePath() + public function testGetModuleDevTests() { - $modulePathExtractor = new ModulePathExtractor(); - $this->assertEquals( - 'Magento', - $modulePathExtractor->getExtensionPath( - self::MAGENTO_PATH - ) - ); + $mockPath = '/base/path/dev/tests/acceptance/tests/functional/Magento/ModuleCTest/Test/SomeTest.xml'; + + $this->mockModuleResolver($this->mockTestModulePaths); + $extractor = new ModulePathExtractor(); + $this->assertEquals('ModuleC', $extractor->extractModuleName($mockPath)); } /** - * Validate correct extension path is returned + * Validate vendor for dev/tests path + * * @throws \Exception */ - public function testExtensionModulePath() + public function testGetVendorDevTests() { - $modulePathExtractor = new ModulePathExtractor(); - $this->assertEquals( - 'TestExtension', - $modulePathExtractor->getExtensionPath( - self::EXTENSION_PATH - ) - ); + $mockPath = '/base/path/dev/tests/acceptance/tests/functional/VendorD/ModuleDTest/Test/SomeTest.xml'; + + $this->mockModuleResolver($this->mockTestModulePaths); + $extractor = new ModulePathExtractor(); + $this->assertEquals('VendorD', $extractor->getExtensionPath($mockPath)); + } + + /** + * Validate module with no _ + * + * @throws \Exception + */ + public function testGetModule() + { + $mockPath = '/base/path/dev/tests/acceptance/tests/functional/FunctionalTest/SomeModuleE/Test/SomeTest.xml'; + + $this->mockModuleResolver($this->mockTestModulePaths); + $extractor = new ModulePathExtractor(); + $this->assertEquals('NO MODULE DETECTED', $extractor->extractModuleName($mockPath)); + } + + /** + * Validate module for vendor/tests path + * + * @throws \Exception + */ + public function testGetModuleVendorDir() + { + $mockPath = '/base/path/vendor/magento/module-modulef/Test/Mftf/Test/SomeTest.xml'; + + $this->mockModuleResolver($this->mockTestModulePaths); + $extractor = new ModulePathExtractor(); + $this->assertEquals('ModuleF', $extractor->extractModuleName($mockPath)); + } + + /** + * Validate vendor for vendor path + * + * @throws \Exception + */ + public function testGetVendorVendorDir() + { + $mockPath = '/base/path/vendor/vendorg/module-moduleg-test/Test/SomeTest.xml'; + + $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(null, $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 15aa4ad9d..5d14edf2d 100644 --- a/dev/tests/unit/Magento/FunctionalTestFramework/Util/ModuleResolverTest.php +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Util/ModuleResolverTest.php @@ -1,362 +1,961 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2018 Adobe + * All Rights Reserved. */ -namespace tests\unit\Magento\FunctionalTestFramework\Test\Util; +declare(strict_types=1); -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\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, null); + + $mftfAppConfigInstance = new ReflectionProperty(MftfApplicationConfig::class, 'MFTF_APPLICATION_CONTEXT'); + $mftfAppConfigInstance->setAccessible(true); + $mftfAppConfigInstance->setValue(null, 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, ["example" => "example" . DIRECTORY_SEPARATOR . "paths"]); + + $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"]); + $this->setMockResolverProperties($resolver, null, [0 => 'Magento_example', 1 => 'Magento_sample']); $this->assertEquals( [ - "example" . DIRECTORY_SEPARATOR . "paths", - "example" . DIRECTORY_SEPARATOR . "paths", - "example" . DIRECTORY_SEPARATOR . "paths" + 'some' . DIRECTORY_SEPARATOR . 'path' . DIRECTORY_SEPARATOR . 'example', + 'other' . DIRECTORY_SEPARATOR . 'path' . DIRECTORY_SEPARATOR . 'sample' ], $resolver->getModulesPath() ); } /** - * 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 { + $origin = TESTS_MODULE_PATH; + $modulePath = ModuleResolver::DEV_TESTS . DIRECTORY_SEPARATOR . "Magento"; + putenv("TESTS_MODULE_PATH=$modulePath"); + $this->mockForceGenerate(false); - $mockResolver = $this->setMockResolverClass( - true, - [0 => "magento_example"], - null, - null, - ["example" => "example" . DIRECTORY_SEPARATOR . "paths"] + $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, []); + $this->assertEquals([], $resolver->getModulesPath()); + + 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, null); + + $this->mockForceGenerate(false); + // Define the Module paths from default TESTS_MODULE_PATH + $modulePath = defined('TESTS_MODULE_PATH') ? TESTS_MODULE_PATH : TESTS_BP; + $invokedWithParams = $expectedParams = [ + [ + $modulePath, + '' + ], + [ + MAGENTO_BP . '/vendor', + 'Test/Mftf' + ], + [ + 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. + * + * @return void + * @throws Exception + */ + public function testAggregateTestModulePathsFromComposerJson(): void + { + $this->mockForceGenerate(false); + + $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, null); + $this->setMockResolverProperties($resolver, null, [0 => 'Magento_ModuleB', 1 => 'Magento_ModuleC']); $this->assertEquals( [ - "example" . DIRECTORY_SEPARATOR . "paths", - "example" . DIRECTORY_SEPARATOR . "paths", - "example" . DIRECTORY_SEPARATOR . "paths" + 'composer' . DIRECTORY_SEPARATOR . 'json' . DIRECTORY_SEPARATOR . 'pathB' ], $resolver->getModulesPath() ); + } + + /** + * Validate getComposerJsonTestModulePaths with paths invocation. + * + * @return void + * @throws Exception + */ + public function testGetComposerJsonTestModulePathsForPathInvocation(): void + { + $this->mockForceGenerate(false); - // Define the Module paths from app/code - $appCodePath = MAGENTO_BP + // Expected dev tests path + $expectedSearchPaths[] = MAGENTO_BP + . DIRECTORY_SEPARATOR + . 'dev' . DIRECTORY_SEPARATOR - . 'app' . DIRECTORY_SEPARATOR - . 'code' . DIRECTORY_SEPARATOR; + . 'tests' + . DIRECTORY_SEPARATOR + . 'acceptance' + . DIRECTORY_SEPARATOR + . 'tests' + . DIRECTORY_SEPARATOR + . 'functional'; - // Define the Module paths from default TESTS_MODULE_PATH - $modulePath = defined('TESTS_MODULE_PATH') ? TESTS_MODULE_PATH : TESTS_BP; + // Expected test module path + $testModulePath = defined('TESTS_MODULE_PATH') ? TESTS_MODULE_PATH : TESTS_BP; - // Define the Module paths from vendor modules - $vendorCodePath = PROJECT_ROOT - . DIRECTORY_SEPARATOR - . 'vendor' . DIRECTORY_SEPARATOR; + if (array_search($testModulePath, $expectedSearchPaths) === false) { + $expectedSearchPaths[] = $testModulePath; + } - $mockResolver->verifyInvoked('globRelevantPaths', [$modulePath, '']); - $mockResolver->verifyInvoked( - 'globRelevantPaths', - [$appCodePath, DIRECTORY_SEPARATOR . 'Test' . DIRECTORY_SEPARATOR .'Mftf'] + $moduleResolverService = $this->createPartialMock( + ModuleResolverService::class, + ['getComposerJsonTestModulePaths'] ); - $mockResolver->verifyInvoked( - 'globRelevantPaths', - [$vendorCodePath, DIRECTORY_SEPARATOR . 'Test' . DIRECTORY_SEPARATOR .'Mftf'] + $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. + * + * @return void + * @throws Exception + */ + public function testAggregateTestModulePathsFromComposerInstaller(): void + { + $this->mockForceGenerate(false); + $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, + null, + [0 => 'Magento_ModuleA', 1 => 'Magento_ModuleB', 2 => 'Magento_ModuleC'] + ); + $this->assertEquals( + [ + 'composer' . DIRECTORY_SEPARATOR . 'install' . DIRECTORY_SEPARATOR . 'pathA', + 'composer' . DIRECTORY_SEPARATOR . 'install' . DIRECTORY_SEPARATOR . 'pathB' + ], + $resolver->getModulesPath() ); } /** - * Validate custom modules are added - * @throws \Exception + * Validate getComposerInstalledTestModulePaths with paths invocation. + * + * @return void + * @throws Exception */ - public function testGetCustomModulePath() + public function testGetComposerInstalledTestModulePathsForPathInvocation(): void { - $this->setMockResolverClass(false, ["Magento_TestModule"], null, null, [], ['otherPath']); + $this->mockForceGenerate(false); + + // Expected file path + $expectedSearchPath = MAGENTO_BP . DIRECTORY_SEPARATOR . 'composer.json'; + $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, null, null); - $this->assertEquals(['otherPath'], $resolver->getModulesPath()); - TestLoggingUtil::getInstance()->validateMockLogStatement( - 'info', - 'including custom module', - ['module' => 'otherPath'] + $this->setMockResolverProperties($resolver, null, []); + $this->assertEquals([], $resolver->getModulesPath()); + } + + /** + * Validate mergeModulePaths() and flipAndFilterModulePathsArray(). + * + * @return void + * @throws Exception + */ + public function testMergeFlipAndFilterModulePathsNoForceGenerate(): void + { + $this->mockForceGenerate(false); + $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, + null, + [ + 0 => 'Magento_Path1', + 1 => 'Magento_Path2', + 2 => 'Magento_Path4', + 3 => 'Magento_Example', + 4 => 'Magento_ModuleB', + 5 => 'Magento_ModuleD', + 6 => 'Magento_Otherexample', + 7 => 'Magento_ModuleC' + ] + ); + $this->assertEquals( + [ + 'some' . DIRECTORY_SEPARATOR . 'path' . DIRECTORY_SEPARATOR . 'path1', + 'other' . DIRECTORY_SEPARATOR . 'path' . DIRECTORY_SEPARATOR . 'path2', + 'other' . DIRECTORY_SEPARATOR . 'path' . DIRECTORY_SEPARATOR . 'path4', + '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' + + ], + $resolver->getModulesPath() ); } /** - * Validate blacklisted modules are removed - * @throws \Exception + * Validate mergeModulePaths() and flipAndSortModulePathsArray(). + * + * @return void + * @throws Exception */ - public function testGetModulePathsBlacklist() + public function testMergeFlipNoSortModulePathsNoForceGenerate(): void { - $this->setMockResolverClass( - false, + $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, + [ + 0 => 'Magento_ModuleB', + 1 => 'Magento_ModuleC', + 2 => 'Magento_ModuleE', + 3 => 'Magento_Example', + 4 => 'Magento_Otherexample' + ] + ); + + $this->assertEquals( + [ + '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' + ], + $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, null, + [ + 0 => 'Magento_ModuleB', + 1 => 'Magento_ModuleC', + 2 => 'Magento_ModuleD', + 3 => 'Magento_Example', + 4 => 'Magento_Otherexample' + ] + ); + + $this->assertEquals( + [ + 'some' . DIRECTORY_SEPARATOR . 'path' . DIRECTORY_SEPARATOR . 'example', + '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(). + * + * @return void + * @throws Exception + */ + public function testMergeFlipAndFilterModulePathsWithLogging(): void + { + $this->mockForceGenerate(false); + $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, null, - function ($arg1, $arg2) { - if ($arg2 === "") { - $mockValue = ["somePath" => "somePath"]; - } elseif (strpos($arg1, "app")) { - $mockValue = ["otherPath" => "otherPath"]; - } else { - $mockValue = ["lastPath" => "lastPath"]; - } - return $mockValue; - } + [ + 0 => 'Magento_ModuleA', + 1 => 'Magento_ModuleB', + 2 => 'Magento_ModuleC' + ] ); + $this->assertEquals( + [ + 'composer' . DIRECTORY_SEPARATOR . 'json' . DIRECTORY_SEPARATOR . 'pathA', + 'composer' . DIRECTORY_SEPARATOR . 'json' . DIRECTORY_SEPARATOR . 'pathB', + 'composer' . DIRECTORY_SEPARATOR . 'install' . DIRECTORY_SEPARATOR . 'pathB' + ], + $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, []); + } + + /** + * Validate custom modules are added. + * + * @return void + * @throws Exception + */ + public function testApplyCustomModuleMethods(): void + { + $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, ["somePath"]); - $this->assertEquals(["otherPath", "lastPath"], $resolver->getModulesPath()); + $this->setMockResolverProperties($resolver); + $this->assertEquals(['otherPath'], $resolver->getModulesPath()); + TestLoggingUtil::getInstance()->validateMockLogStatement( + 'info', + 'including custom module', + [ 'Magento_Module' => 'otherPath'] + ); + } + + /** + * Validate blocklisted modules are removed + * Module paths are sorted according to module name in alphabetically ascending order + * + * @throws Exception + */ + public function testGetModulePathsBlocklist(): void + { + $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( + ['vendor/amazon/path1', 'appCode/Magento/path2', 'thisPath/some/path4'], + $resolver->getModulesPath() + ); TestLoggingUtil::getInstance()->validateMockLogStatement( 'info', 'excluding module', - ['module' => 'somePath'] + ['module' => 'Magento_Module3'] ); } /** - * 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"], []); $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 string $mockToken - * @param array $mockGetModules - * @param string[] $mockCustomMethods - * @param string[] $mockGlob - * @param string[] $mockRelativePaths - * @param string[] $mockCustomModules - * @throws \Exception - * @return Verifier ModuleResolver double + * @param ModuleResolver $instance + * @param null $mockPaths + * @param null $mockModules + * @param array $mockBlockList + * + * @return void */ - private function setMockResolverClass( - $mockToken = null, - $mockGetModules = null, - $mockCustomMethods = null, - $mockGlob = null, - $mockRelativePaths = null, - $mockCustomModules = 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($mockGetModules)) { - $mockMethods['getEnabledModules'] = $mockGetModules; - } - if (isset($mockCustomMethods)) { - $mockMethods['applyCustomModuleMethods'] = $mockCustomMethods; - } - if (isset($mockGlob)) { - $mockMethods['globRelevantWrapper'] = $mockGlob; - } - if (isset($mockRelativePaths)) { - $mockMethods['globRelevantPaths'] = $mockRelativePaths; - } - if (isset($mockCustomModules)) { - $mockMethods['getCustomModulePaths'] = $mockCustomModules; - } -// $mockMethods['printMagentoVersionInfo'] = null; + $property = new ReflectionProperty(ModuleResolver::class, 'enabledModules'); + $property->setAccessible(true); + $property->setValue($instance, $mockModules); - $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, '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(null, $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(null, $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..6747205be --- /dev/null +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Util/Path/FilePathFormatterTest.php @@ -0,0 +1,103 @@ +<?php +/** + * Copyright 2019 Adobe + * All Rights Reserved. + */ + +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 static 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 static 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..7b9469d23 --- /dev/null +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Util/Path/UrlFormatterTest.php @@ -0,0 +1,107 @@ +<?php +/** + * Copyright 2019 Adobe + * All Rights Reserved. + */ + +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 static 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 static 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 c4a04d361..706cc46ae 100644 --- a/dev/tests/unit/Magento/FunctionalTestFramework/Util/Sorter/ParallelGroupSorterTest.php +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Util/Sorter/ParallelGroupSorterTest.php @@ -1,24 +1,29 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2018 Adobe + * All Rights Reserved. */ -namespace Tests\unit\Magento\FunctionalTestFramework\Util\Sorter; -use AspectMock\Test as AspectMock; -use Magento\FunctionalTestingFramework\Suite\Handlers\SuiteObjectHandler; -use Magento\FunctionalTestingFramework\Suite\Objects\SuiteObject; +declare(strict_types=1); + +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 +48,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 = [ @@ -98,17 +86,449 @@ public function testSortWithSuites() $actualResult = $testSorter->getTestsGroupedBySize($sampleSuiteArray, $sampleTestArray, 500); // verify the resulting groups - $this->assertCount(4, $actualResult); + $this->assertCount(5, $actualResult); $expectedResults = [ - 1 => ['test3'], - 2 => ['test2','test5', 'test4'], - 3 => ['mockSuite1_0', 'test1'], - 4 => ['mockSuite1_1'] + 1 => ['mockSuite1_0_G'], + 2 => ['mockSuite1_1_G'], + 3 => ['test3'], + 4 => ['test2','test5', 'test4'], + 5 => ['test1'], ]; foreach ($actualResult as $groupNum => $group) { $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 when none of the suites has test. + * For example, this can happen when --filter option is used. + * + * @return void + * @throws FastFailException + */ + public function testTestsAndSuitesSplitByGroupNumberSuiteNoTest(): 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' => [], + 'mockSuite2' => [], + ]; + + // perform sort + $testSorter = new ParallelGroupSorter(); + $actualResult = $testSorter->getTestsGroupedByFixedGroupCount($sampleSuiteArray, $sampleTestArray, 3); + // verify the resulting groups + $this->assertCount(3, $actualResult); + + $expectedResults = [ + 1 => ['test2'], + 2 => ['test3'], + 3 => ['test1'] + ]; + + foreach ($actualResult as $groupNum => $group) { + $this->assertEquals($expectedResults[$groupNum], array_keys($group)); + } + } + + /** + * Test splitting into minimum groups. + * + * @return void + */ + public function testSplitMinGroups(): 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'], + 'mockSuite2' => ['mockTest3', 'mockTest4'], + ]; + + // 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'], + 3 => ['mockSuite2'], + ]; + + 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'], + 'mockSuite2' => ['mockTest3', 'mockTest4'], + ]; + + $this->expectException(FastFailException::class); + $this->expectExceptionMessage("Invalid parameter 'groupTotal': must be equal or greater than 3"); + + // perform sort + $testSorter = new ParallelGroupSorter(); + $testSorter->getTestsGroupedByFixedGroupCount($sampleSuiteArray, $sampleTestArray, 2); + } + + /** + * @inheritDoc + */ + public static function tearDownAfterClass(): void + { + $instanceProperty = new ReflectionProperty(TestObjectHandler::class, 'testObjectHandler'); + $instanceProperty->setAccessible(true); + $instanceProperty->setValue(null, 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 new file mode 100644 index 000000000..207624720 --- /dev/null +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Util/TestGeneratorTest.php @@ -0,0 +1,567 @@ +<?php +/** + * Copyright 2018 Adobe + * All Rights Reserved. + */ + +declare(strict_types=1); + +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\Filesystem\CestFileCreatorUtil; +use Magento\FunctionalTestingFramework\Util\GenerationErrorHandler; +use Magento\FunctionalTestingFramework\Util\ModuleResolver; +use Magento\FunctionalTestingFramework\Util\TestGenerator; +use ReflectionClass; +use ReflectionProperty; +use tests\unit\Util\MagentoTestCase; +use tests\unit\Util\TestLoggingUtil; +use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; + +class TestGeneratorTest extends MagentoTestCase +{ + /** + * @inheritdoc + */ + public static function setUpBeforeClass(): void + { + parent::setUpBeforeClass(); + + $property = new ReflectionProperty(ObjectManager::class, 'instance'); + $property->setAccessible(true); + $property->setValue(null, null); + + $property = new ReflectionProperty(ModuleResolver::class, 'instance'); + $property->setAccessible(true); + $property->setValue(null, 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. + * + * @return void + * @throws Exception + */ + public function testEntityException(): void + { + $actionObject = new ActionObject('fakeAction', 'comment', [ + 'userInput' => '{{someEntity.entity}}' + ]); + + $testObject = new TestObject('sampleTest', ['merge123' => $actionObject], [], [], 'filename'); + $testGeneratorObject = TestGenerator::getInstance('', ['sampleTest' => $testObject]); + $this->mockTestObjectHandler(); + + $testGeneratorObject->createAllTestFiles(null, []); + + // 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); + } + + /** + * 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}}' + ]); + + $testObject = new TestObject('sampleTest', ['merge123' => $actionObject], [], [], 'filename'); + $testGeneratorObject = TestGenerator::getInstance('', ['sampleTest' => $testObject]); + + $result = $testGeneratorObject->getUniqueIdForInput('prefix', "foo"); + + $this->assertMatchesRegularExpression('/[A-Za-z0-9]+foo/', $result); + } + + /** + * Basic test to check if exception is thrown when invalid entity is found in xml file + * + * @return void + * @throws Exception + */ + 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', [ + 'userInput' => $actionInput + ]); + + $annotations = ['skip' => ['issue']]; + $testObject = new TestObject('sampleTest', ['merge123' => $actionObject], $annotations, [], 'filename'); + + $testGeneratorObject = TestGenerator::getInstance('', ['sampleTest' => $testObject]); + $output = $testGeneratorObject->assembleTestPhp($testObject); + + $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. + * + * @return void + * @throws TestReferenceException + */ + public function testAllowSkipped(): void + { + // Mock allowSkipped for TestGenerator + $mockConfig = $this->createMock(MftfApplicationConfig::class); + $mockConfig + ->method('allowSkipped') + ->willReturn(true); + + $property = new ReflectionProperty(MftfApplicationConfig::class, 'MFTF_APPLICATION_CONTEXT'); + $property->setAccessible(true); + $property->setValue(null, $mockConfig); + + $actionInput = 'fakeInput'; + $actionObject = new ActionObject('fakeAction', 'comment', [ + 'userInput' => $actionInput + ]); + $beforeActionInput = 'beforeInput'; + $beforeActionObject = new ActionObject('beforeAction', 'comment', [ + 'userInput' => $beforeActionInput + ]); + + $annotations = ['skip' => ['issue']]; + $beforeHook = new TestHookObject('before', 'sampleTest', ['beforeAction' => $beforeActionObject]); + $testObject = new TestObject( + 'sampleTest', + ['fakeAction' => $actionObject], + $annotations, + ['before' => $beforeHook], + 'filename' + ); + + $testGeneratorObject = TestGenerator::getInstance('', ['sampleTest' => $testObject]); + $output = $testGeneratorObject->assembleTestPhp($testObject); + + $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(null, $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(null, $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); + } + + /** + * Test for exception thrown when duplicate arguments found + * + * @return void + * @throws TestFrameworkException + */ + public function testIfExceptionThrownWhenDuplicateArgumentsFound() + { + $fileContents = '<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"/> + <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>'; + $actionInput = 'fakeInput'; + $actionObject = new ActionObject('fakeAction', 'comment', [ + 'userInput' => $actionInput + ]); + $annotation1 = ['group' => ['someGroupValue']]; + + $test1 = new TestObject( + 'test1', + ['fakeAction' => $actionObject], + $annotation1, + [], + 'filename' + ); + $annotation2 = ['group' => ['someOtherGroupValue']]; + + $test2 = new TestObject( + 'test2', + ['fakeAction' => $actionObject], + $annotation2, + [], + 'filename' + ); + $testGeneratorObject = TestGenerator::getInstance('', ['sampleTest' => $test1, 'test2' => $test2]); + $result = $testGeneratorObject->throwExceptionIfDuplicateArgumentsFound($testGeneratorObject); + $this->assertEquals($result, ""); + } + + /** + * Test for exception not thrown when duplicate arguments not found + * + * @return void + */ + public function testIfExceptionNotThrownWhenDuplicateArgumentsNotFound() + { + $fileContents = '<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>'; + $actionInput = 'fakeInput'; + $actionObject = new ActionObject('fakeAction', 'comment', [ + 'userInput' => $actionInput + ]); + $annotation1 = ['group' => ['someGroupValue']]; + + $test1 = new TestObject( + 'test1', + ['fakeAction' => $actionObject], + $annotation1, + [], + 'filename' + ); + $annotation2 = ['group' => ['someOtherGroupValue']]; + + $test2 = new TestObject( + 'test2', + ['fakeAction' => $actionObject], + $annotation2, + [], + 'filename' + ); + $testGeneratorObject = TestGenerator::getInstance('', ['sampleTest' => $test1, 'test2' => $test2]); + $result = $testGeneratorObject->throwExceptionIfDuplicateArgumentsFound($testGeneratorObject); + $this->assertEquals($result, ""); + } + + /** + * 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(null, $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(null, $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(null, $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(null, $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, null); + + $mftfAppConfigInstance = new ReflectionProperty(MftfApplicationConfig::class, 'MFTF_APPLICATION_CONTEXT'); + $mftfAppConfigInstance->setAccessible(true); + $mftfAppConfigInstance->setValue(null, null); + + $property = new ReflectionProperty(TestObjectHandler::class, 'testObjectHandler'); + $property->setAccessible(true); + $property->setValue(null, 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(null, $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..a7ae56b41 --- /dev/null +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Util/UnusedEntityCheckTest.php @@ -0,0 +1,191 @@ +<?php +/** + * Copyright 2022 Adobe + * All Rights Reserved. + */ + +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() + { + $scriptUtil = new ScriptUtil(); + $unusedEntityCheck = new UnusedEntityCheck(); + $domDocument = new \DOMDocument(); + $modulePaths = $scriptUtil->getAllModulePaths(); + $actionGroupXmlFiles = $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(); + $scriptUtil = new ScriptUtil(); + $modulePaths = $scriptUtil->getAllModulePaths(); + $sectionXmlFiles = $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(); + $scriptUtil = new ScriptUtil(); + $modulePaths = $scriptUtil->getAllModulePaths(); + $pageXmlFiles = $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(); + $scriptUtil = new ScriptUtil(); + $modulePaths = $scriptUtil->getAllModulePaths(); + $dataXmlFiles = $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..f47ca29b8 100644 --- a/dev/tests/unit/Magento/FunctionalTestFramework/Util/Validation/DuplicateNodeValidationUtilTest.php +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Util/Validation/DuplicateNodeValidationUtilTest.php @@ -1,16 +1,14 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2018 Adobe + * All Rights Reserved. */ -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..6043fa741 100644 --- a/dev/tests/unit/Magento/FunctionalTestFramework/Util/Validation/NameValidationUtilTest.php +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Util/Validation/NameValidationUtilTest.php @@ -1,14 +1,14 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2018 Adobe + * All Rights Reserved. */ -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/Resources/alteredDocumentation.txt b/dev/tests/unit/Resources/alteredDocumentation.txt new file mode 100644 index 000000000..228683db4 --- /dev/null +++ b/dev/tests/unit/Resources/alteredDocumentation.txt @@ -0,0 +1,12 @@ +#Action Group Information +This documentation contains a list of all Action Groups. + +--- +###testActionGroupObject +**Description**: +- alteredDescription + +**Located In**: + +- filename +*** diff --git a/dev/tests/unit/Resources/basicDocumentation.txt b/dev/tests/unit/Resources/basicDocumentation.txt new file mode 100644 index 000000000..cdc67a491 --- /dev/null +++ b/dev/tests/unit/Resources/basicDocumentation.txt @@ -0,0 +1,12 @@ +#Action Group Information +This documentation contains a list of all Action Groups. + +--- +###testActionGroupObject +**Description**: +- someDescription + +**Located In**: + +- filename +*** diff --git a/dev/tests/unit/Util/ActionGroupArrayBuilder.php b/dev/tests/unit/Util/ActionGroupArrayBuilder.php new file mode 100644 index 000000000..8a6f0d64a --- /dev/null +++ b/dev/tests/unit/Util/ActionGroupArrayBuilder.php @@ -0,0 +1,170 @@ +<?php +/** + * Copyright 2019 Adobe + * All Rights Reserved. + */ + +namespace tests\unit\Util; + +use Magento\FunctionalTestingFramework\Test\Util\ActionGroupObjectExtractor; +use Magento\FunctionalTestingFramework\Test\Util\ActionObjectExtractor; + +class ActionGroupArrayBuilder +{ + const DEFAULT_ACTION_GROUP_KEY = 'actionGroupStepKey'; + + /** + * Action group name + * + * @var string + */ + private $name = "testActionGroup"; + + /** + * Action group actions (default value set by constructor) + * + * @var array + */ + private $actionObjects = []; + + /** + * Action group annotations + * + * @var array + */ + private $annotations = []; + + /** + * Action group arguments + * + * @var array + */ + private $arguments = []; + + /** + * Action group extends name + * + * @var string + */ + private $extends = null; + + /** + * Action group filename + * + * @var string + */ + private $filename = ''; + + /** + * Setter for action group name + * + * @param string $name + * @return $this + */ + public function withName($name) + { + $this->name = $name; + return $this; + } + + /** + * Setter for action group annotations + * + * @param array $annotations + * @return $this + */ + public function withAnnotations($annotations = []) + { + $this->annotations = $annotations; + return $this; + } + + /** + * Setter for action group arguments + * + * @param array $args + * @return $this + */ + public function withArguments($args = []) + { + $this->arguments = $args; + return $this; + } + + /** + * Setter for action group actions + * + * @param array $actionObjs + * @return $this + */ + public function withActionObjects($actionObjs = []) + { + if (!empty($actionObjs)) { + $this->actionObjects = $actionObjs; + } + return $this; + } + + /** + * Setter for action group extended action group name + * + * @param string $extendedActionGroup + * @return $this + */ + public function withExtendedAction(?string $extendedActionGroup = null) + { + $this->extends = $extendedActionGroup; + return $this; + } + + /** + * Setter for action group filename + * + * @param string $filename + * @return $this + */ + public function withFilename($filename = '') + { + if (empty($filename)) { + $this->filename = "/magento2-functional-testing-framework/dev/tests/verification/" + . "TestModule/ActionGroup/BasicActionGroup.xml"; + } else { + $this->filename = $filename; + } + + return $this; + } + + /** + * ActionGroupArrayBuilder constructor + */ + public function __construct() + { + $this->actionObjects = [ + self::DEFAULT_ACTION_GROUP_KEY => [ + ActionObjectExtractor::NODE_NAME => 'testActionType', + ActionObjectExtractor::TEST_STEP_MERGE_KEY => self::DEFAULT_ACTION_GROUP_KEY, + ] + ]; + } + + /** + * Function which takes builder parameters and returns an action group array + * + * @return array + */ + public function build() + { + // Build and return action group array + return [$this->name => array_merge( + [ + ActionGroupObjectExtractor::NAME => $this->name, + ActionGroupObjectExtractor::ACTION_GROUP_ANNOTATIONS => $this->annotations, + ActionGroupObjectExtractor::ACTION_GROUP_ARGUMENTS => $this->arguments, + ActionGroupObjectExtractor::FILENAME => $this->filename, + ActionGroupObjectExtractor::EXTENDS_ACTION_GROUP => $this->extends + ], + $this->actionObjects + )]; + } +} diff --git a/dev/tests/unit/Util/ActionGroupObjectBuilder.php b/dev/tests/unit/Util/ActionGroupObjectBuilder.php index f97d0465d..e9a4da703 100644 --- a/dev/tests/unit/Util/ActionGroupObjectBuilder.php +++ b/dev/tests/unit/Util/ActionGroupObjectBuilder.php @@ -1,7 +1,7 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ namespace tests\unit\Util; @@ -27,6 +27,13 @@ class ActionGroupObjectBuilder */ private $actionObjects = []; + /** + * Action Group Object Builder default entity annotations. + * + * @var array + */ + private $annotations = []; + /** * Action Group Object Builder default entity arguments. * @@ -41,6 +48,13 @@ class ActionGroupObjectBuilder */ private $extends = null; + /** + * Action Group Object Builder default filenames + * + * @var string + */ + private $filename = []; + /** * Setter for the Action Group Object name * @@ -53,6 +67,18 @@ public function withName($name) return $this; } + /** + * Setter for the Action Group Object annotations + * + * @param array $annotations + * @return ActionGroupObjectBuilder + */ + public function withAnnotations($annotations) + { + $this->annotations = $annotations; + return $this; + } + /** * Setter for the Action Group Object arguments * @@ -89,6 +115,18 @@ public function withExtendedAction($extendedActionGroup) return $this; } + /** + * Setter for the Action Group Object filename + * + * @param string $filename + * @return ActionGroupObjectBuilder + */ + public function withFilename($filename) + { + $this->filename = $filename; + return $this; + } + /** * ActionGroupObjectBuilder constructor. */ @@ -108,9 +146,11 @@ public function build() { return new ActionGroupObject( $this->name, + $this->annotations, $this->arguments, $this->actionObjects, - $this->extends + $this->extends, + $this->filename ); } } diff --git a/dev/tests/unit/Util/EntityDataObjectBuilder.php b/dev/tests/unit/Util/EntityDataObjectBuilder.php index dfe7d6836..98ca54856 100644 --- a/dev/tests/unit/Util/EntityDataObjectBuilder.php +++ b/dev/tests/unit/Util/EntityDataObjectBuilder.php @@ -1,8 +1,9 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ + namespace tests\unit\Util; use Magento\FunctionalTestingFramework\DataGenerator\Objects\EntityDataObject; @@ -18,7 +19,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 0aa153106..87398c922 100644 --- a/dev/tests/unit/Util/MagentoTestCase.php +++ b/dev/tests/unit/Util/MagentoTestCase.php @@ -1,12 +1,13 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2018 Adobe + * All Rights Reserved. */ -namespace Magento\FunctionalTestingFramework\Util; +declare(strict_types=1); + +namespace tests\unit\Util; -use AspectMock\Test as AspectMock; use PHPUnit\Framework\TestCase; /** @@ -15,11 +16,26 @@ class MagentoTestCase extends TestCase { /** - * Teardown for removing AspectMock Double References - * @return void + * @inheritDoc + */ + public static function setUpBeforeClass(): void + { + if (!self::fileExists(DOCS_OUTPUT_DIR)) { + mkdir(DOCS_OUTPUT_DIR, 0755, true); + } + + parent::setUpBeforeClass(); + } + + /** + * @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/OperationDefinitionBuilder.php b/dev/tests/unit/Util/OperationDefinitionBuilder.php index 63b0fbbfc..fccae5103 100644 --- a/dev/tests/unit/Util/OperationDefinitionBuilder.php +++ b/dev/tests/unit/Util/OperationDefinitionBuilder.php @@ -1,8 +1,9 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ + namespace tests\unit\Util; use Magento\FunctionalTestingFramework\DataGenerator\Objects\OperationDefinitionObject; diff --git a/dev/tests/unit/Util/OperationElementBuilder.php b/dev/tests/unit/Util/OperationElementBuilder.php index 2c2a4d9da..fee385eb0 100644 --- a/dev/tests/unit/Util/OperationElementBuilder.php +++ b/dev/tests/unit/Util/OperationElementBuilder.php @@ -1,8 +1,9 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ + namespace tests\unit\Util; use Magento\FunctionalTestingFramework\DataGenerator\Handlers\OperationDefinitionObjectHandler; @@ -20,7 +21,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..76fa48473 100644 --- a/dev/tests/unit/Util/SuiteDataArrayBuilder.php +++ b/dev/tests/unit/Util/SuiteDataArrayBuilder.php @@ -1,8 +1,9 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2018 Adobe + * All Rights Reserved. */ + namespace tests\unit\Util; use Magento\FunctionalTestingFramework\Suite\Util\SuiteObjectExtractor; @@ -179,9 +180,9 @@ private function appendEntriesToSuiteContents($currentContents, $type, $contents * @param null $afterHook * @return $this */ - public function withAfterHook($afterHook = null) + public function withAfterHook(?array $afterHook = null) { - if ($afterHook == null) { + if ($afterHook === null) { $this->afterHook = [$this->testActionAfterName => [ ActionObjectExtractor::NODE_NAME => $this->testActionType, ActionObjectExtractor::TEST_STEP_MERGE_KEY => $this->testActionAfterName @@ -200,9 +201,9 @@ public function withAfterHook($afterHook = null) * @param null $beforeHook * @return $this */ - public function withBeforeHook($beforeHook = null) + public function withBeforeHook(?array $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 aeeeae850..25f4d64a8 100644 --- a/dev/tests/unit/Util/TestDataArrayBuilder.php +++ b/dev/tests/unit/Util/TestDataArrayBuilder.php @@ -1,7 +1,7 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2018 Adobe + * All Rights Reserved. */ namespace tests\unit\Util; @@ -107,9 +107,9 @@ public function withName($name) * @param array $annotations * @return $this */ - public function withAnnotations($annotations = null) + public function withAnnotations(?array $annotations = null) { - if ($annotations == null) { + if ($annotations === null) { $this->annotations = ['group' => [['value' => 'test']]]; } else { $this->annotations = $annotations; @@ -124,9 +124,9 @@ public function withAnnotations($annotations = null) * @param null $beforeHook * @return $this */ - public function withBeforeHook($beforeHook = null) + public function withBeforeHook(?array $beforeHook = null) { - if ($beforeHook == null) { + if ($beforeHook === null) { $this->beforeHook = [$this->testActionBeforeName => [ ActionObjectExtractor::NODE_NAME => $this->testActionType, ActionObjectExtractor::TEST_STEP_MERGE_KEY => $this->testActionBeforeName @@ -144,9 +144,9 @@ public function withBeforeHook($beforeHook = null) * @param null $afterHook * @return $this */ - public function withAfterHook($afterHook = null) + public function withAfterHook(?array $afterHook = null) { - if ($afterHook == null) { + if ($afterHook === null) { $this->afterHook = [$this->testActionAfterName => [ ActionObjectExtractor::NODE_NAME => $this->testActionType, ActionObjectExtractor::TEST_STEP_MERGE_KEY => $this->testActionAfterName @@ -165,9 +165,9 @@ public function withAfterHook($afterHook = null) * @param null $failedHook * @return $this */ - public function withFailedHook($failedHook = null) + public function withFailedHook(?array $failedHook = null) { - if ($failedHook == null) { + if ($failedHook === null) { $this->failedHook = [$this->testActionFailedName => [ ActionObjectExtractor::NODE_NAME => $this->testActionType, ActionObjectExtractor::TEST_STEP_MERGE_KEY => $this->testActionFailedName @@ -186,9 +186,9 @@ public function withFailedHook($failedHook = null) * @param array $actions * @return $this */ - public function withTestActions($actions = null) + public function withTestActions(?array $actions = null) { - if ($actions == null) { + if ($actions === null) { $this->testActions = [$this->testTestActionName => [ ActionObjectExtractor::NODE_NAME => $this->testActionType, ActionObjectExtractor::TEST_STEP_MERGE_KEY => $this->testTestActionName @@ -205,9 +205,9 @@ public function withTestActions($actions = null) * @param string $filename * @return $this */ - public function withFileName($filename = null) + public function withFileName(?string $filename = null) { - if ($filename == null) { + if ($filename === null) { $this->filename = "/magento2-functional-testing-framework/dev/tests/verification/TestModule/Test/BasicFunctionalTest.xml"; } else { @@ -223,15 +223,37 @@ public function withFileName($filename = null) * @param string $reference * @return $this */ - public function withTestReference($reference = null) + public function withTestReference(?string $reference = null) { - if ($reference != null) { + if ($reference !== null) { $this->testReference = $reference; } 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 655048552..de21e4260 100644 --- a/dev/tests/unit/Util/TestLoggingUtil.php +++ b/dev/tests/unit/Util/TestLoggingUtil.php @@ -1,24 +1,26 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2018 Adobe + * All Rights Reserved. */ +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 Monolog\Logger; -use PHPUnit\Framework\Assert; +use PHPUnit\Framework\TestCase; +use ReflectionProperty; +use ReflectionClass; -class TestLoggingUtil extends Assert +class TestLoggingUtil extends TestCase { /** * @var TestLoggingUtil */ - private static $INSTANCE; + private static $instance; /** * @var TestHandler @@ -26,25 +28,24 @@ class TestLoggingUtil extends Assert private $testLogHandler; /** - * TestLoggingUtil constructor. + * Private constructor. */ private function __construct() { - // private constructor + parent::__construct('', [], ''); } /** - * Static singleton get function + * Static singleton get function. * * @return TestLoggingUtil */ - public static function getInstance() + public static function getInstance(): TestLoggingUtil { - if (self::$INSTANCE == null) { - self::$INSTANCE = new TestLoggingUtil(); + if (self::$instance === null) { + self::$instance = new TestLoggingUtil(); } - - return self::$INSTANCE; + return self::$instance; } /** @@ -52,18 +53,31 @@ 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); + $property->setValue(null, $mockLoggingUtil); + } + + /** + * Check if mock log is empty. + * + * @return void + */ + public function validateMockLogEmpty(): void + { + $records = $this->testLogHandler->getRecords(); + $this->assertTrue(empty($records)); } /** @@ -72,9 +86,10 @@ public function setMockLoggingUtil() * @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 @@ -83,12 +98,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']); } @@ -98,8 +122,9 @@ public function validateMockLogStatmentRegex($type, $regex, $context) * * @return void */ - public function clearMockLoggingUtil() + public function clearMockLoggingUtil(): void { - AspectMock::clean(LoggingUtil::class); + $reflectionClass = new ReflectionClass(LoggingUtil::class); + $reflectionClass->setStaticPropertyValue('instance', null); } } diff --git a/dev/tests/util/MftfStaticTestCase.php b/dev/tests/util/MftfStaticTestCase.php new file mode 100644 index 000000000..f4778a5f8 --- /dev/null +++ b/dev/tests/util/MftfStaticTestCase.php @@ -0,0 +1,49 @@ +<?php +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ + +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..1d4cb084a 100644 --- a/dev/tests/util/MftfTestCase.php +++ b/dev/tests/util/MftfTestCase.php @@ -1,14 +1,19 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2018 Adobe + * All Rights Reserved. */ + 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\Logger\LoggingUtil; use Magento\FunctionalTestingFramework\Util\TestGenerator; use PHPUnit\Framework\TestCase; +use ReflectionClass; abstract class MftfTestCase extends TestCase { @@ -80,6 +85,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 * @@ -88,13 +115,19 @@ public function validateSchemaErrorWithTest($fileContents, $objectType ,$expecte private function clearHandler() { // clear test object handler to force recollection of test data - $property = new \ReflectionProperty(TestObjectHandler::class, 'testObjectHandler'); - $property->setAccessible(true); - $property->setValue(null); + $reflectionClass = new ReflectionClass(TestObjectHandler::class); + $reflectionClass->setStaticPropertyValue('testObjectHandler', null); // clear test object handler to force recollection of test data - $property = new \ReflectionProperty(ObjectManager::class, 'instance'); - $property->setAccessible(true); - $property->setValue(null); + $reflectionClass = new ReflectionClass(ObjectManager::class); + $reflectionClass->setStaticPropertyValue('instance', null); + + // clear suite generator to force recollection of test data + $reflectionClass = new ReflectionClass(SuiteGenerator::class); + $reflectionClass->setStaticPropertyValue('instance', null); + + // clear suite object handler to force recollection of test data + $reflectionClass = new ReflectionClass(SuiteObjectHandler::class); + $reflectionClass->setStaticPropertyValue('instance', 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..0e262746c --- /dev/null +++ b/dev/tests/verification/DeprecationCheckModule/ActionGroup/DeprecationCheckActionGroup.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..8e1a9bfc2 --- /dev/null +++ b/dev/tests/verification/DeprecationCheckModule/ActionGroup/DeprecationCheckDeprecatedActionGroup.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..ed5f6e7ca --- /dev/null +++ b/dev/tests/verification/DeprecationCheckModule/Data/DeprecationCheckData.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> + +<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..eecf9a5c3 --- /dev/null +++ b/dev/tests/verification/DeprecationCheckModule/Metadata/DeprecationCheckMeta.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> + +<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..1c307d31e --- /dev/null +++ b/dev/tests/verification/DeprecationCheckModule/Page/DeprecationCheckPage.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> + +<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..0b8e58eda --- /dev/null +++ b/dev/tests/verification/DeprecationCheckModule/Section/DeprecationCheckSection.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> + +<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..23e3a7032 --- /dev/null +++ b/dev/tests/verification/DeprecationCheckModule/Suite/deprecationCheckSuite.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> + +<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..f3ce2b6ca --- /dev/null +++ b/dev/tests/verification/DeprecationCheckModule/Test/DeprecationCheckDeprecatedTest.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> + +<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..c28eca2fc --- /dev/null +++ b/dev/tests/verification/DeprecationCheckModule/Test/DeprecationCheckTest.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> + +<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..3d72beaef --- /dev/null +++ b/dev/tests/verification/PauseCheckModule/ActionGroup/ActionGroupWithMultiplePausesActionGroup.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..f599ce143 --- /dev/null +++ b/dev/tests/verification/PauseCheckModule/ActionGroup/ActionGroupWithNoPauseActionGroup.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..a32fd69fa --- /dev/null +++ b/dev/tests/verification/PauseCheckModule/ActionGroup/ActionGroupWithPauseActionGroup.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..d15ac0075 --- /dev/null +++ b/dev/tests/verification/PauseCheckModule/Suite/suiteWithMultiplePauseActionsSuite.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> + +<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..ea96def04 --- /dev/null +++ b/dev/tests/verification/PauseCheckModule/Suite/suiteWithPauseActionSuite.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> + +<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..a7adbe213 --- /dev/null +++ b/dev/tests/verification/PauseCheckModule/Test/TestWithMultiplePauseActionsTest.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> + +<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..c1cb88e6d --- /dev/null +++ b/dev/tests/verification/PauseCheckModule/Test/TestWithPauseActionTest.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> + +<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..f837f6134 --- /dev/null +++ b/dev/tests/verification/ResilientGenerationModule/ActionGroup/BasicActionGroup/NotGenerateArgActionGroup.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..950808af3 --- /dev/null +++ b/dev/tests/verification/ResilientGenerationModule/ActionGroup/BasicActionGroup/NotGenerateCreateDataActionGroup.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..a625aedf9 --- /dev/null +++ b/dev/tests/verification/ResilientGenerationModule/ActionGroup/BasicActionGroup/NotGenerateExtendChildActionGroup.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..3497ff08c --- /dev/null +++ b/dev/tests/verification/ResilientGenerationModule/ActionGroup/BasicActionGroup/NotGenerateExtendChildBadReferenceActionGroup.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..1178ccbc9 --- /dev/null +++ b/dev/tests/verification/ResilientGenerationModule/ActionGroup/BasicActionGroup/NotGenerateExtendParentActionGroup.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..849530201 --- /dev/null +++ b/dev/tests/verification/ResilientGenerationModule/ActionGroup/BasicActionGroup/NotGenerateParamsActionGroup.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..2f1648eae --- /dev/null +++ b/dev/tests/verification/ResilientGenerationModule/ActionGroup/BasicActionGroup/NotGenerateSectionActionGroup.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..fe671f55c --- /dev/null +++ b/dev/tests/verification/ResilientGenerationModule/ActionGroup/FunctionalActionGroup/NotGenerateMassMergeAfterActionGroup.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..cba21bc7f --- /dev/null +++ b/dev/tests/verification/ResilientGenerationModule/ActionGroup/FunctionalActionGroup/NotGenerateMassMergeBeforeActionGroup.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..ef9c72733 --- /dev/null +++ b/dev/tests/verification/ResilientGenerationModule/ActionGroup/FunctionalActionGroup/NotGenerateMergeActionGroup.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..8240c5747 --- /dev/null +++ b/dev/tests/verification/ResilientGenerationModule/ActionGroup/MergeFunctionalActionGroup/NotGenerateMassMergeAfterActionGroup.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..016c1fafe --- /dev/null +++ b/dev/tests/verification/ResilientGenerationModule/ActionGroup/MergeFunctionalActionGroup/NotGenerateMassMergeBeforeActionGroup.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..035b787f5 --- /dev/null +++ b/dev/tests/verification/ResilientGenerationModule/ActionGroup/MergeFunctionalActionGroup/NotGenerateMergeActionGroup.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..c2bfb1e84 --- /dev/null +++ b/dev/tests/verification/ResilientGenerationModule/Data/ExtendedData.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> + +<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..8c90b8784 --- /dev/null +++ b/dev/tests/verification/ResilientGenerationModule/Suite/NotGenerateEmptySuite.xml @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> + +<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..be7a18e50 --- /dev/null +++ b/dev/tests/verification/ResilientGenerationModule/Suite/NotGenerateHookAfterSuite.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> + +<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..c306ad37e --- /dev/null +++ b/dev/tests/verification/ResilientGenerationModule/Suite/NotGenerateHookBeforeSuite.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> + +<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..5fb48c8eb --- /dev/null +++ b/dev/tests/verification/ResilientGenerationModule/Suite/PartialGenerateForIncludeSuite.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> + +<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..e9829fd46 --- /dev/null +++ b/dev/tests/verification/ResilientGenerationModule/Suite/PartialGenerateNoExcludeSuite.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> + +<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..6e93cfef6 --- /dev/null +++ b/dev/tests/verification/ResilientGenerationModule/Test/ActionGroupTest/NotGenerateActionGroupHasBeforeOrAfterTest.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..686eae908 --- /dev/null +++ b/dev/tests/verification/ResilientGenerationModule/Test/ActionGroupTest/NotGenerateArgTest.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..7924c983b --- /dev/null +++ b/dev/tests/verification/ResilientGenerationModule/Test/ActionGroupTest/NotGenerateExtendChildTest.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..78704b6ec --- /dev/null +++ b/dev/tests/verification/ResilientGenerationModule/Test/ActionGroupTest/NotGenerateMassMergeAfterTest.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..0c16692e9 --- /dev/null +++ b/dev/tests/verification/ResilientGenerationModule/Test/ActionGroupTest/NotGenerateMassMergeBeforeTest.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..5bbe6154d --- /dev/null +++ b/dev/tests/verification/ResilientGenerationModule/Test/ActionGroupTest/NotGenerateMergeTest.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..f550e0e7e --- /dev/null +++ b/dev/tests/verification/ResilientGenerationModule/Test/ActionGroupTest/NotGenerateNonExistingActionGroupTest.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..ad6ecab2b --- /dev/null +++ b/dev/tests/verification/ResilientGenerationModule/Test/ActionGroupTest/NotGenerateParamsTest.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..e8fd88e3b --- /dev/null +++ b/dev/tests/verification/ResilientGenerationModule/Test/ActionGroupTest/NotGenerateRequiredDataTest.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..2c69c1516 --- /dev/null +++ b/dev/tests/verification/ResilientGenerationModule/Test/ActionGroupTest/NotGenerateSectionTest.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..3cf37a94d --- /dev/null +++ b/dev/tests/verification/ResilientGenerationModule/Test/ExtendTest/NotGenerateChildExtendedBadReferenceTest.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..fbe8031f2 --- /dev/null +++ b/dev/tests/verification/ResilientGenerationModule/Test/ExtendTest/NotGenerateChildExtendedMergingTest.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..e99bf5aaa --- /dev/null +++ b/dev/tests/verification/ResilientGenerationModule/Test/ExtendTest/NotGenerateExtendNonExistingParentTest.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..c3800e6b7 --- /dev/null +++ b/dev/tests/verification/ResilientGenerationModule/Test/ExtendTest/NotGenerateExtendSelfTest.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..ab5c3bdb1 --- /dev/null +++ b/dev/tests/verification/ResilientGenerationModule/Test/ExtendTest/NotGenerateExtendedChildTest.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..1f3e62e14 --- /dev/null +++ b/dev/tests/verification/ResilientGenerationModule/Test/ExtendTest/NotGenerateParentTest.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..7fcc227ac --- /dev/null +++ b/dev/tests/verification/ResilientGenerationModule/Test/MergeTest/NotGenerateBasicMergeTest.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..92d816046 --- /dev/null +++ b/dev/tests/verification/ResilientGenerationModule/Test/MergeTest/NotGenerateMergeMassViaInsertAfter.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..1f3e3b7c0 --- /dev/null +++ b/dev/tests/verification/ResilientGenerationModule/Test/MergeTest/NotGenerateMergeMassViaInsertBefore.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..70d18a68a --- /dev/null +++ b/dev/tests/verification/ResilientGenerationModule/Test/NotGenerateActionHasBothBeforeAndAfterTest.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..828e34fda --- /dev/null +++ b/dev/tests/verification/ResilientGenerationModule/Test/NotGenerateActionHasInvalidBeforeOrAfterTest.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..6365945ad --- /dev/null +++ b/dev/tests/verification/ResilientGenerationModule/Test/NotGenerateAssertTest.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> + +<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..7a95891aa --- /dev/null +++ b/dev/tests/verification/ResilientGenerationModule/Test/NotGenerateBasicMergeTest.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..4e6e6703a --- /dev/null +++ b/dev/tests/verification/ResilientGenerationModule/Test/NotGenerateBasicXYOffsetTest.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..dc74224e2 --- /dev/null +++ b/dev/tests/verification/ResilientGenerationModule/Test/NotGenerateDataReferenceTest.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> + +<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..b6f7c0060 --- /dev/null +++ b/dev/tests/verification/ResilientGenerationModule/Test/NotGenerateDataReplacementTest.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> + +<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..aa44ec286 --- /dev/null +++ b/dev/tests/verification/ResilientGenerationModule/Test/NotGenerateMergeMassViaInsertAfter.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..a2c69aaa8 --- /dev/null +++ b/dev/tests/verification/ResilientGenerationModule/Test/NotGenerateMergeMassViaInsertBefore.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..32956cc46 --- /dev/null +++ b/dev/tests/verification/ResilientGenerationModule/Test/NotGeneratePageReplacementTest.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> + +<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..ee094bc4a --- /dev/null +++ b/dev/tests/verification/ResilientGenerationModule/Test/NotGenerateSectionReplacementTest.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> + +<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 8d33003af..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,27 +13,55 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** + * @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->see("arg1", ".selector"); + $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 */ public function ActionGroupContainsStepKeyInArgText(AcceptanceTester $I) { - $I->see("arg1", ".selector"); + $I->comment("Entering Action Group [actionGroup] actionGroupContainsStepKeyInArgValue"); + $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 2c2536136..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,23 +13,47 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** + * @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 */ public function ActionGroupMergedViaInsertAfter(AcceptanceTester $I) { - $I->fillField("#foo", "foo"); - $I->fillField("#bar", "bar"); - $I->click("#foo2"); - $I->click("#bar2"); - $I->click("#baz2"); - $I->fillField("#baz", "baz"); + $I->comment("Entering Action Group [keyone] FunctionalActionGroupForMassMergeAfter"); + $I->fillField("#foo", "foo"); // stepKey: fillField1Keyone + $I->fillField("#bar", "bar"); // stepKey: fillField2Keyone + $I->click("#foo2"); // stepKey: mergeAfterBarKeyone + $I->click("#bar2"); // stepKey: mergeAfterFoo2Keyone + $I->click("#baz2"); // stepKey: mergeAfterBar2Keyone + $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 110ce3059..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,23 +13,47 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** + * @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 */ public function ActionGroupMergedViaInsertBefore(AcceptanceTester $I) { - $I->fillField("#foo", "foo"); - $I->click("#foo2"); - $I->click("#bar2"); - $I->click("#baz2"); - $I->fillField("#bar", "bar"); - $I->fillField("#baz", "baz"); + $I->comment("Entering Action Group [keyone] FunctionalActionGroupForMassMergeBefore"); + $I->fillField("#foo", "foo"); // stepKey: fillField1Keyone + $I->click("#foo2"); // stepKey: mergeBeforeBarKeyone + $I->click("#bar2"); // stepKey: mergeAfterFoo2Keyone + $I->click("#baz2"); // stepKey: mergeAfterBar2Keyone + $I->fillField("#bar", "bar"); // stepKey: fillField2Keyone + $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 b787f9116..000000000 --- a/dev/tests/verification/Resources/ActionGroupSkipReadiness.txt +++ /dev/null @@ -1,34 +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; - -/** - */ -class ActionGroupSkipReadinessCest -{ - /** - * @Features({"TestModule"}) - * @Parameter(name = "AcceptanceTester", value="$I") - * @param AcceptanceTester $I - * @return void - * @throws \Exception - */ - public function ActionGroupSkipReadiness(AcceptanceTester $I) - { - $I->skipReadinessCheck(true); - $I->comment("ActionGroupSkipReadiness"); - $I->skipReadinessCheck(false); - } -} diff --git a/dev/tests/verification/Resources/ActionGroupToExtend.txt b/dev/tests/verification/Resources/ActionGroupToExtend.txt index 7c7e666cd..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,19 +13,43 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** + * @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 */ public function ActionGroupToExtend(AcceptanceTester $I) { - $grabProductsActionGroup = $I->grabMultiple("selector"); - $I->assertCount(99, $grabProductsActionGroup); + $I->comment("Entering Action Group [actionGroup] ActionGroupToExtend"); + $grabProductsActionGroup = $I->grabMultiple("selector"); // stepKey: grabProductsActionGroup + $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 f0d14dbd9..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,36 +13,41 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** + * @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->amGoingTo("create entity that has the stepKey: createCategoryKey1"); - PersistedObjectHandler::getInstance()->createEntity( - "createCategoryKey1", - "hook", - "ApiCategory", - [], - null - ); - $I->amGoingTo("create entity that has the stepKey: createConfigProductKey1"); - PersistedObjectHandler::getInstance()->createEntity( - "createConfigProductKey1", - "hook", - "ApiConfigurableProduct", - ["createCategoryKey1"], - null - ); + $I->comment('[START BEFORE HOOK]'); + $I->comment("Entering Action Group [Key1] actionGroupWithCreateData"); + $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 @@ -52,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 2d435b051..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,19 +13,43 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** + * @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 */ public function ActionGroupUsingNestedArgument(AcceptanceTester $I) { - $grabProductsActionGroup = $I->grabMultiple("selector"); - $I->assertCount(99, $grabProductsActionGroup); + $I->comment("Entering Action Group [actionGroup] ActionGroupToExtend"); + $grabProductsActionGroup = $I->grabMultiple("selector"); // stepKey: grabProductsActionGroup + $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 3bc708a1a..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,25 +14,28 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @group functional + * @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->amGoingTo("create entity that has the stepKey: createPersonParam"); - PersistedObjectHandler::getInstance()->createEntity( - "createPersonParam", - "hook", - "ReplacementPerson", - [], - null - ); - $I->fillField("#foo", "myData1"); - $I->fillField("#bar", "myData2"); + $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]'); } /** @@ -43,8 +44,15 @@ class ActionGroupWithDataOverrideTestCest */ public function _after(AcceptanceTester $I) { - $I->fillField("#foo", "myData1"); - $I->fillField("#bar", "myData2"); + $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__); + } } /** @@ -53,26 +61,33 @@ class ActionGroupWithDataOverrideTestCest */ public function _failed(AcceptanceTester $I) { - $I->saveScreenshot(); + $I->saveScreenshot(); // stepKey: saveScreenshot } /** * @Severity(level = SeverityLevel::CRITICAL) * @Features({"TestModule"}) * @Stories({"MQE-433"}) - * @Parameter(name = "AcceptanceTester", value="$I") * @param AcceptanceTester $I * @return void * @throws \Exception */ public function ActionGroupWithDataOverrideTest(AcceptanceTester $I) { - $I->amOnPage("/someUrl"); - $I->amOnPage("/John/Doe.html"); - $I->fillField("#foo", "John"); - $I->fillField("#bar", "Doe"); - $I->searchAndMultiSelectOption("#foo", ["John", "Doe"]); - $I->see("#element .John"); - $I->click("loginButton"); + $I->amOnPage("/someUrl"); // stepKey: step1 + $I->comment("Entering Action Group [actionGroupWithDataOverride1] FunctionalActionGroupWithData"); + $I->amOnPage("/John/Doe.html"); // stepKey: amOnPage1ActionGroupWithDataOverride1 + $I->fillField("#foo", "John"); // stepKey: fillField1ActionGroupWithDataOverride1 + $I->fillField("#bar", "Doe"); // stepKey: fillField2ActionGroupWithDataOverride1 + $I->searchAndMultiSelectOption("#foo", ["John", "Doe"]); // stepKey: multi1ActionGroupWithDataOverride1 + $I->see("#element .John"); // stepKey: see1ActionGroupWithDataOverride1 + $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 e79534bad..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,25 +14,28 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @group functional + * @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->amGoingTo("create entity that has the stepKey: createPersonParam"); - PersistedObjectHandler::getInstance()->createEntity( - "createPersonParam", - "hook", - "ReplacementPerson", - [], - null - ); - $I->fillField("#foo", "myData1"); - $I->fillField("#bar", "myData2"); + $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]'); } /** @@ -43,8 +44,15 @@ class ActionGroupWithDataTestCest */ public function _after(AcceptanceTester $I) { - $I->fillField("#foo", "myData1"); - $I->fillField("#bar", "myData2"); + $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__); + } } /** @@ -53,26 +61,33 @@ class ActionGroupWithDataTestCest */ public function _failed(AcceptanceTester $I) { - $I->saveScreenshot(); + $I->saveScreenshot(); // stepKey: saveScreenshot } /** * @Severity(level = SeverityLevel::CRITICAL) * @Features({"TestModule"}) * @Stories({"MQE-433"}) - * @Parameter(name = "AcceptanceTester", value="$I") * @param AcceptanceTester $I * @return void * @throws \Exception */ public function ActionGroupWithDataTest(AcceptanceTester $I) { - $I->amOnPage("/someUrl"); - $I->amOnPage("/Jane/Dane.html"); - $I->fillField("#foo", "Jane"); - $I->fillField("#bar", "Dane"); - $I->searchAndMultiSelectOption("#foo", ["Jane", "Dane"]); - $I->see("#element .Jane"); - $I->click("loginButton"); + $I->amOnPage("/someUrl"); // stepKey: step1 + $I->comment("Entering Action Group [actionGroupWithData1] FunctionalActionGroupWithData"); + $I->amOnPage("/Jane/Dane.html"); // stepKey: amOnPage1ActionGroupWithData1 + $I->fillField("#foo", "Jane"); // stepKey: fillField1ActionGroupWithData1 + $I->fillField("#bar", "Dane"); // stepKey: fillField2ActionGroupWithData1 + $I->searchAndMultiSelectOption("#foo", ["Jane", "Dane"]); // stepKey: multi1ActionGroupWithData1 + $I->see("#element .Jane"); // stepKey: see1ActionGroupWithData1 + $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 db3c6325c..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,19 +14,43 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @Title("[NO TESTCASEID]: Action Group With Default Argument Value and Hardcoded Value in Param") + * @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 */ public function ActionGroupWithDefaultArgumentAndStringSelectorParam(AcceptanceTester $I) { - $I->see("John", "#element .test1"); + $I->comment("Entering Action Group [actionGroup] actionGroupWithDefaultArgumentAndStringSelectorParam"); + $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 e446cc407..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,19 +14,43 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @Title("[NO TESTCASEID]: Action Group With Passed Argument Value and Multiple Argument Values in Param") + * @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 */ public function ActionGroupWithMultipleParameterSelectorsFromDefaultArgument(AcceptanceTester $I) { - $I->see("Doe", "#John-Doe .test"); + $I->comment("Entering Action Group [actionGroup] actionGroupWithMultipleParameterSelectorsFromArgument"); + $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 39e4bda89..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,19 +14,43 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @Title("[NO TESTCASEID]: Action Group With No Argument") + * @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 */ public function ActionGroupWithNoArguments(AcceptanceTester $I) { - $I->wait(1); + $I->comment("Entering Action Group [actionGroup] actionGroupWithoutArguments"); + $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 ed02790e5..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,25 +14,28 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @group functional + * @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->amGoingTo("create entity that has the stepKey: createPersonParam"); - PersistedObjectHandler::getInstance()->createEntity( - "createPersonParam", - "hook", - "ReplacementPerson", - [], - null - ); - $I->fillField("#foo", "myData1"); - $I->fillField("#bar", "myData2"); + $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]'); } /** @@ -43,8 +44,15 @@ class ActionGroupWithNoDefaultTestCest */ public function _after(AcceptanceTester $I) { - $I->fillField("#foo", "myData1"); - $I->fillField("#bar", "myData2"); + $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__); + } } /** @@ -53,24 +61,31 @@ class ActionGroupWithNoDefaultTestCest */ public function _failed(AcceptanceTester $I) { - $I->saveScreenshot(); + $I->saveScreenshot(); // stepKey: saveScreenshot } /** * @Severity(level = SeverityLevel::CRITICAL) * @Features({"TestModule"}) * @Stories({"MQE-433"}) - * @Parameter(name = "AcceptanceTester", value="$I") * @param AcceptanceTester $I * @return void * @throws \Exception */ public function ActionGroupWithNoDefaultTest(AcceptanceTester $I) { - $I->amOnPage("/someUrl"); - $I->fillField("#foo", "Jane"); - $I->fillField("#bar", "Dane"); - $I->see("#Jane .Dane"); - $I->click("loginButton"); + $I->amOnPage("/someUrl"); // stepKey: step1 + $I->comment("Entering Action Group [actionGroupWithDataOverride1] FunctionalActionGroupNoDefault"); + $I->fillField("#foo", "Jane"); // stepKey: fillField1ActionGroupWithDataOverride1 + $I->fillField("#bar", "Dane"); // stepKey: fillField2ActionGroupWithDataOverride1 + $I->see("#Jane .Dane"); // stepKey: see2ActionGroupWithDataOverride1 + $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 64a350505..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,18 +13,42 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** + * @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 */ public function ActionGroupWithParameterizedElementWithHyphen(AcceptanceTester $I) { - $keyoneActionGroup = $I->executeJS("#element .full-width"); + $I->comment("Entering Action Group [actionGroup] SectionArgumentWithParameterizedSelector"); + $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 new file mode 100644 index 000000000..83aaecf08 --- /dev/null +++ b/dev/tests/verification/Resources/ActionGroupWithParameterizedElementsWithStepKeyReferences.txt @@ -0,0 +1,59 @@ +<?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>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"}) + * @param AcceptanceTester $I + * @return void + * @throws \Exception + */ + public function ActionGroupWithParameterizedElementsWithStepKeyReferences(AcceptanceTester $I) + { + $I->comment("Entering Action Group [actionGroup] actionGroupWithParametrizedSelectors"); + $testVariableActionGroup = $I->executeJS("return 1"); // stepKey: testVariableActionGroup + $testVariable2ActionGroup = $I->executeJS("return 'test'"); // stepKey: testVariable2ActionGroup + $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}'][" . $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 157690bee..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,19 +14,43 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @Title("[NO TESTCASEID]: Action Group With Passed Argument Value and Hardcoded Value in Param") + * @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 */ public function ActionGroupWithPassedArgumentAndStringSelectorParam(AcceptanceTester $I) { - $I->see("John" . msq("UniquePerson"), "#element .test1"); + $I->comment("Entering Action Group [actionGroup] actionGroupWithDefaultArgumentAndStringSelectorParam"); + $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 5ed6eb7f7..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,25 +14,28 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @group functional + * @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->amGoingTo("create entity that has the stepKey: createPersonParam"); - PersistedObjectHandler::getInstance()->createEntity( - "createPersonParam", - "hook", - "ReplacementPerson", - [], - null - ); - $I->fillField("#foo", "myData1"); - $I->fillField("#bar", "myData2"); + $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]'); } /** @@ -43,8 +44,15 @@ class ActionGroupWithPersistedDataCest */ public function _after(AcceptanceTester $I) { - $I->fillField("#foo", "myData1"); - $I->fillField("#bar", "myData2"); + $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__); + } } /** @@ -53,32 +61,32 @@ class ActionGroupWithPersistedDataCest */ public function _failed(AcceptanceTester $I) { - $I->saveScreenshot(); + $I->saveScreenshot(); // stepKey: saveScreenshot } /** * @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->amGoingTo("create entity that has the stepKey: createPerson"); - PersistedObjectHandler::getInstance()->createEntity( - "createPerson", - "test", - "DefaultPerson", - [], - null - ); - $I->amOnPage("/" . PersistedObjectHandler::getInstance()->retrieveEntityField('createPerson', 'firstname', 'test') . "/" . PersistedObjectHandler::getInstance()->retrieveEntityField('createPerson', 'lastname', 'test') . ".html"); - $I->fillField("#foo", PersistedObjectHandler::getInstance()->retrieveEntityField('createPerson', 'firstname', 'test')); - $I->fillField("#bar", PersistedObjectHandler::getInstance()->retrieveEntityField('createPerson', 'lastname', 'test')); - $I->searchAndMultiSelectOption("#foo", [PersistedObjectHandler::getInstance()->retrieveEntityField('createPerson', 'firstname', 'test'), PersistedObjectHandler::getInstance()->retrieveEntityField('createPerson', 'lastname', 'test')]); - $I->see("#element ." . PersistedObjectHandler::getInstance()->retrieveEntityField('createPerson', 'firstname', 'test')); + $I->createEntity("createPerson", "test", "DefaultPerson", [], []); // stepKey: createPerson + $I->comment("Entering Action Group [actionGroupWithPersistedData1] FunctionalActionGroupWithData"); + $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 new file mode 100644 index 000000000..65d25df00 --- /dev/null +++ b/dev/tests/verification/Resources/ActionGroupWithSectionAndDataAsArguments.txt @@ -0,0 +1,54 @@ +<?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>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"}) + * @param AcceptanceTester $I + * @return void + * @throws \Exception + */ + public function ActionGroupWithSectionAndDataAsArguments(AcceptanceTester $I) + { + $I->comment("Entering Action Group [actionGroup] actionGroupWithSectionAndData"); + $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 a71cd562f..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,19 +14,43 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @Title("[NO TESTCASEID]: Action Group With Simple Data Usage From Default Argument") + * @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 */ public function ActionGroupWithSimpleDataUsageFromDefaultArgument(AcceptanceTester $I) { - $I->see("stringLiteral", "#element .stringLiteral"); + $I->comment("Entering Action Group [actionGroup] actionGroupWithStringUsage"); + $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 1453d716f..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,28 +14,70 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @Title("[NO TESTCASEID]: Action Group With Simple Data Usage From Passed Argument") + * @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 */ public function ActionGroupWithSimpleDataUsageFromPassedArgument(AcceptanceTester $I) { - $I->see("overrideString", "#element .overrideString"); - $I->see("1", "#element .1"); - $I->see("1.5", "#element .1.5"); - $I->see("true", "#element .true"); - $I->see("simpleData.firstname", "#element .simpleData.firstname"); - $I->see(PersistedObjectHandler::getInstance()->retrieveEntityField('persisted', 'data', 'test'), "#element ." . PersistedObjectHandler::getInstance()->retrieveEntityField('persisted', 'data', 'test')); - $I->see("John", "#element .John"); - $I->see(PersistedObjectHandler::getInstance()->retrieveEntityField('simpleData', 'firstname', 'test'), "#element ." . PersistedObjectHandler::getInstance()->retrieveEntityField('simpleData', 'firstname', 'test')); - $I->see(PersistedObjectHandler::getInstance()->retrieveEntityField('simpleData', 'firstname[0]', 'test'), "#element ." . PersistedObjectHandler::getInstance()->retrieveEntityField('simpleData', 'firstname[0]', 'test')); - $I->see(PersistedObjectHandler::getInstance()->retrieveEntityField('simpleData', 'firstname[data_index]', 'test'), "#element ." . PersistedObjectHandler::getInstance()->retrieveEntityField('simpleData', 'firstname[data_index]', 'test')); + $I->comment("Entering Action Group [actionGroup1] actionGroupWithStringUsage"); + $I->see("overrideString", "#element .overrideString"); // stepKey: see1ActionGroup1 + $I->comment("Exiting Action Group [actionGroup1] actionGroupWithStringUsage"); + $I->comment("Entering Action Group [actionGroup11] actionGroupWithStringUsage"); + $I->see("1", "#element .1"); // stepKey: see1ActionGroup11 + $I->comment("Exiting Action Group [actionGroup11] actionGroupWithStringUsage"); + $I->comment("Entering Action Group [actionGroup12] actionGroupWithStringUsage"); + $I->see("1.5", "#element .1.5"); // stepKey: see1ActionGroup12 + $I->comment("Exiting Action Group [actionGroup12] actionGroupWithStringUsage"); + $I->comment("Entering Action Group [actionGroup13] actionGroupWithStringUsage"); + $I->see("true", "#element .true"); // stepKey: see1ActionGroup13 + $I->comment("Exiting Action Group [actionGroup13] actionGroupWithStringUsage"); + $I->comment("Entering Action Group [actionGroup2] actionGroupWithStringUsage"); + $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($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($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($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($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 86be7fb72..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,19 +14,43 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @Title("[NO TESTCASEID]: Action Group With Default Argument Value and Argument Value in Param") + * @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 */ public function ActionGroupWithSingleParameterSelectorFromDefaultArgument(AcceptanceTester $I) { - $I->see("Doe", "#element .John"); + $I->comment("Entering Action Group [actionGroup] actionGroupWithSingleParameterSelectorFromArgument"); + $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 9a30bcc5c..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,19 +14,43 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @Title("[NO TESTCASEID]: Action Group With Passed Argument Value and Argument Value in Param") + * @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 */ public function ActionGroupWithSingleParameterSelectorFromPassedArgument(AcceptanceTester $I) { - $I->see("Doe", "#element .John" . msq("UniquePerson")); + $I->comment("Entering Action Group [actionGroup] actionGroupWithSingleParameterSelectorFromArgument"); + $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 542e20133..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,73 +13,67 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** + * @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 */ public function ActionGroupWithStepKeyReferences(AcceptanceTester $I) { - $I->amGoingTo("create entity that has the stepKey: createSimpleDataActionGroup"); - PersistedObjectHandler::getInstance()->createEntity( - "createSimpleDataActionGroup", - "test", - "simpleData", - [], - null - ); - $grabTextDataActionGroup = $I->grabTextFrom(".class"); - $I->fillField(".{$grabTextDataActionGroup}", PersistedObjectHandler::getInstance()->retrieveEntityField('createSimpleDataActionGroup', 'field', 'test')); + $I->comment("Entering Action Group [actionGroup] FunctionActionGroupWithStepKeyReferences"); + $I->createEntity("createSimpleDataActionGroup", "test", "TestData", [], []); // stepKey: createSimpleDataActionGroup + $grabTextDataActionGroup = $I->grabTextFrom(".class"); // stepKey: grabTextDataActionGroup + $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); - $I->fillField($action1); + $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); - $action4ActionGroup = $I->magentoCLI($action4ActionGroup, "\"stuffHere\""); + $action3ActionGroup = $I->executeJS($action3ActionGroup); // stepKey: action3ActionGroup + $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); - $I->amGoingTo("delete entity that has the createDataKey: {$action7ActionGroupActionGroup}"); - PersistedObjectHandler::getInstance()->deleteEntity( - "{$action7ActionGroupActionGroup}", - "test" - ); - $I->amGoingTo("get entity that has the stepKey: action8ActionGroup"); - PersistedObjectHandler::getInstance()->getEntity( - "action8ActionGroup", - "test", - "{$action8}", - [], - null - ); - $I->amGoingTo("update entity that has the createdDataKey: 1"); - PersistedObjectHandler::getInstance()->updateEntity( - "1", - "test", - "{$action9}", - [] - ); - $I->amGoingTo("create entity that has the stepKey: action10ActionGroup"); - PersistedObjectHandler::getInstance()->createEntity( - "action10ActionGroup", - "test", - "{$action10}", - [], - null - ); - $action11ActionGroup = $I->grabAttributeFrom($action11ActionGroup, "someInput"); - $action12ActionGroup = $I->grabCookie($action12ActionGroup, ['domain' => 'www.google.com']); - $action13ActionGroup = $I->grabFromCurrentUrl($action13ActionGroup); - $action14ActionGroup = $I->grabMultiple($action14ActionGroup); - $action15ActionGroup = $I->grabTextFrom($action15ActionGroup); - $action16ActionGroup = $I->grabValueFrom($action16ActionGroup); + + $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 acc274567..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,25 +14,28 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @group functional + * @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->amGoingTo("create entity that has the stepKey: createPersonParam"); - PersistedObjectHandler::getInstance()->createEntity( - "createPersonParam", - "hook", - "ReplacementPerson", - [], - null - ); - $I->fillField("#foo", "myData1"); - $I->fillField("#bar", "myData2"); + $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]'); } /** @@ -43,8 +44,15 @@ class ActionGroupWithTopLevelPersistedDataCest */ public function _after(AcceptanceTester $I) { - $I->fillField("#foo", "myData1"); - $I->fillField("#bar", "myData2"); + $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__); + } } /** @@ -53,24 +61,31 @@ class ActionGroupWithTopLevelPersistedDataCest */ public function _failed(AcceptanceTester $I) { - $I->saveScreenshot(); + $I->saveScreenshot(); // stepKey: saveScreenshot } /** * @Severity(level = SeverityLevel::CRITICAL) * @Features({"TestModule"}) * @Stories({"MQE-433"}) - * @Parameter(name = "AcceptanceTester", value="$I") * @param AcceptanceTester $I * @return void * @throws \Exception */ public function ActionGroupWithTopLevelPersistedData(AcceptanceTester $I) { - $I->amOnPage("/" . PersistedObjectHandler::getInstance()->retrieveEntityField('createPersonParam', 'firstname', 'test') . "/" . PersistedObjectHandler::getInstance()->retrieveEntityField('createPersonParam', 'lastname', 'test') . ".html"); - $I->fillField("#foo", PersistedObjectHandler::getInstance()->retrieveEntityField('createPersonParam', 'firstname', 'test')); - $I->fillField("#bar", PersistedObjectHandler::getInstance()->retrieveEntityField('createPersonParam', 'lastname', 'test')); - $I->searchAndMultiSelectOption("#foo", [PersistedObjectHandler::getInstance()->retrieveEntityField('createPersonParam', 'firstname', 'test'), PersistedObjectHandler::getInstance()->retrieveEntityField('createPersonParam', 'lastname', 'test')]); - $I->see("#element ." . PersistedObjectHandler::getInstance()->retrieveEntityField('createPersonParam', 'firstname', 'test')); + $I->comment("Entering Action Group [actionGroupWithPersistedData1] FunctionalActionGroupWithData"); + $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..c986b6fb4 --- /dev/null +++ b/dev/tests/verification/Resources/ActionsInDifferentModulesSuite.txt @@ -0,0 +1,254 @@ +<?php + +namespace Group; + +use Facebook\WebDriver\Remote\RemoteWebDriver; +use Facebook\WebDriver\Remote\DesiredCapabilities; +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; +use Facebook\WebDriver\Chrome\ChromeOptions; + +/** + * 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->getResultAggregator(); + }, + $cest + )); + $errors = $testResultObject->errors(); + + if (!empty($errors)) { + foreach ($errors as $error) { + if ($error->getTest()->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) { + try { + $chromeOptions = new ChromeOptions(); + $capabilities = DesiredCapabilities::chrome(); + $remoteWebDriver = RemoteWebDriver::createBySessionID($session['id'], $wdHost, + $webDriverConfig['connection_timeout'], $webDriverConfig['request_timeout'], true, $capabilities); + $remoteWebDriver->quit(); + } catch (\Exception $exception) { + print("Failed trying to quit WebDriver session. Exception message: " . $exception->getMessage() . " Test execution will continue." . PHP_EOL); + // Session already closed so nothing to do + } + } + } + } + + /** + * 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; + } +} \ No newline at end of file diff --git a/dev/tests/verification/Resources/ArgumentWithSameNameAsElement.txt b/dev/tests/verification/Resources/ArgumentWithSameNameAsElement.txt index 80a1d3c80..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,25 +14,28 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @group functional + * @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->amGoingTo("create entity that has the stepKey: createPersonParam"); - PersistedObjectHandler::getInstance()->createEntity( - "createPersonParam", - "hook", - "ReplacementPerson", - [], - null - ); - $I->fillField("#foo", "myData1"); - $I->fillField("#bar", "myData2"); + $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]'); } /** @@ -43,8 +44,15 @@ class ArgumentWithSameNameAsElementCest */ public function _after(AcceptanceTester $I) { - $I->fillField("#foo", "myData1"); - $I->fillField("#bar", "myData2"); + $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__); + } } /** @@ -53,21 +61,28 @@ class ArgumentWithSameNameAsElementCest */ public function _failed(AcceptanceTester $I) { - $I->saveScreenshot(); + $I->saveScreenshot(); // stepKey: saveScreenshot } /** * @Severity(level = SeverityLevel::CRITICAL) * @Features({"TestModule"}) * @Stories({"MQE-433"}) - * @Parameter(name = "AcceptanceTester", value="$I") * @param AcceptanceTester $I * @return void * @throws \Exception */ public function ArgumentWithSameNameAsElement(AcceptanceTester $I) { - $I->seeElement("#element"); - $I->seeElement("#element .John"); + $I->comment("Entering Action Group [actionGroup1] FunctionalActionGroupWithTrickyArgument"); + $I->seeElement("#element"); // stepKey: see1ActionGroup1 + $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 adc764975..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,148 +13,174 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** + * @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->amGoingTo("create entity that has the stepKey: createData1"); - PersistedObjectHandler::getInstance()->createEntity( - "createData1", - "hook", - "ReplacementPerson", - [], - null - ); + $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->amGoingTo("create entity that has the stepKey: createData2"); - PersistedObjectHandler::getInstance()->createEntity( - "createData2", - "test", - "UniquePerson", - [], - null - ); - $grabTextFrom1 = $I->grabTextFrom(".copyright>span"); - $I->assertArrayIsSorted(["1", "2", "3", "4", "5"], "asc"); + $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 + $I->comment("asserts without variable replacement"); $I->comment("asserts without variable replacement"); - $I->assertArrayHasKey("apple", ['orange' => 2, 'apple' => 1], "pass"); - $I->assertArrayNotHasKey("kiwi", ['orange' => 2, 'apple' => 1], "pass"); - $I->assertArraySubset([1, 2], [1, 2, 3, 5], "pass"); - $I->assertContains("ab", ['item1' => 'a', 'item2' => 'ab'], "pass"); - $I->assertCount(2, ['a', 'b'], "pass"); - $I->assertEmpty([], "pass"); - $I->assertEquals($text, "Copyright © 2013-2017 Magento, Inc. All rights reserved.", "pass"); - $I->assertEquals("Copyright © 2013-2017 Magento, Inc. All rights reserved.", $text, "pass"); - $I->assertFalse(false, "pass"); - $I->assertFileNotExists("/out.txt", "pass"); - $I->assertFileNotExists($text, "pass"); - $I->assertGreaterOrEquals(2, 5, "pass"); - $I->assertGreaterThan(2, 5, "pass"); - $I->assertGreaterThanOrEqual(2, 5, "pass"); - $I->assertInternalType("string", "xyz", "pass"); - $I->assertInternalType("int", 21, "pass"); - $I->assertInternalType("string", $text, "pass"); - $I->assertLessOrEquals(5, 2, "pass"); - $I->assertLessThan(5, 2, "pass"); - $I->assertLessThanOrEqual(5, 2, "pass"); - $I->assertNotContains("bc", ['item1' => 'a', 'item2' => 'ab'], "pass"); - $I->assertNotContains("bc", $text, "pass"); - $I->assertNotEmpty([1, 2], "pass"); - $I->assertNotEmpty($text, "pass"); - $I->assertNotEquals(2, 5, "pass", 0); - $I->assertNotNull("abc", "pass"); - $I->assertNotNull($text, "pass"); - $I->assertNotRegExp("/foo/", "bar", "pass"); - $I->assertNotSame("log", "tag", "pass"); - $I->assertRegExp("/foo/", "foo", "pass"); - $I->assertSame("bar", "bar", "pass"); - $I->assertStringStartsNotWith("a", "banana", "pass"); - $I->assertStringStartsWith("a", "apple", "pass"); - $I->assertTrue(true, "pass"); + $I->assertArrayHasKey("apple", ['orange' => 2, 'apple' => 1], "pass"); // stepKey: assertArrayHasKey + $I->assertArrayNotHasKey("kiwi", ['orange' => 2, 'apple' => 1], "pass"); // stepKey: assertArrayNotHasKey + $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->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: 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"); // stepKey: assertNotEquals + $I->assertNotNull("abc", "pass"); // stepKey: assertNotNull1 + $I->assertNotNull($text, "pass"); // stepKey: assertNotNull2 + $I->assertNotRegExp("/foo/", "bar", "pass"); // stepKey: assertNotRegExp + $I->assertNotSame("log", "tag", "pass"); // stepKey: assertNotSame + $I->assertRegExp("/foo/", "foo", "pass"); // stepKey: assertRegExp + $I->assertSame("bar", "bar", "pass"); // stepKey: assertSame + $I->assertStringStartsNotWith("a", "banana", "pass"); // stepKey: assertStringStartsNotWith + $I->assertStringStartsWith("a", "apple", "pass"); // stepKey: assertStringStartsWith + $I->assertTrue(true, "pass"); // stepKey: assertTrue $I->comment("asserts backward compatible"); - $I->assertArrayHasKey("apple", ['orange' => 2, 'apple' => 1], "pass"); - $I->assertArrayNotHasKey("kiwi", ['orange' => 2, 'apple' => 1], "pass"); - $I->assertArraySubset([1, 2], [1, 2, 3, 5], "pass"); - $I->assertContains("ab", ['item1' => 'a', 'item2' => 'ab'], "pass"); - $I->assertCount(2, ['a', 'b'], "pass"); - $I->assertEmpty([], "pass"); - $I->assertEquals($text, "Copyright © 2013-2017 Magento, Inc. All rights reserved.", "pass"); - $I->assertEquals("Copyright © 2013-2017 Magento, Inc. All rights reserved.", $text, "pass"); - $I->assertFalse(false, "pass"); - $I->assertFileNotExists("/out.txt", "pass"); - $I->assertFileNotExists($text, "pass"); - $I->assertGreaterOrEquals(2, 5, "pass"); - $I->assertGreaterThan(2, 5, "pass"); - $I->assertGreaterThanOrEqual(2, 5, "pass"); - $I->assertInternalType("string", "xyz", "pass"); - $I->assertInternalType("int", 21, "pass"); - $I->assertInternalType("string", $text, "pass"); - $I->assertLessOrEquals(5, 2, "pass"); - $I->assertLessThan(5, 2, "pass"); - $I->assertLessThanOrEqual(5, 2, "pass"); - $I->assertNotContains("bc", ['item1' => 'a', 'item2' => 'ab'], "pass"); - $I->assertNotContains("bc", $text, "pass"); - $I->assertNotEmpty([1, 2], "pass"); - $I->assertNotEmpty($text, "pass"); - $I->assertNotEquals(2, 5, "pass", 0); - $I->assertNotNull("abc", "pass"); - $I->assertNotNull($text, "pass"); - $I->assertNotRegExp("/foo/", "bar", "pass"); - $I->assertNotSame("log", "tag", "pass"); - $I->assertRegExp("/foo/", "foo", "pass"); - $I->assertSame("bar", "bar", "pass"); - $I->assertStringStartsNotWith("a", "banana", "pass"); - $I->assertStringStartsWith("a", "apple", "pass"); - $I->assertTrue(true, "pass"); - $I->assertElementContainsAttribute("#username", "class", "admin__control-text"); - $I->assertInstanceOf(User::class, $text, "pass"); - $I->assertNotInstanceOf(User::class, 21, "pass"); - $I->assertFileExists($text, "pass"); - $I->assertIsEmpty($text, "pass"); - $I->assertNull($text, "pass"); - $I->expectException(new MyException('exception msg'), function() {$this->doSomethingBad();}); + $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->assertContains("ab", ['item1' => 'a', 'item2' => 'ab'], "pass"); // stepKey: assertContainsBackwardCompatible + $I->assertCount(2, ['a', 'b'], "pass"); // stepKey: assertCountBackwardCompatible + $I->assertEmpty([], "pass"); // stepKey: assertEmptyBackwardCompatible + $I->assertEquals($text, "Copyright © 2013-2017 Magento, Inc. All rights reserved.", "pass"); // stepKey: assertEquals1BackwardCompatible + $I->assertEquals("Copyright © 2013-2017 Magento, Inc. All rights reserved.", $text, "pass"); // stepKey: assertEquals2BackwardCompatible + $I->assertFalse(false, "pass"); // stepKey: assertFalse1BackwardCompatible + $I->assertFileNotExists("/out.txt", "pass"); // stepKey: assertFileNotExists1BackwardCompatible + $I->assertFileNotExists($text, "pass"); // stepKey: assertFileNotExists2BackwardCompatible + $I->assertGreaterOrEquals(2, 5, "pass"); // stepKey: assertGreaterOrEqualsBackwardCompatible + $I->assertGreaterThan(2, 5, "pass"); // stepKey: assertGreaterThanBackwardCompatible + $I->assertGreaterThanOrEqual(2, 5, "pass"); // stepKey: assertGreaterThanOrEqualBackwardCompatible + $I->assertLessOrEquals(5, 2, "pass"); // stepKey: assertLessOrEqualBackwardCompatibles + $I->assertLessThan(5, 2, "pass"); // stepKey: assertLessThanBackwardCompatible + $I->assertLessThanOrEqual(5, 2, "pass"); // stepKey: assertLessThanOrEqualBackwardCompatible + $I->assertNotContains("bc", ['item1' => 'a', 'item2' => 'ab'], "pass"); // stepKey: assertNotContains1BackwardCompatible + $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"); // stepKey: assertNotEqualsBackwardCompatible + $I->assertNotNull("abc", "pass"); // stepKey: assertNotNull1BackwardCompatible + $I->assertNotNull($text, "pass"); // stepKey: assertNotNull2BackwardCompatible + $I->assertNotRegExp("/foo/", "bar", "pass"); // stepKey: assertNotRegExpBackwardCompatible + $I->assertNotSame("log", "tag", "pass"); // stepKey: assertNotSameBackwardCompatible + $I->assertRegExp("/foo/", "foo", "pass"); // stepKey: assertRegExpBackwardCompatible + $I->assertSame("bar", "bar", "pass"); // stepKey: assertSameBackwardCompatible + $I->assertStringStartsNotWith("a", "banana", "pass"); // stepKey: assertStringStartsNotWithBackwardCompatible + $I->assertStringStartsWith("a", "apple", "pass"); // stepKey: assertStringStartsWithBackwardCompatible + $I->assertTrue(true, "pass"); // stepKey: assertTrueBackwardCompatible + $I->assertElementContainsAttribute("#username", "class", "admin__control-text"); // stepKey: assertElementContainsAttributeBackwardCompatible + $I->assertInstanceOf(User::class, $text, "pass"); // stepKey: assertInstanceOfBackwardCompatible + $I->assertNotInstanceOf(User::class, 21, "pass"); // stepKey: assertNotInstanceOfBackwardCompatible + $I->assertFileExists($text, "pass"); // stepKey: assertFileExistsBackwardCompatible + $I->assertIsEmpty($text, "pass"); // stepKey: assertIsEmptyBackwardCompatible + $I->assertNull($text, "pass"); // stepKey: assertNullBackwardCompatible + $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"); - $I->assertStringStartsNotWith("W", PersistedObjectHandler::getInstance()->retrieveEntityField('createData2', 'firstname', 'test') . ", " . PersistedObjectHandler::getInstance()->retrieveEntityField('createData2', 'lastname', 'test'), "pass"); - $I->assertEquals(PersistedObjectHandler::getInstance()->retrieveEntityField('createData1', 'lastname', 'test'), PersistedObjectHandler::getInstance()->retrieveEntityField('createData1', 'lastname', 'test'), "pass"); + $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->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"); - $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"); - $I->assertArrayHasKey("lastname", ['lastname' => PersistedObjectHandler::getInstance()->retrieveEntityField('createData1', 'lastname', 'test'), 'firstname' => PersistedObjectHandler::getInstance()->retrieveEntityField('createData1', 'firstname', 'test')], "pass"); - $I->assertArrayHasKey("lastname", ['lastname' => PersistedObjectHandler::getInstance()->retrieveEntityField('createData2', 'lastname', 'test'), 'firstname' => PersistedObjectHandler::getInstance()->retrieveEntityField('createData2', 'firstname', 'test')], "pass"); - $I->assertInstanceOf(User::class, $text, "pass"); - $I->assertNotInstanceOf(User::class, 21, "pass"); - $I->assertFileExists($text, "pass"); - $I->assertFileExists("AssertCest.php", "pass"); - $I->assertIsEmpty($text, "pass"); - $I->assertNull($text, "pass"); - $I->expectException(new MyException('exception msg'), function() {$this->doSomethingBad();}); - $I->fail("fail"); - $I->fail(PersistedObjectHandler::getInstance()->retrieveEntityField('createData2', 'firstname', 'test') . " " . PersistedObjectHandler::getInstance()->retrieveEntityField('createData2', 'lastname', 'test')); - $I->fail(PersistedObjectHandler::getInstance()->retrieveEntityField('createData1', 'firstname', 'test') . " " . PersistedObjectHandler::getInstance()->retrieveEntityField('createData1', 'lastname', 'test')); - $I->assertElementContainsAttribute("#username", "class", "admin__control-text"); - $I->assertElementContainsAttribute("#username", "name", "login[username]"); - $I->assertElementContainsAttribute("#username", "autofocus", "true"); - $I->assertElementContainsAttribute("#username", "data-validate", "{required:true}"); - $I->assertElementContainsAttribute(".admin__menu-overlay", "style", "display: none;"); - $I->assertElementContainsAttribute(".admin__menu-overlay", "border", "0"); - $I->assertElementContainsAttribute("#username", "value", PersistedObjectHandler::getInstance()->retrieveEntityField('createData2', 'firstname', 'test')); - $I->assertElementContainsAttribute("#username", "value", PersistedObjectHandler::getInstance()->retrieveEntityField('createData1', 'firstname', 'test')); - $I->assertEquals("John", "Doe", "pass"); + $I->comment("array type that use created data"); + $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 + $I->assertFileExists($text, "pass"); // stepKey: assertFileExists2 + $I->assertFileExists("AssertCest.php", "pass"); // stepKey: assertFileExists3 + $I->assertIsEmpty($text, "pass"); // stepKey: assertIsEmpty + $I->assertNull($text, "pass"); // stepKey: assertNull + $I->expectException(new MyException('exception msg'), function() {$this->doSomethingBad();}); // stepKey: expectException + $I->fail("fail"); // stepKey: fail + $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 + $I->assertElementContainsAttribute("#username", "autofocus", "true"); // stepKey: assertElementContainsAttribute3 + $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", $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 d6d953a88..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,41 +14,62 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @group functional + * @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->amGoingTo("create entity that has the stepKey: createPersonParam"); - PersistedObjectHandler::getInstance()->createEntity( - "createPersonParam", - "hook", - "ReplacementPerson", - [], - null - ); - $I->fillField("#foo", "myData1"); - $I->fillField("#bar", "myData2"); + $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 */ public function BasicActionGroupTest(AcceptanceTester $I) { - $I->amOnPage("/someUrl"); - $I->fillField("#foo", "myData1"); - $I->fillField("#bar", "myData2"); - $I->click("loginButton"); + $I->amOnPage("/someUrl"); // stepKey: step1 + $I->comment("Entering Action Group [actionGroup1] FunctionalActionGroup"); + $I->fillField("#foo", "myData1"); // stepKey: fillField1ActionGroup1 + $I->fillField("#bar", "myData2"); // stepKey: fillField2ActionGroup1 + $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 493d3c2c0..5016fba93 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,16 +15,24 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @Title("[NO TESTCASEID]: A Functional Cest") * @group functional + * @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->amOnPage("/beforeUrl"); + $I->comment('[START BEFORE HOOK]'); + $I->amOnPage("/beforeUrl"); // stepKey: beforeAmOnPageKey + $I->comment('[END BEFORE HOOK]'); } /** @@ -35,7 +41,12 @@ class BasicFunctionalTestCest */ public function _after(AcceptanceTester $I) { - $I->amOnPage("/afterUrl"); + $I->comment('[START AFTER HOOK]'); + $I->amOnPage("/afterUrl"); // stepKey: afterAmOnPageKey + $I->comment('[END AFTER HOOK]'); + if ($this->isSuccess) { + unlink(__FILE__); + } } /** @@ -44,14 +55,13 @@ class BasicFunctionalTestCest */ public function _failed(AcceptanceTester $I) { - $I->saveScreenshot(); + $I->saveScreenshot(); // stepKey: saveScreenshot } /** * @Severity(level = SeverityLevel::CRITICAL) * @Features({"TestModule"}) * @Stories({"MQE-305"}) - * @Parameter(name = "AcceptanceTester", value="$I") * @param AcceptanceTester $I * @return void * @throws \Exception @@ -60,113 +70,138 @@ class BasicFunctionalTestCest { $I->comment(""); $I->comment(""); - $I->skipReadinessCheck(true); - $I->comment("skipReadiness"); - $I->skipReadinessCheck(false); - $someVarDefinition = $I->grabValueFrom(); - $I->acceptPopup(); - $I->amOnPage("/test/url"); - $I->appendField(".functionalTestSelector"); - $I->attachFile(".functionalTestSelector", "testFileAttachment"); - $I->cancelPopup(); - $I->checkOption(".functionalTestSelector"); - $I->click(".functionalTestSelector"); - $I->clickWithLeftButton(".functionalTestSelector"); - $I->clickWithRightButton(".functionalTestSelector"); - $I->clickWithLeftButton("#element#element .200", 200, 300); - $I->clickWithRightButton("#element .4123#element", 200, 300); - $I->closeTab(); - $I->conditionalClick(".functionalTestSelector", ".functionalDependentTestSelector", true); - $I->amGoingTo("delete entity that has the createDataKey: createKey1"); - PersistedObjectHandler::getInstance()->deleteEntity( - "createKey1", - "test" - ); - $I->deleteEntityByUrl("/V1/categories{$grabbedData}"); - $I->dontSee("someInput", ".functionalTestSelector"); - $I->dontSeeCheckboxIsChecked(".functionalTestSelector"); - $I->dontSeeCookie("someInput"); - $I->dontSeeCurrentUrlEquals("/functionalUrl"); - $I->dontSeeCurrentUrlMatches("/[0-9]+/"); - $I->dontSeeElement(".functionalTestSelector"); - $I->dontSeeElementInDOM(".functionalTestSelector"); - $I->dontSeeInCurrentUrl("/functionalUrl"); - $I->dontSeeInField(".functionalTestSelector"); - $I->dontSeeInPageSource("someInput"); - $I->dontSeeInSource("<myHtmlHere>"); - $I->dontSeeInTitle("someInput"); - $I->dontSeeLink("someInput", "/functionalUrl"); - $I->dontSeeOptionIsSelected(".functionalTestSelector", "someInput"); - $I->doubleClick(".functionalTestSelector"); - $I->dragAndDrop(".functionalTestSelector", ".functionalTestSelector2"); - $I->dragAndDrop(".functionalTestSelector", ".functionalTestSelector2", 100, 900); - $executeJSKey1 = $I->executeJS("someJSFunction"); - $I->fillField(".functionalTestSelector", "someInput"); - $I->fillField(".functionalTestSelector", "0"); + $I->comment("seeComment"); + $someVarDefinition = $I->grabValueFrom(); // stepKey: someVarDefinition + $I->acceptPopup(); // stepKey: acceptPopupKey1 + $I->amOnPage("/test/url"); // stepKey: amOnPageKey1 + $I->appendField(".functionalTestSelector"); // stepKey: appendFieldKey1 + $I->attachFile(".functionalTestSelector", "testFileAttachment"); // stepKey: attachFileKey1 + $I->cancelPopup(); // stepKey: cancelPopupKey1 + $I->checkOption(".functionalTestSelector"); // stepKey: checkOptionKey1 + $I->click(".functionalTestSelector"); // stepKey: clickKey1 + $I->clickWithLeftButton(".functionalTestSelector"); // stepKey: clickWithLeftButtonKey1 + $I->clickWithRightButton(".functionalTestSelector"); // stepKey: clickWithRightButtonKey1 + $I->clickWithLeftButton("#element#element .200", 200, 300); // stepKey: clickWithLeftButtonKeyXY1 + $I->clickWithRightButton("#element .4123#element", 200, 300); // stepKey: clickWithRightButtonKeyXY1 + $I->closeTab(); // stepKey: closeTabKey1 + $I->conditionalClick(".functionalTestSelector", ".functionalDependentTestSelector", true); // stepKey: conditionalClickKey1 + $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 + $I->dontSeeCurrentUrlEquals("/functionalUrl"); // stepKey: dontSeeCurrentUrlEqualsKey1 + $I->dontSeeCurrentUrlMatches("/[0-9]+/"); // stepKey: dontSeeCurrentUrlMatchesKey1 + $I->dontSeeElement(".functionalTestSelector"); // stepKey: dontSeeElementKey1 + $I->dontSeeElementInDOM(".functionalTestSelector"); // stepKey: dontSeeElementInDOMKey1 + $I->dontSeeInCurrentUrl("/functionalUrl"); // stepKey: dontSeeInCurrentUrlKey1 + $I->dontSeeInField(".functionalTestSelector"); // stepKey: dontSeeInFieldKey1 + $I->dontSeeInPageSource("Cosmo Kramer"); // stepKey: dontSeeInPageSourceKey1 + $I->dontSeeInPageSource("<p>Jerry Seinfeld</p>"); // stepKey: dontSeeInPageSourceKey2 + $I->dontSeeInPageSource("Cosmo Kramer"); // stepKey: dontSeeInPageSourceKey3 + $I->dontSeeInPageSource("<p>Jerry Seinfeld</p>"); // stepKey: dontSeeInPageSourceKey4 + $I->dontSeeInPageSource("foo"); // stepKey: dontSeeInPageSourceKey5 + $I->dontSeeInPageSource("<p>foo</p>"); // stepKey: dontSeeInPageSourceKey6 + $I->dontSeeInSource("Cosmo Kramer"); // stepKey: dontSeeInSourceKey1 + $I->dontSeeInSource("<p>Jerry Seinfeld</p>"); // stepKey: dontSeeInSourceKey2 + $I->dontSeeInTitle("someInput"); // stepKey: dontSeeInTitleKey1 + $I->dontSeeLink("someInput", "/functionalUrl"); // stepKey: dontSeeLinkKey1 + $I->dontSeeOptionIsSelected(".functionalTestSelector", "someInput"); // stepKey: dontSeeOptionIsSelectedKey1 + $I->doubleClick(".functionalTestSelector"); // stepKey: doubleClickKey1 + $I->dragAndDrop(".functionalTestSelector", ".functionalTestSelector2"); // stepKey: dragAndDropKey1 + $I->dragAndDrop(".functionalTestSelector", ".functionalTestSelector2", 100, 900); // stepKey: dragAndDropKey2 + $executeJSKey1 = $I->executeJS("someJSFunction"); // stepKey: executeJSKey1 + $I->fillField(".functionalTestSelector", "someInput"); // stepKey: fillFieldKey1 + $I->fillField(".functionalTestSelector", "0"); // stepKey: fillFieldKey2 $date = new \DateTime(); $date->setTimestamp(strtotime("Now")); $date->setTimezone(new \DateTimeZone("America/Los_Angeles")); $generateDateKey = $date->format("H:i:s"); + $date = new \DateTime(); $date->setTimestamp(strtotime("Now")); $date->setTimezone(new \DateTimeZone("UTC")); $generateDateKey2 = $date->format("H:i:s"); - $grabAttributeFromKey1 = $I->grabAttributeFrom(".functionalTestSelector", "someInput"); - $grabCookieKey1 = $I->grabCookie("grabCookieInput", ['domain' => 'www.google.com']); - $grabFromCurrentUrlKey1 = $I->grabFromCurrentUrl("/grabCurrentUrl"); - $grabMultipleKey1 = $I->grabMultiple(".functionalTestSelector"); - $grabTextFromKey1 = $I->grabTextFrom(".functionalTestSelector"); - $grabValueFromKey1 = $I->grabValueFrom(".functionalTestSelector"); - $magentoCli1 = $I->magentoCLI("maintenance:enable", "\"stuffHere\""); + + $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", 60, "\"stuffHere\""); // stepKey: magentoCli1 $I->comment($magentoCli1); - $I->makeScreenshot("screenShotInput"); - $I->maximizeWindow(); - $I->moveBack(); - $I->moveForward(); - $I->moveMouseOver(".functionalTestSelector"); - $I->openNewTab(); - $I->pauseExecution(); - $I->performOn("#selector", function(\WebDriverElement $el) {return $el->isDisplayed();}); - $I->pressKey("#page", "a"); - $I->pressKey("#page", ['ctrl', 'a'],'new'); - $I->pressKey("#page", ['shift', '111'],'1','x'); - $I->pressKey("#page", ['ctrl', 'a'],\Facebook\WebDriver\WebDriverKeys::DELETE); - $I->reloadPage(); - $I->resetCookie("cookieInput"); - $I->resizeWindow(0, 0); - $I->scrollTo(".functionalTestSelector"); - $I->see("someInput", ".functionalTestSelector"); - $I->seeCheckboxIsChecked(".functionalTestSelector"); - $I->seeCookie("someInput"); - $I->seeCurrentUrlEquals("/functionalUrl"); - $I->seeCurrentUrlMatches("/[0-9]+/"); - $I->seeElement(".functionalTestSelector"); - $I->seeElementInDOM(".functionalTestSelector"); - $I->seeInCurrentUrl("/functionalUrl"); - $I->seeInField(".functionalTestSelector", "someInput"); - $I->seeInPageSource("<myHtmlHere>"); - $I->seeInPopup("someInput"); - $I->seeInSource("<myHtmlHere>"); - $I->seeInTitle("someInput"); - $I->seeLink("someInput", "/functionalUrl"); - $I->seeNumberOfElements(".functionalTestSelector"); - $I->seeOptionIsSelected(".functionalTestSelector", "someInput"); - $I->selectOption(".functionalTestSelector"); - $I->selectMultipleOptions(".filter", ".option", ['opt1', 'opt2']); - $I->setCookie("someInput", "someCookieValue"); - $I->switchToIFrame("someInput"); - $I->switchToNextTab(); - $I->switchToPreviousTab(); - $I->switchToWindow(); - $I->typeInPopup("someInput"); - $I->uncheckOption(".functionalTestSelector"); - $I->unselectOption(".functionalTestSelector", "someInput"); - $I->wait(30); - $I->waitForElement(".functionalTestSelector", 30); - $I->waitForElementNotVisible(".functionalTestSelector", 30); - $I->waitForElementVisible(".functionalTestSelector", 30); - $I->waitForElementChange("#selector", function(\WebDriverElement $el) {return $el->isDisplayed();}); - $I->waitForJS("someJsFunction", 30); - $I->waitForText("someInput", 30, ".functionalTestSelector"); + $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->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 + $I->pressKey("#page", ['ctrl', 'a'],\Facebook\WebDriver\WebDriverKeys::DELETE); // stepKey: pressKey4 + $I->reloadPage(); // stepKey: reloadPageKey1 + $I->resetCookie("cookieInput"); // stepKey: resetCookieKey1 + $I->resizeWindow(0, 0); // stepKey: resizeWindowKey1 + $I->scrollTo(".functionalTestSelector"); // stepKey: scrollToKey1 + $I->see("someInput", ".functionalTestSelector"); // stepKey: seeKey1 + $I->seeCheckboxIsChecked(".functionalTestSelector"); // stepKey: seeCheckboxIsCheckedKey1 + $I->seeCookie("someInput"); // stepKey: seeCookieKey1 + $I->seeCurrentUrlEquals("/functionalUrl"); // stepKey: seeCurrentUrlEqualsKey1 + $I->seeCurrentUrlMatches("/[0-9]+/"); // stepKey: seeCurrentUrlMatchesKey1 + $I->seeElement(".functionalTestSelector"); // stepKey: seeElementKey1 + $I->seeElementInDOM(".functionalTestSelector"); // stepKey: seeElementInDOMKey1 + $I->seeInCurrentUrl("/functionalUrl"); // stepKey: seeInCurrentUrlKey1 + $I->seeInField(".functionalTestSelector", "someInput"); // stepKey: seeInFieldKey1 + $I->seeInSecretField(".functionalTestSelector", $I->getSecret("someKey")); // stepKey: seeInFieldKey2 + $I->seeInPageSource("Home Page"); // stepKey: seeInPageSourceKey1 + $I->seeInPageSource("<h1 class=\"page-title\">"); // stepKey: seeInPageSourceKey2 + $I->seeInPopup("someInput"); // stepKey: seeInPopupKey1 + $I->seeInSource("Home Page"); // stepKey: seeInSourceKey1 + $I->seeInSource("<h1 class=\"page-title\">"); // stepKey: seeInSourceKey2 + $I->seeInTitle("someInput"); // stepKey: seeInTitleKey1 + $I->seeLink("someInput", "/functionalUrl"); // stepKey: seeLinkKey1 + $I->seeNumberOfElements(".functionalTestSelector"); // stepKey: seeNumberOfElementsKey1 + $I->seeOptionIsSelected(".functionalTestSelector", "someInput"); // stepKey: seeOptionIsSelectedKey1 + $I->selectOption(".functionalTestSelector"); // stepKey: selectOptionKey1 + $I->selectMultipleOptions(".filter", ".option", ['opt1', 'opt2']); // stepKey: selectMultipleOpts1 + $I->setCookie("someInput", "someCookieValue"); // stepKey: setCookieKey1 + $I->switchToIFrame("someInput"); // stepKey: switchToIFrameKey1 + $I->switchToNextTab(); // stepKey: switchToNextTabKey1 + $I->switchToPreviousTab(); // stepKey: switchToPreviousTabKey1 + $I->switchToWindow(); // stepKey: switchToWindowKey1 + $I->typeInPopup("someInput"); // stepKey: typeInPopupKey1 + $I->uncheckOption(".functionalTestSelector"); // stepKey: uncheckOptionKey1 + $I->unselectOption(".functionalTestSelector", "someInput"); // stepKey: unselectOptionKey1 + $I->wait(30); // stepKey: waitKey1 + $I->waitForElement(".functionalTestSelector", 30); // stepKey: waitForElementKey1 + $I->waitForElementNotVisible(".functionalTestSelector", 30); // stepKey: waitForElementNotVisibleKey1 + $I->waitForElementVisible(".functionalTestSelector", 30); // stepKey: waitForElementVisibleKey1 + $I->waitForElementChange("#selector", function(\WebDriverElement $el) {return $el->isDisplayed();}, 10); // stepKey: waitForElementChangeKey1 + $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 039e31a58..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,17 +16,25 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; * @Title("[NO TESTCASEID]: BasicMergeTest") * @group functional * @group mergeTest + * @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->amOnPage("/beforeUrl"); - $I->see("#before2"); + $I->comment('[START BEFORE HOOK]'); + $I->amOnPage("/beforeUrl"); // stepKey: before1 + $I->see("#before2"); // stepKey: before2 + $I->comment('[END BEFORE HOOK]'); } /** @@ -37,7 +43,12 @@ class BasicMergeTestCest */ public function _after(AcceptanceTester $I) { - $I->amOnPage("/afterUrl1"); + $I->comment('[START AFTER HOOK]'); + $I->amOnPage("/afterUrl1"); // stepKey: after1 + $I->comment('[END AFTER HOOK]'); + if ($this->isSuccess) { + unlink(__FILE__); + } } /** @@ -46,32 +57,39 @@ class BasicMergeTestCest */ public function _failed(AcceptanceTester $I) { - $I->saveScreenshot(); + $I->saveScreenshot(); // stepKey: saveScreenshot } /** * @Severity(level = SeverityLevel::CRITICAL) * @Features({"TestModule"}) * @Stories({"MQE-433"}) - * @Parameter(name = "AcceptanceTester", value="$I") * @param AcceptanceTester $I * @return void * @throws \Exception */ public function BasicMergeTest(AcceptanceTester $I) { - $I->amOnPage("/step1"); - $I->click("#step2"); - $I->fillField("#username", "step3"); - $I->click("#step4"); - $I->fillField("#password", "step5"); - $I->click("#step6Merged"); - $I->click("#element .Jane .step7Merge"); - $I->amOnPage("/Jane/Dane.html"); - $I->fillField("#foo", "Jane"); - $I->fillField("#bar", "Dane"); - $I->searchAndMultiSelectOption("#foo", ["Jane", "Dane"]); - $I->see("#element .Jane"); - $I->click("#step10MergedInResult"); + $I->amOnPage("/step1"); // stepKey: step1 + $I->click("#step2"); // stepKey: step2 + $I->fillField("#username", "step3"); // stepKey: step3 + $I->click("#step4"); // stepKey: step4 + $I->fillField("#password", "step5"); // stepKey: step5 + $I->click("#step6Merged"); // stepKey: step6Merge + $I->click("#element .Jane .step7Merge"); // stepKey: step7Merge + $I->comment("Entering Action Group [step8Merge] FunctionalActionGroupWithData"); + $I->amOnPage("/Jane/Dane.html"); // stepKey: amOnPage1Step8Merge + $I->fillField("#foo", "Jane"); // stepKey: fillField1Step8Merge + $I->fillField("#bar", "Dane"); // stepKey: fillField2Step8Merge + $I->searchAndMultiSelectOption("#foo", ["Jane", "Dane"]); // stepKey: multi1Step8Merge + $I->see("#element .Jane"); // stepKey: see1Step8Merge + $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 c1fddf69b..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,25 +13,47 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** + * @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 */ public function CharacterReplacementTest(AcceptanceTester $I) { - $I->click("#element"); - $I->fillField("#element", "7700 West Parmer Lane"); - $I->click("#element .abcdefghijklmnopqrstuvwxyz1234567890"); - $I->click("#element .`~!@#$%^&*()-_=+{}[]|\;:\".,></?()3., "); - $I->click("#element .words, and, commas, and, spaces"); - $I->click("#abcdefghijklmnopqrstuvwxyz1234567890 .abcdefghijklmnopqrstuvwxyz1234567890"); - $I->click("#`~!@#$%^&*()-_=+{}[]|\;:\".,></?() .`~!@#$%^&*()-_=+{}[]|\;:\".,></?()"); - $I->click("#words, and, commas, and, spaces .words, and, commas, and, spaces"); + $I->click("#element"); // stepKey: charsInSectionElement + $I->fillField("#element", "7700 West Parmer Lane"); // stepKey: charsInDataRef + $I->click("#element .abcdefghijklmnopqrstuvwxyz1234567890"); // stepKey: allChars1 + $I->click("#element .`~!@#$%^&*()-_=+{}[]|\;:\".,></?()3., "); // stepKey: allChars2 + $I->click("#element .words, and, commas, and, spaces"); // stepKey: allChars3 + $I->click("#abcdefghijklmnopqrstuvwxyz1234567890 .abcdefghijklmnopqrstuvwxyz1234567890"); // stepKey: allChars4 + $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 27c9a99c6..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,16 +15,24 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @Title("[NO TESTCASEID]: ChildExtendedTestAddHooks") * @group Parent + * @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->amOnPage("/beforeUrl"); + $I->comment('[START BEFORE HOOK]'); + $I->amOnPage("/beforeUrl"); // stepKey: beforeAmOnPageKey + $I->comment('[END BEFORE HOOK]'); } /** @@ -35,7 +41,12 @@ class ChildExtendedTestAddHooksCest */ public function _after(AcceptanceTester $I) { - $I->amOnPage("/afterUrl"); + $I->comment('[START AFTER HOOK]'); + $I->amOnPage("/afterUrl"); // stepKey: afterAmOnPageKey + $I->comment('[END AFTER HOOK]'); + if ($this->isSuccess) { + unlink(__FILE__); + } } /** @@ -44,14 +55,13 @@ class ChildExtendedTestAddHooksCest */ public function _failed(AcceptanceTester $I) { - $I->saveScreenshot(); + $I->saveScreenshot(); // stepKey: saveScreenshot } /** * @Severity(level = SeverityLevel::MINOR) * @Features({"TestModule"}) * @Stories({"Parent"}) - * @Parameter(name = "AcceptanceTester", value="$I") * @param AcceptanceTester $I * @return void * @throws \Exception @@ -59,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 788818272..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,18 +15,26 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @Title("[NO TESTCASEID]: ChildExtendedTestMerging") * @group Child + * @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->amOnPage("/firstUrl"); - $I->amOnPage("/beforeUrl"); - $I->amOnPage("/lastUrl"); + $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]'); } /** @@ -37,7 +43,12 @@ class ChildExtendedTestMergingCest */ public function _after(AcceptanceTester $I) { - $I->amOnPage("/afterUrl"); + $I->comment('[START AFTER HOOK]'); + $I->amOnPage("/afterUrl"); // stepKey: afterAmOnPageKey + $I->comment('[END AFTER HOOK]'); + if ($this->isSuccess) { + unlink(__FILE__); + } } /** @@ -46,14 +57,13 @@ class ChildExtendedTestMergingCest */ public function _failed(AcceptanceTester $I) { - $I->saveScreenshot(); + $I->saveScreenshot(); // stepKey: saveScreenshot } /** * @Severity(level = SeverityLevel::TRIVIAL) * @Features({"TestModule"}) * @Stories({"Child"}) - * @Parameter(name = "AcceptanceTester", value="$I") * @param AcceptanceTester $I * @return void * @throws \Exception @@ -65,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 cea7ffeca..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,21 +15,38 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @Title("[NO TESTCASEID]: ChildExtendedTestNoParent") * @group Child + * @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) { - $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:No issues have been specified."); } } diff --git a/dev/tests/verification/Resources/ChildExtendedTestRemoveAction.txt b/dev/tests/verification/Resources/ChildExtendedTestRemoveAction.txt index 40a3184c9..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,16 +15,24 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @Title("[NO TESTCASEID]: ChildExtendedTestRemoveAction") * @group Child + * @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->amOnPage("/beforeUrl"); + $I->comment('[START BEFORE HOOK]'); + $I->amOnPage("/beforeUrl"); // stepKey: beforeAmOnPageKey + $I->comment('[END BEFORE HOOK]'); } /** @@ -35,7 +41,12 @@ class ChildExtendedTestRemoveActionCest */ public function _after(AcceptanceTester $I) { - $I->amOnPage("/afterUrl"); + $I->comment('[START AFTER HOOK]'); + $I->amOnPage("/afterUrl"); // stepKey: afterAmOnPageKey + $I->comment('[END AFTER HOOK]'); + if ($this->isSuccess) { + unlink(__FILE__); + } } /** @@ -44,14 +55,13 @@ class ChildExtendedTestRemoveActionCest */ public function _failed(AcceptanceTester $I) { - $I->saveScreenshot(); + $I->saveScreenshot(); // stepKey: saveScreenshot } /** * @Severity(level = SeverityLevel::CRITICAL) * @Features({"TestModule"}) * @Stories({"Child"}) - * @Parameter(name = "AcceptanceTester", value="$I") * @param AcceptanceTester $I * @return void * @throws \Exception @@ -59,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 7419e681a..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,9 +15,15 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @Title("[NO TESTCASEID]: ChildExtendedTestRemoveHookAction") * @group Child + * @Description("<h3>Test files</h3>verification/TestModule/Test/ExtendedFunctionalTest/ChildExtendedTestRemoveHookAction.xml<br>") */ class ChildExtendedTestRemoveHookActionCest { + /** + * @var bool + */ + private $isSuccess = false; + /** * @param AcceptanceTester $I * @throws \Exception @@ -34,7 +38,12 @@ class ChildExtendedTestRemoveHookActionCest */ public function _after(AcceptanceTester $I) { - $I->amOnPage("/afterUrl"); + $I->comment('[START AFTER HOOK]'); + $I->amOnPage("/afterUrl"); // stepKey: afterAmOnPageKey + $I->comment('[END AFTER HOOK]'); + if ($this->isSuccess) { + unlink(__FILE__); + } } /** @@ -43,14 +52,13 @@ class ChildExtendedTestRemoveHookActionCest */ public function _failed(AcceptanceTester $I) { - $I->saveScreenshot(); + $I->saveScreenshot(); // stepKey: saveScreenshot } /** * @Severity(level = SeverityLevel::CRITICAL) * @Features({"TestModule"}) * @Stories({"Child"}) - * @Parameter(name = "AcceptanceTester", value="$I") * @param AcceptanceTester $I * @return void * @throws \Exception @@ -59,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 18b1984c0..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,16 +15,24 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @Title("[NO TESTCASEID]: ChildExtendedTestReplace") * @group Child + * @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->amOnPage("/beforeUrl"); + $I->comment('[START BEFORE HOOK]'); + $I->amOnPage("/beforeUrl"); // stepKey: beforeAmOnPageKey + $I->comment('[END BEFORE HOOK]'); } /** @@ -35,7 +41,12 @@ class ChildExtendedTestReplaceCest */ public function _after(AcceptanceTester $I) { - $I->amOnPage("/afterUrl"); + $I->comment('[START AFTER HOOK]'); + $I->amOnPage("/afterUrl"); // stepKey: afterAmOnPageKey + $I->comment('[END AFTER HOOK]'); + if ($this->isSuccess) { + unlink(__FILE__); + } } /** @@ -44,14 +55,13 @@ class ChildExtendedTestReplaceCest */ public function _failed(AcceptanceTester $I) { - $I->saveScreenshot(); + $I->saveScreenshot(); // stepKey: saveScreenshot } /** * @Severity(level = SeverityLevel::TRIVIAL) * @Features({"TestModule"}) * @Stories({"Child"}) - * @Parameter(name = "AcceptanceTester", value="$I") * @param AcceptanceTester $I * @return void * @throws \Exception @@ -60,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 90ebf3676..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,16 +15,24 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @Title("[NO TESTCASEID]: ChildExtendedTestReplaceHook") * @group Child + * @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->amOnPage("/slightlyDifferentBeforeUrl"); + $I->comment('[START BEFORE HOOK]'); + $I->amOnPage("/slightlyDifferentBeforeUrl"); // stepKey: beforeAmOnPageKey + $I->comment('[END BEFORE HOOK]'); } /** @@ -35,7 +41,12 @@ class ChildExtendedTestReplaceHookCest */ public function _after(AcceptanceTester $I) { - $I->amOnPage("/afterUrl"); + $I->comment('[START AFTER HOOK]'); + $I->amOnPage("/afterUrl"); // stepKey: afterAmOnPageKey + $I->comment('[END AFTER HOOK]'); + if ($this->isSuccess) { + unlink(__FILE__); + } } /** @@ -44,14 +55,13 @@ class ChildExtendedTestReplaceHookCest */ public function _failed(AcceptanceTester $I) { - $I->saveScreenshot(); + $I->saveScreenshot(); // stepKey: saveScreenshot } /** * @Severity(level = SeverityLevel::TRIVIAL) * @Features({"TestModule"}) * @Stories({"Child"}) - * @Parameter(name = "AcceptanceTester", value="$I") * @param AcceptanceTester $I * @return void * @throws \Exception @@ -60,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 cac3b72cd..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,77 +13,57 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** + * @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->amGoingTo("create entity that has the stepKey: createdInBefore"); - PersistedObjectHandler::getInstance()->createEntity( - "createdInBefore", - "hook", - "entity", - [], - null - ); - $I->amGoingTo("update entity that has the createdDataKey: createdInBefore"); - PersistedObjectHandler::getInstance()->updateEntity( - "createdInBefore", - "hook", - "entity", - [] - ); - $I->amGoingTo("delete entity that has the createDataKey: 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->amGoingTo("create entity that has the stepKey: createdInTest"); - PersistedObjectHandler::getInstance()->createEntity( - "createdInTest", - "test", - "entity", - [], - null - ); - $I->amGoingTo("update entity that has the createdDataKey: createdInTest"); - PersistedObjectHandler::getInstance()->updateEntity( - "createdInTest", - "test", - "entity", - [] - ); - $I->amGoingTo("delete entity that has the createDataKey: createdInTest"); - PersistedObjectHandler::getInstance()->deleteEntity( - "createdInTest", - "test" - ); - $I->amGoingTo("update entity that has the createdDataKey: createdInBefore"); - PersistedObjectHandler::getInstance()->updateEntity( - "createdInBefore", - "test", - "entity", - [] - ); - $I->amGoingTo("delete entity that has the createDataKey: 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 60dd2faae..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,43 +13,101 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** + * @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 */ public function DataReplacementTest(AcceptanceTester $I) { - $I->fillField("#selector", "StringBefore John StringAfter"); - $I->seeCurrentUrlMatches("~\/John~i"); - $I->fillField("#John", "input"); - $I->dragAndDrop("#John", "Doe"); - $I->conditionalClick("Doe", "#John", true); - $I->amOnUrl("John.html"); - $I->searchAndMultiSelectOption("#selector", ["John", "Doe"]); - $I->fillField("#selector", "StringBefore " . msq("uniqueData") . "John StringAfter"); - $I->fillField("#" . msq("uniqueData") . "John", "input"); - $I->dragAndDrop("#" . msq("uniqueData") . "John", msq("uniqueData") . "John"); - $I->conditionalClick(msq("uniqueData") . "John", "#" . msq("uniqueData") . "John", true); - $I->amOnUrl(msq("uniqueData") . "John.html"); - $I->searchAndMultiSelectOption("#selector", [msq("uniqueData") . "John", "Doe"]); - $I->click("#" . msq("uniqueData") . "John#" . msq("uniqueData") . "John"); - $I->click("#Doe" . msq("uniqueData") . "#Doe" . msq("uniqueData")); - $I->fillField("#selector", "StringBefore Doe" . msq("uniqueData") . " StringAfter"); - $I->fillField("#Doe" . msq("uniqueData"), "input"); - $I->dragAndDrop("#Doe" . msq("uniqueData"), "Doe" . msq("uniqueData")); - $I->conditionalClick("Doe" . msq("uniqueData"), "#Doe" . msq("uniqueData"), true); - $I->amOnUrl("Doe" . msq("uniqueData") . ".html"); - $I->searchAndMultiSelectOption("#selector", ["John", "Doe" . msq("uniqueData")]); - $I->searchAndMultiSelectOption("#selector", [msq("uniqueData") . "John", "Doe" . msq("uniqueData")]); - $I->selectMultipleOptions("#Doe" . msq("uniqueData"), "#element", [msq("uniqueData") . "John", "Doe" . msq("uniqueData")]); - $I->fillField(".selector", "0"); - $insertCommand = $I->magentoCLI("do something Doe" . msq("uniqueData") . " with uniqueness"); + $I->fillField("#selector", "StringBefore John StringAfter"); // stepKey: inputReplace + $I->seeCurrentUrlMatches("~\/John~i"); // stepKey: seeInRegex + $I->fillField("#John", "input"); // stepKey: selectorReplace + $I->dragAndDrop("#John", "Doe"); // stepKey: selector12Replace + $I->conditionalClick("Doe", "#John", true); // stepKey: dependentSelectorReplace + $I->amOnUrl("John.html"); // stepKey: urlReplace + $I->searchAndMultiSelectOption("#selector", ["John", "Doe"]); // stepKey: parameterArrayReplacement + $I->fillField("#selector", "StringBefore " . msq("uniqueData") . "John StringAfter"); // stepKey: inputPrefixReplaceMSQPrefix + $I->fillField("#" . msq("uniqueData") . "John", "input"); // stepKey: selectorReplaceMSQPrefix + $I->dragAndDrop("#" . msq("uniqueData") . "John", msq("uniqueData") . "John"); // stepKey: selector12ReplaceMSQPrefix + $I->conditionalClick(msq("uniqueData") . "John", "#" . msq("uniqueData") . "John", true); // stepKey: dependentSelectorReplaceMSQPrefix + $I->amOnUrl(msq("uniqueData") . "John.html"); // stepKey: urlReplaceMSQPrefix + $I->searchAndMultiSelectOption("#selector", [msq("uniqueData") . "John", "Doe"]); // stepKey: parameterArrayReplacementMSQPrefix + $I->click("#" . msq("uniqueData") . "John#" . msq("uniqueData") . "John"); // stepKey: selectorReplaceDupedMSQPrefix + $I->click("#Doe" . msq("uniqueData") . "#Doe" . msq("uniqueData")); // stepKey: selectorReplaceDupedMSQSuffix + $I->fillField("#selector", "StringBefore Doe" . msq("uniqueData") . " StringAfter"); // stepKey: inputReplaceMSQSuffix + $I->fillField("#Doe" . msq("uniqueData"), "input"); // stepKey: selectorReplaceMSQSuffix + $I->dragAndDrop("#Doe" . msq("uniqueData"), "Doe" . msq("uniqueData")); // stepKey: selector12ReplaceMSQSuffix + $I->conditionalClick("Doe" . msq("uniqueData"), "#Doe" . msq("uniqueData"), true); // stepKey: dependentSelectorReplaceMSQSuffix + $I->amOnUrl("Doe" . msq("uniqueData") . ".html"); // stepKey: urlReplaceMSQSuffix + $I->searchAndMultiSelectOption("#selector", ["John", "Doe" . msq("uniqueData")]); // stepKey: parameterArrayReplacementMSQSuffix + $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", 60); // stepKey: insertCommand $I->comment($insertCommand); + $I->seeInPageSource("StringBefore John StringAfter"); // stepKey: htmlReplace1 + $I->seeInPageSource("#John"); // stepKey: htmlReplace2 + $I->seeInPageSource("StringBefore " . msq("uniqueData") . "John StringAfter"); // stepKey: htmlReplace3 + $I->seeInPageSource("#" . msq("uniqueData") . "John"); // stepKey: htmlReplace4 + $I->seeInPageSource("#" . msq("uniqueData") . "John#" . msq("uniqueData") . "John"); // stepKey: htmlReplace5 + $I->seeInPageSource("StringBefore Doe" . msq("uniqueData") . " StringAfter"); // stepKey: htmlReplace6 + $I->seeInPageSource("#Doe" . msq("uniqueData")); // stepKey: htmlReplace7 + $I->seeInPageSource("#element"); // stepKey: htmlReplace8 + $I->seeInPageSource("StringBefore #element StringAfter"); // stepKey: htmlReplace9 + $I->dontSeeInPageSource("StringBefore John StringAfter"); // stepKey: htmlReplace10 + $I->dontSeeInPageSource("#John"); // stepKey: htmlReplace11 + $I->dontSeeInPageSource("StringBefore " . msq("uniqueData") . "John StringAfter"); // stepKey: htmlReplace12 + $I->dontSeeInPageSource("#" . msq("uniqueData") . "John"); // stepKey: htmlReplace13 + $I->dontSeeInPageSource("#" . msq("uniqueData") . "John#" . msq("uniqueData") . "John"); // stepKey: htmlReplace14 + $I->dontSeeInPageSource("StringBefore Doe" . msq("uniqueData") . " StringAfter"); // stepKey: htmlReplace15 + $I->dontSeeInPageSource("#Doe" . msq("uniqueData")); // stepKey: htmlReplace16 + $I->dontSeeInPageSource("#element"); // stepKey: htmlReplace17 + $I->dontSeeInPageSource("StringBefore #element StringAfter"); // stepKey: htmlReplace18 + $I->seeInSource("StringBefore John StringAfter"); // stepKey: htmlReplace19 + $I->seeInSource("#John"); // stepKey: htmlReplace20 + $I->seeInSource("StringBefore " . msq("uniqueData") . "John StringAfter"); // stepKey: htmlReplace21 + $I->seeInSource("#" . msq("uniqueData") . "John"); // stepKey: htmlReplace22 + $I->seeInSource("#" . msq("uniqueData") . "John#" . msq("uniqueData") . "John"); // stepKey: htmlReplace23 + $I->seeInSource("StringBefore Doe" . msq("uniqueData") . " StringAfter"); // stepKey: htmlReplace24 + $I->seeInSource("#Doe" . msq("uniqueData")); // stepKey: htmlReplace25 + $I->seeInSource("#element"); // stepKey: htmlReplace26 + $I->seeInSource("StringBefore #element StringAfter"); // stepKey: htmlReplace27 + $I->dontSeeInSource("StringBefore John StringAfter"); // stepKey: htmlReplace28 + $I->dontSeeInSource("#John"); // stepKey: htmlReplace29 + $I->dontSeeInSource("StringBefore " . msq("uniqueData") . "John StringAfter"); // stepKey: htmlReplace30 + $I->dontSeeInSource("#" . msq("uniqueData") . "John"); // stepKey: htmlReplace31 + $I->dontSeeInSource("#" . msq("uniqueData") . "John#" . msq("uniqueData") . "John"); // stepKey: htmlReplace32 + $I->dontSeeInSource("StringBefore Doe" . msq("uniqueData") . " StringAfter"); // stepKey: htmlReplace33 + $I->dontSeeInSource("#Doe" . msq("uniqueData")); // stepKey: htmlReplace34 + $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/ExecuteJsEscapingTest.txt b/dev/tests/verification/Resources/ExecuteJsEscapingTest.txt index 6dd8ef3db..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,22 +13,44 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** + * @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 */ public function ExecuteJsEscapingTest(AcceptanceTester $I) { - $javaVariableEscape = $I->executeJS("return \$javascriptVariable"); - $mftfVariableNotEscaped = $I->executeJS("return {$doNotEscape}"); - $persistedDataNotEscaped = $I->executeJS("return " . PersistedObjectHandler::getInstance()->retrieveEntityField('persisted', 'data', 'test')); - $hookPersistedDataNotEscaped = $I->executeJS("return " . PersistedObjectHandler::getInstance()->retrieveEntityField('persisted', 'data', 'test')); - $addNewAttributeForRule = $I->executeJS("document.querySelector('entity option[value=" . PersistedObjectHandler::getInstance()->retrieveEntityField('productAttribute', 'attribute_code', 'test') . "]').setAttribute('selected', 'selected')"); + $javaVariableEscape = $I->executeJS("return \$javascriptVariable"); // stepKey: javaVariableEscape + $mftfVariableNotEscaped = $I->executeJS("return {$doNotEscape}"); // stepKey: mftfVariableNotEscaped + $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 41bea9d6f..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,30 +13,45 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** + * @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->amGoingTo("create entity that has the stepKey: simpleDataKey"); - PersistedObjectHandler::getInstance()->createEntity( - "simpleDataKey", - "test", - "extendParentData", - [], - null - ); - $I->searchAndMultiSelectOption("#selector", ["otherName"]); - $I->searchAndMultiSelectOption("#selector", ["extendName"]); - $I->searchAndMultiSelectOption("#selector", ["item"]); - $I->searchAndMultiSelectOption("#selector", [msq("extendParentData") . "prename"]); - $I->searchAndMultiSelectOption("#selector", ["postnameExtend" . msq("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 48fa542bc..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,22 +13,46 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** + * @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 */ public function ExtendedActionGroup(AcceptanceTester $I) { + $I->comment("Entering Action Group [actionGroup] extendTestActionGroup"); $I->comment("New Input Before"); - $grabProductsActionGroup = $I->grabMultiple("notASelector"); + $grabProductsActionGroup = $I->grabMultiple("notASelector"); // stepKey: grabProductsActionGroup $I->comment("New Input After"); - $I->assertCount(99, $grabProductsActionGroup); - $I->assertCount(8000, $grabProductsActionGroup); + $I->assertCount(99, $grabProductsActionGroup); // stepKey: assertCountActionGroup + $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 new file mode 100644 index 000000000..c46076353 --- /dev/null +++ b/dev/tests/verification/Resources/ExtendedChildTestInSuiteCest.txt @@ -0,0 +1,79 @@ +<?php +namespace Magento\AcceptanceTest\_suiteExtends\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]: ExtendedChildTestInSuite") + * @group ExtendedTestInSuite + * @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]'); + } + + /** + * @param AcceptanceTester $I + * @throws \Exception + */ + 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__); + } + } + + /** + * @param AcceptanceTester $I + * @throws \Exception + */ + public function _failed(AcceptanceTester $I) + { + $I->saveScreenshot(); // stepKey: saveScreenshot + } + + /** + * @Severity(level = SeverityLevel::TRIVIAL) + * @Features({"TestModule"}) + * @Stories({"ExtendedChildTestInSuite"}) + * @param AcceptanceTester $I + * @return void + * @throws \Exception + */ + public function ExtendedChildTestInSuite(AcceptanceTester $I) + { + $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 new file mode 100644 index 000000000..38ba62faf --- /dev/null +++ b/dev/tests/verification/Resources/ExtendedChildTestNotInSuite.txt @@ -0,0 +1,78 @@ +<?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]: ExtendedChildTestNotInSuite") + * @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]'); + } + + /** + * @param AcceptanceTester $I + * @throws \Exception + */ + 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__); + } + } + + /** + * @param AcceptanceTester $I + * @throws \Exception + */ + public function _failed(AcceptanceTester $I) + { + $I->saveScreenshot(); // stepKey: saveScreenshot + } + + /** + * @Severity(level = SeverityLevel::TRIVIAL) + * @Features({"TestModule"}) + * @Stories({"ExtendedChildTestNotInSuite"}) + * @param AcceptanceTester $I + * @return void + * @throws \Exception + */ + public function ExtendedChildTestNotInSuite(AcceptanceTester $I) + { + $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 41bea9d6f..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", - [], - null - ); + $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 936e78721..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,17 +13,41 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** + * @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 */ public function ExtendedRemoveActionGroup(AcceptanceTester $I) { + $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 2275de057..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,48 +15,26 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @Title("[NO TESTCASEID]: ChildExtendedTestSkippedParent") * @group Child + * @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"); - } - - /** - * @param AcceptanceTester $I - * @throws \Exception - */ - public function _after(AcceptanceTester $I) - { - $I->amOnPage("/afterUrl"); - } - - /** - * @param AcceptanceTester $I - * @throws \Exception - */ - public function _failed(AcceptanceTester $I) - { - $I->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 70e4ece10..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,36 +13,21 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** + * @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->amGoingTo("create entity that has the stepKey: sampleCreateBefore"); - PersistedObjectHandler::getInstance()->createEntity( - "sampleCreateBefore", - "hook", - "sampleCreatedEntity", - [], - null - ); - $I->amGoingTo("delete entity that has the createDataKey: sampleCreateBefore"); - PersistedObjectHandler::getInstance()->deleteEntity( - "sampleCreateBefore", - "hook" - ); - $I->amGoingTo("create entity that has the stepKey: sampleCreateForAfter"); - PersistedObjectHandler::getInstance()->createEntity( - "sampleCreateForAfter", - "hook", - "sampleCreatedEntity", - [], - null - ); } /** @@ -53,19 +36,12 @@ class HookActionsTestCest */ public function _after(AcceptanceTester $I) { - $I->amGoingTo("create entity that has the stepKey: sampleCreateAfter"); - PersistedObjectHandler::getInstance()->createEntity( - "sampleCreateAfter", - "hook", - "sampleCreatedEntity", - [], - null - ); - $I->amGoingTo("delete entity that has the createDataKey: 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__); + } } /** @@ -74,12 +50,11 @@ class HookActionsTestCest */ public function _failed(AcceptanceTester $I) { - $I->saveScreenshot(); + $I->saveScreenshot(); // stepKey: saveScreenshot } /** * @Features({"TestModule"}) - * @Parameter(name = "AcceptanceTester", value="$I") * @param AcceptanceTester $I * @return void * @throws \Exception @@ -87,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 bde511aac..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,37 +13,52 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** + * @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->amGoingTo("create entity that has the stepKey: data"); - PersistedObjectHandler::getInstance()->createEntity( - "data", - "test", - "ReplacementPerson", - [], - null - ); - $I->click(Locator::contains("'label'", "'Name'")); - $I->click(Locator::contains("'label'", "'Name'")); - $I->click(Locator::find("'img'", ['title' => 'diagram'])); - $I->click(Locator::contains("string", "'Name'")); - $I->click(Locator::contains("John", "'Name'")); - $I->click(Locator::contains(PersistedObjectHandler::getInstance()->retrieveEntityField('data', 'key', 'test'), "'Name'")); - $I->click(Locator::contains("string1", "string2")); - $I->click(Locator::contains("John", "Doe")); - $I->click(Locator::contains(PersistedObjectHandler::getInstance()->retrieveEntityField('data', 'key1', 'test'), PersistedObjectHandler::getInstance()->retrieveEntityField('data', 'key2', 'test'))); - $I->click(Locator::contains("string1", "John")); - $I->click(Locator::contains("string1", PersistedObjectHandler::getInstance()->retrieveEntityField('data', 'key1', 'test'))); - $I->click(Locator::contains("John", PersistedObjectHandler::getInstance()->retrieveEntityField('data', 'key1', 'test'))); + $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($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($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", $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/MagentoCliTest.txt b/dev/tests/verification/Resources/MagentoCliTest.txt new file mode 100644 index 000000000..3efc73c01 --- /dev/null +++ b/dev/tests/verification/Resources/MagentoCliTest.txt @@ -0,0 +1,59 @@ +<?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>Test files</h3>verification/TestModule/Test/BasicFunctionalTest/MagentoCliTest.xml<br>") + */ +class MagentoCliTestCest +{ + /** + * @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 MagentoCliTest(AcceptanceTester $I) + { + $magentoCli1 = $I->magentoCLI("maintenance:enable", 45, "\"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"), 45); // stepKey: magentoCli3 + $I->comment($magentoCli3); // stepKey: magentoCli3 + $magentoCli4 = $I->magentoCLISecret("config:set somePath " . $I->getSecret("someKey"), 120); // stepKey: magentoCli4 + $I->comment($magentoCli4); // stepKey: magentoCli4 + } + + 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 5c5091566..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,23 +13,45 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** + * @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 */ public function MergeMassViaInsertAfter(AcceptanceTester $I) { - $I->fillField("#foo", "foo"); - $I->fillField("#bar", "bar"); - $I->click("#mergeOne"); - $I->click("#mergeTwo"); - $I->click("#mergeThree"); - $I->fillField("#baz", "baz"); + $I->fillField("#foo", "foo"); // stepKey: fillField1 + $I->fillField("#bar", "bar"); // stepKey: fillField2 + $I->click("#mergeOne"); // stepKey: clickOne + $I->click("#mergeTwo"); // stepKey: clickTwo + $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 94982ec21..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,23 +13,45 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** + * @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 */ public function MergeMassViaInsertBefore(AcceptanceTester $I) { - $I->fillField("#foo", "foo"); - $I->click("#mergeOne"); - $I->click("#mergeTwo"); - $I->click("#mergeThree"); - $I->fillField("#bar", "bar"); - $I->fillField("#baz", "baz"); + $I->fillField("#foo", "foo"); // stepKey: fillField1 + $I->click("#mergeOne"); // stepKey: clickOne + $I->click("#mergeTwo"); // stepKey: clickTwo + $I->click("#mergeThree"); // stepKey: clickThree + $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 878fe6497..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,18 +13,24 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** + * @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 2d1748f56..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,25 +14,28 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @group functional + * @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->amGoingTo("create entity that has the stepKey: createPersonParam"); - PersistedObjectHandler::getInstance()->createEntity( - "createPersonParam", - "hook", - "ReplacementPerson", - [], - null - ); - $I->fillField("#foo", "myData1"); - $I->fillField("#bar", "myData2"); + $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]'); } /** @@ -43,8 +44,15 @@ class MergedActionGroupTestCest */ public function _after(AcceptanceTester $I) { - $I->fillField("#foo", "myData1"); - $I->fillField("#bar", "myData2"); + $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__); + } } /** @@ -53,23 +61,30 @@ class MergedActionGroupTestCest */ public function _failed(AcceptanceTester $I) { - $I->saveScreenshot(); + $I->saveScreenshot(); // stepKey: saveScreenshot } /** * @Severity(level = SeverityLevel::CRITICAL) * @Features({"TestModule"}) * @Stories({"MQE-433"}) - * @Parameter(name = "AcceptanceTester", value="$I") * @param AcceptanceTester $I * @return void * @throws \Exception */ public function MergedActionGroupTest(AcceptanceTester $I) { - $I->see(".merge .Jane"); - $I->see("#element .Jane"); - $I->amOnPage("/Jane/Dane.html"); - $I->click(".merge .Dane"); + $I->comment("Entering Action Group [actionGroupForMerge] FunctionalActionGroupForMerge"); + $I->see(".merge .Jane"); // stepKey: myMergedSeeElementActionGroupForMerge + $I->see("#element .Jane"); // stepKey: see1ActionGroupForMerge + $I->amOnPage("/Jane/Dane.html"); // stepKey: amOnPage1ActionGroupForMerge + $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 3f47703b9..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,16 +15,24 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @Title("[NO TESTCASEID]: MergedReferencesTest") * @group functional + * @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->amOnPage("/beforeUrl"); + $I->comment('[START BEFORE HOOK]'); + $I->amOnPage("/beforeUrl"); // stepKey: before1 + $I->comment('[END BEFORE HOOK]'); } /** @@ -35,7 +41,12 @@ class MergedReferencesTestCest */ public function _after(AcceptanceTester $I) { - $I->amOnPage("/afterUrl"); + $I->comment('[START AFTER HOOK]'); + $I->amOnPage("/afterUrl"); // stepKey: after1 + $I->comment('[END AFTER HOOK]'); + if ($this->isSuccess) { + unlink(__FILE__); + } } /** @@ -44,21 +55,26 @@ class MergedReferencesTestCest */ public function _failed(AcceptanceTester $I) { - $I->saveScreenshot(); + $I->saveScreenshot(); // stepKey: saveScreenshot } /** * @Severity(level = SeverityLevel::CRITICAL) * @Features({"TestModule"}) * @Stories({"MQE-433"}) - * @Parameter(name = "AcceptanceTester", value="$I") * @param AcceptanceTester $I * @return void * @throws \Exception */ public function MergedReferencesTest(AcceptanceTester $I) { - $I->fillField("#merge", "merged"); - $I->fillField("#newElement", "newField"); + $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 5084692be..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,25 +14,28 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @group functional + * @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->amGoingTo("create entity that has the stepKey: createPersonParam"); - PersistedObjectHandler::getInstance()->createEntity( - "createPersonParam", - "hook", - "ReplacementPerson", - [], - null - ); - $I->fillField("#foo", "myData1"); - $I->fillField("#bar", "myData2"); + $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]'); } /** @@ -43,8 +44,15 @@ class MultipleActionGroupsTestCest */ public function _after(AcceptanceTester $I) { - $I->fillField("#foo", "myData1"); - $I->fillField("#bar", "myData2"); + $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__); + } } /** @@ -53,31 +61,40 @@ class MultipleActionGroupsTestCest */ public function _failed(AcceptanceTester $I) { - $I->saveScreenshot(); + $I->saveScreenshot(); // stepKey: saveScreenshot } /** * @Severity(level = SeverityLevel::CRITICAL) * @Features({"TestModule"}) * @Stories({"MQE-433"}) - * @Parameter(name = "AcceptanceTester", value="$I") * @param AcceptanceTester $I * @return void * @throws \Exception */ public function MultipleActionGroupsTest(AcceptanceTester $I) { - $I->amOnPage("/someUrl"); - $I->amOnPage("/Jane/Dane.html"); - $I->fillField("#foo", "Jane"); - $I->fillField("#bar", "Dane"); - $I->searchAndMultiSelectOption("#foo", ["Jane", "Dane"]); - $I->see("#element .Jane"); - $I->click("loginButton"); - $I->amOnPage("/John/Doe.html"); - $I->fillField("#foo", "John"); - $I->fillField("#bar", "Doe"); - $I->searchAndMultiSelectOption("#foo", ["John", "Doe"]); - $I->see("#element .John"); + $I->amOnPage("/someUrl"); // stepKey: step1 + $I->comment("Entering Action Group [actionGroup1] FunctionalActionGroupWithData"); + $I->amOnPage("/Jane/Dane.html"); // stepKey: amOnPage1ActionGroup1 + $I->fillField("#foo", "Jane"); // stepKey: fillField1ActionGroup1 + $I->fillField("#bar", "Dane"); // stepKey: fillField2ActionGroup1 + $I->searchAndMultiSelectOption("#foo", ["Jane", "Dane"]); // stepKey: multi1ActionGroup1 + $I->see("#element .Jane"); // stepKey: see1ActionGroup1 + $I->comment("Exiting Action Group [actionGroup1] FunctionalActionGroupWithData"); + $I->click("loginButton"); // stepKey: step6 + $I->comment("Entering Action Group [actionGroupWithDataOverride2] FunctionalActionGroupWithData"); + $I->amOnPage("/John/Doe.html"); // stepKey: amOnPage1ActionGroupWithDataOverride2 + $I->fillField("#foo", "John"); // stepKey: fillField1ActionGroupWithDataOverride2 + $I->fillField("#bar", "Doe"); // stepKey: fillField2ActionGroupWithDataOverride2 + $I->searchAndMultiSelectOption("#foo", ["John", "Doe"]); // stepKey: multi1ActionGroupWithDataOverride2 + $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 01ddedf78..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,36 +13,51 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** + * @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->amGoingTo("create entity that has the stepKey: datakey"); - PersistedObjectHandler::getInstance()->createEntity( - "datakey", - "test", - "simpleData", - [], - null - ); - $I->amOnPage("/page.html"); - $I->amOnPage("/StringLiteral/page.html"); - $I->amOnPage("/John/page.html"); - $I->amOnPage("/" . PersistedObjectHandler::getInstance()->retrieveEntityField('datakey', 'firstname', 'test') . "/page.html"); - $I->amOnPage("/StringLiteral1/StringLiteral2.html"); - $I->amOnPage("/John/StringLiteral2.html"); - $I->amOnPage("/John/" . PersistedObjectHandler::getInstance()->retrieveEntityField('datakey', 'firstname', 'test') . ".html"); - $I->amOnPage("/" . PersistedObjectHandler::getInstance()->retrieveEntityField('datakey', 'firstname', 'test') . "/StringLiteral2.html"); - $I->amOnPage("/" . getenv("MAGENTO_BACKEND_NAME") . "/backend"); - $I->amOnPage("/" . getenv("MAGENTO_BACKEND_NAME") . "/StringLiteral/page.html"); - $I->amOnUrl("http://myFullUrl.com/"); + $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("/" . $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/" . $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 76e1bed32..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,47 +13,62 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** + * @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->amGoingTo("create entity that has the stepKey: simpleDataKey"); - PersistedObjectHandler::getInstance()->createEntity( - "simpleDataKey", - "test", - "simpleParamData", - [], - null - ); - $I->searchAndMultiSelectOption("#selector", ["name"]); - $I->searchAndMultiSelectOption("#selector", [msq("simpleParamData") . "prename"]); - $I->searchAndMultiSelectOption("#selector", ["postname" . msq("simpleParamData")]); - $I->searchAndMultiSelectOption("#selector", [PersistedObjectHandler::getInstance()->retrieveEntityField('simpleDataKey', 'name', 'test')]); - $I->searchAndMultiSelectOption("#selector", ["name", PersistedObjectHandler::getInstance()->retrieveEntityField('simpleDataKey', 'name', 'test')]); - $I->searchAndMultiSelectOption("#selector", ['someKey' => PersistedObjectHandler::getInstance()->retrieveEntityField('simpleDataKey', 'name', 'test')]); - $I->searchAndMultiSelectOption("#selector", ['someKey' => "name"]); - $I->searchAndMultiSelectOption("#selector", ['someKey' => msq("simpleParamData") . "prename"]); - $I->searchAndMultiSelectOption("#selector", ['someKey' => "postname" . msq("simpleParamData")]); - $I->unselectOption("#selector", ['foo']); - $I->unselectOption("#selector", ['foo', 'bar']); - $I->unselectOption("#selector", ["name"]); - $I->unselectOption("#selector", [msq("simpleParamData") . "prename"]); - $I->unselectOption("#selector", ["postname" . msq("simpleParamData")]); - $I->unselectOption("#selector", [PersistedObjectHandler::getInstance()->retrieveEntityField('simpleDataKey', 'name', 'test')]); - $I->unselectOption("#selector", ["name", PersistedObjectHandler::getInstance()->retrieveEntityField('simpleDataKey', 'name', 'test')]); - $I->pressKey("#selector", PersistedObjectHandler::getInstance()->retrieveEntityField('simpleDataKey', 'name', 'test'), ['ctrl', 'a'],\Facebook\WebDriver\WebDriverKeys::DELETE,PersistedObjectHandler::getInstance()->retrieveEntityField('simpleDataKey', 'name', 'test')); - $I->pressKey("#selector", ['ctrl', 'a'], 10, 20,\Facebook\WebDriver\WebDriverKeys::DELETE,PersistedObjectHandler::getInstance()->retrieveEntityField('simpleDataKey', 'name', 'test')); - $I->pressKey("#selector", ['ctrl', 'a'],'new', 10, 20,\Facebook\WebDriver\WebDriverKeys::DELETE,PersistedObjectHandler::getInstance()->retrieveEntityField('simpleDataKey', 'name', 'test')); - $I->pressKey("#selector", ['ctrl', 'a'],'new', 1, ['ctrl'], ['shift', 'ctrl', 'del'], [PersistedObjectHandler::getInstance()->retrieveEntityField('simpleDataKey', 'name', 'test'), 'a', "name"]); - $I->pressKey("#selector", ['ctrl', 'a'],'new', 1, ['ctrl'], ['shift', 'ctrl', 'del'], 0, [PersistedObjectHandler::getInstance()->retrieveEntityField('simpleDataKey', 'name', 'test'), PersistedObjectHandler::getInstance()->retrieveEntityField('simpleDataKey', 'name', 'test')]); - $I->pressKey("#selector", ['ctrl', 'a'],'new', 1, ['ctrl'], ['shift', 'ctrl', 'del'], [msq("simpleParamData") . "prename", "postname" . msq("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", [$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 + $I->unselectOption("#selector", ['foo']); // stepKey: 000 + $I->unselectOption("#selector", ['foo', 'bar']); // stepKey: 001 + $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", [$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 c3d7217b3..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,16 +15,24 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @Title("[NO TESTCASEID]: ParentExtendedTest") * @group Parent + * @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->amOnPage("/beforeUrl"); + $I->comment('[START BEFORE HOOK]'); + $I->amOnPage("/beforeUrl"); // stepKey: beforeAmOnPageKey + $I->comment('[END BEFORE HOOK]'); } /** @@ -35,7 +41,12 @@ class ParentExtendedTestCest */ public function _after(AcceptanceTester $I) { - $I->amOnPage("/afterUrl"); + $I->comment('[START AFTER HOOK]'); + $I->amOnPage("/afterUrl"); // stepKey: afterAmOnPageKey + $I->comment('[END AFTER HOOK]'); + if ($this->isSuccess) { + unlink(__FILE__); + } } /** @@ -44,14 +55,13 @@ class ParentExtendedTestCest */ public function _failed(AcceptanceTester $I) { - $I->saveScreenshot(); + $I->saveScreenshot(); // stepKey: saveScreenshot } /** * @Severity(level = SeverityLevel::MINOR) * @Features({"TestModule"}) * @Stories({"Parent"}) - * @Parameter(name = "AcceptanceTester", value="$I") * @param AcceptanceTester $I * @return void * @throws \Exception @@ -60,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 02d371571..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,18 +13,42 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** + * @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 */ public function PersistedAndXmlEntityArguments(AcceptanceTester $I) { - $I->seeInCurrentUrl("/" . PersistedObjectHandler::getInstance()->retrieveEntityField('persistedInTest', 'urlKey', 'test') . ".html?___store=" . msq("uniqueData") . "John"); + $I->comment("Entering Action Group [afterGroup] FunctionalActionGroupWithXmlAndPersistedData"); + $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 c19e33ee7..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,51 +13,73 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** + * @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->amGoingTo("create entity that has the stepKey: createData1"); - PersistedObjectHandler::getInstance()->createEntity( - "createData1", - "hook", - "ReplacementPerson", - [], - null - ); + $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->amGoingTo("create entity that has the stepKey: createdData"); - PersistedObjectHandler::getInstance()->createEntity( - "createdData", - "test", - "simpleData", - [], - null - ); - $I->fillField("#selector", "StringBefore " . PersistedObjectHandler::getInstance()->retrieveEntityField('createdData', 'firstname', 'test') . " StringAfter"); - $I->fillField("#" . PersistedObjectHandler::getInstance()->retrieveEntityField('createdData', 'firstname', 'test'), "input"); - $I->fillField("#" . getenv("MAGENTO_BASE_URL") . "#" . PersistedObjectHandler::getInstance()->retrieveEntityField('createdData', 'firstname', 'test'), "input"); - $I->fillSecretField("#" . CredentialStore::getInstance()->getSecret("SECRET_PARAM") . "#" . PersistedObjectHandler::getInstance()->retrieveEntityField('createdData', 'firstname', 'test'), "input"); - $I->dragAndDrop("#" . PersistedObjectHandler::getInstance()->retrieveEntityField('createdData', 'firstname', 'test'), PersistedObjectHandler::getInstance()->retrieveEntityField('createdData', 'lastname', 'test')); - $I->conditionalClick(PersistedObjectHandler::getInstance()->retrieveEntityField('createdData', 'lastname', 'test'), "#" . PersistedObjectHandler::getInstance()->retrieveEntityField('createdData', 'firstname', 'test'), true); - $I->amOnUrl(PersistedObjectHandler::getInstance()->retrieveEntityField('createdData', 'firstname', 'test') . ".html"); - $I->searchAndMultiSelectOption("#selector", [PersistedObjectHandler::getInstance()->retrieveEntityField('createdData', 'firstname', 'test'), PersistedObjectHandler::getInstance()->retrieveEntityField('createdData', 'lastname', 'test')]); - $I->fillField("#selector", "John " . PersistedObjectHandler::getInstance()->retrieveEntityField('createdData', 'firstname', 'test') . " stringLiteral"); - $I->searchAndMultiSelectOption("#selector", [PersistedObjectHandler::getInstance()->retrieveEntityField('createdData', 'firstname', 'test'), "John", "stringLiteral"]); + $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 + } + + 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 c38a14155..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,83 +13,63 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** + * @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->amGoingTo("create entity that has the stepKey: createDataACTIONGROUPBEFORE"); - PersistedObjectHandler::getInstance()->createEntity( - "createDataACTIONGROUPBEFORE", - "hook", - "entity", - [], - null - ); - $I->amGoingTo("update entity that has the createdDataKey: createDataACTIONGROUPBEFORE"); - PersistedObjectHandler::getInstance()->updateEntity( - "createDataACTIONGROUPBEFORE", - "hook", - "newEntity", - [] - ); - $I->amGoingTo("delete entity that has the createDataKey: createDataACTIONGROUPBEFORE"); - PersistedObjectHandler::getInstance()->deleteEntity( - "createDataACTIONGROUPBEFORE", - "hook" - ); - $I->amGoingTo("get entity that has the stepKey: getDataACTIONGROUPBEFORE"); - PersistedObjectHandler::getInstance()->getEntity( - "getDataACTIONGROUPBEFORE", - "hook", - "someEneity", - [], - null - ); - $I->comment(PersistedObjectHandler::getInstance()->retrieveEntityField('createData', 'field', 'hook')); + $I->comment('[START BEFORE HOOK]'); + $I->comment("Entering Action Group [ACTIONGROUPBEFORE] DataPersistenceAppendingActionGroup"); + $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 */ public function PersistenceActionGroupAppendingTest(AcceptanceTester $I) { - $I->amGoingTo("create entity that has the stepKey: createDataACTIONGROUP"); - PersistedObjectHandler::getInstance()->createEntity( - "createDataACTIONGROUP", - "test", - "entity", - [], - null - ); - $I->amGoingTo("update entity that has the createdDataKey: createDataACTIONGROUP"); - PersistedObjectHandler::getInstance()->updateEntity( - "createDataACTIONGROUP", - "test", - "newEntity", - [] - ); - $I->amGoingTo("delete entity that has the createDataKey: createDataACTIONGROUP"); - PersistedObjectHandler::getInstance()->deleteEntity( - "createDataACTIONGROUP", - "test" - ); - $I->amGoingTo("get entity that has the stepKey: getDataACTIONGROUP"); - PersistedObjectHandler::getInstance()->getEntity( - "getDataACTIONGROUP", - "test", - "someEneity", - [], - null - ); - $I->comment(PersistedObjectHandler::getInstance()->retrieveEntityField('createDataACTIONGROUP', 'field', 'test')); + $I->comment("Entering Action Group [ACTIONGROUP] DataPersistenceAppendingActionGroup"); + $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 24faac699..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,91 +13,55 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** + * @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->amGoingTo("create entity that has the stepKey: createData1"); - PersistedObjectHandler::getInstance()->createEntity( - "createData1", - "hook", - "DefaultPerson", - [], - $createData1Fields - ); - $createData2Fields['firstname'] = PersistedObjectHandler::getInstance()->retrieveEntityField('createData1', 'firstname', 'hook'); - $I->amGoingTo("create entity that has the stepKey: createData2"); - 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->amGoingTo("create entity that has the stepKey: createdData"); - PersistedObjectHandler::getInstance()->createEntity( - "createdData", - "test", - "simpleData", - [], - $createdDataFields - ); $createdData3Fields['firstname'] = "Takeshi"; $createdData3Fields['lastname'] = "Kovacs"; - $I->amGoingTo("create entity that has the stepKey: createdData3"); - PersistedObjectHandler::getInstance()->createEntity( - "createdData3", - "test", - "UniquePerson", - ["createdData"], - $createdData3Fields - ); - $createDataAG1CreatedAGFields['firstname'] = "string1"; - $I->amGoingTo("create entity that has the stepKey: createDataAG1CreatedAG"); - PersistedObjectHandler::getInstance()->createEntity( - "createDataAG1CreatedAG", - "test", - "simpleData", - [], - $createDataAG1CreatedAGFields - ); - $createDataAG2CreatedAGFields['firstname'] = "Jane"; - $I->amGoingTo("create entity that has the stepKey: createDataAG2CreatedAG"); - PersistedObjectHandler::getInstance()->createEntity( - "createDataAG2CreatedAG", - "test", - "simpleData", - [], - $createDataAG2CreatedAGFields - ); - $createDataAG3CreatedAGFields['firstname'] = PersistedObjectHandler::getInstance()->retrieveEntityField('createdData3', 'firstname', 'test'); - $I->amGoingTo("create entity that has the stepKey: createDataAG3CreatedAG"); - PersistedObjectHandler::getInstance()->createEntity( - "createDataAG3CreatedAG", - "test", - "simpleData", - [], - $createDataAG3CreatedAGFields - ); + $I->createEntity("createdData3", "test", "UniquePerson", ["createdData"], $createdData3Fields); // stepKey: createdData3 + } + + 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 5bdba2812..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,58 +13,75 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** + * @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 */ public function SectionReplacementTest(AcceptanceTester $I) { - $I->click("#element"); - $I->click("#foo"); - $I->waitForPageLoad(30); - $I->click("#element .stringLiteral"); - $I->click("#stringLiteral1 .stringLiteral2"); - $I->click("#stringLiteral1-stringLiteral2 .stringLiteral3"); - $I->click("#stringLiteral1-stringLiteral2 .stringLiteral1 [stringLiteral3]"); - $I->click("#element .John"); - $I->click("#John .Doe"); - $I->click("#John-Doe .Tiberius"); - $I->click("#John-Doe .John [Tiberius]"); - $I->click("#element ." . msq("uniqueData") . "John"); - $I->click("#" . msq("uniqueData") . "John .stringLiteral2"); - $I->click("#" . msq("uniqueData") . "John-stringLiteral2 .stringLiteral3"); - $I->click("#" . msq("uniqueData") . "John-stringLiteral2 ." . msq("uniqueData") . "John [stringLiteral3]"); - $I->click("#element .Doe" . msq("uniqueData")); - $I->click("#Doe" . msq("uniqueData") . " .stringLiteral2"); - $I->click("#Doe" . msq("uniqueData") . "-stringLiteral2 .stringLiteral3"); - $I->click("#Doe" . msq("uniqueData") . "-stringLiteral2 .Doe" . msq("uniqueData") . " [stringLiteral3]"); - $I->amGoingTo("create entity that has the stepKey: createdData"); - PersistedObjectHandler::getInstance()->createEntity( - "createdData", - "test", - "simpleData", - [], - null - ); - $I->click("#element ." . PersistedObjectHandler::getInstance()->retrieveEntityField('createdData', 'firstname', 'test')); - $I->click("#" . PersistedObjectHandler::getInstance()->retrieveEntityField('createdData', 'firstname', 'test') . " .stringLiteral2"); - $I->click("#" . PersistedObjectHandler::getInstance()->retrieveEntityField('createdData', 'firstname', 'test') . "-stringLiteral2 .stringLiteral3"); - $I->click("#" . PersistedObjectHandler::getInstance()->retrieveEntityField('createdData', 'firstname', 'test') . "-stringLiteral2 ." . PersistedObjectHandler::getInstance()->retrieveEntityField('createdData', 'firstname', 'test') . " [stringLiteral3]"); - $I->click("#element .{$data}"); - $I->click("#{$data1} .{$data2}"); - $I->click("#{$data1}-{$data2} .{$data3}"); - $I->click("#John-Doe .John [Tiberius]"); - $I->click("#stringLiteral1-" . PersistedObjectHandler::getInstance()->retrieveEntityField('createdData', 'firstname', 'test') . " .John"); - $I->click("#stringLiteral1-" . PersistedObjectHandler::getInstance()->retrieveEntityField('createdData', 'firstname', 'test') . " .{$data}"); - $I->click("#stringLiteral1-" . PersistedObjectHandler::getInstance()->retrieveEntityField('createdData', 'firstname', 'test') . " ." . msq("uniqueData") . "John"); - $I->click("#stringLiteral1-" . PersistedObjectHandler::getInstance()->retrieveEntityField('createdData', 'firstname', 'test') . " .Doe" . msq("uniqueData")); - $I->click("#element .1#element .2"); - $I->click("#element .1#element .{$data}"); + $I->click("#element"); // stepKey: selectorReplace + $I->click("#foo"); // stepKey: selectorReplaceTimeout + $I->waitForPageLoad(30); // stepKey: selectorReplaceTimeoutWaitForPageLoad + $I->click("#element .stringLiteral"); // stepKey: selectorReplaceOneParam + $I->click("#stringLiteral1 .stringLiteral2"); // stepKey: selectorReplaceTwoParam + $I->click("#stringLiteral1-stringLiteral2 .stringLiteral3"); // stepKey: selectorReplaceThreeParam + $I->click("#stringLiteral1-stringLiteral2 .stringLiteral1 [stringLiteral3]"); // stepKey: selectorReplaceThreeParamOneDupe + $I->click("#element .John"); // stepKey: selectorReplaceOneParamDataRef + $I->click("#John .Doe"); // stepKey: selectorReplaceTwoParamDataRef + $I->click("#John-Doe .Tiberius"); // stepKey: selectorReplaceThreeParamDataRef + $I->click("#John-Doe .John [Tiberius]"); // stepKey: selectorReplaceThreeParamOneDupeDataRef + $I->click("#element ." . msq("uniqueData") . "John"); // stepKey: selectorReplaceOneParamDataRefMSQPrefix + $I->click("#" . msq("uniqueData") . "John .stringLiteral2"); // stepKey: selectorReplaceTwoParamDataRefMSQPrefix + $I->click("#" . msq("uniqueData") . "John-stringLiteral2 .stringLiteral3"); // stepKey: selectorReplaceThreeParamDataRefMSQPrefix + $I->click("#" . msq("uniqueData") . "John-stringLiteral2 ." . msq("uniqueData") . "John [stringLiteral3]"); // stepKey: selectorReplaceThreeParamOneDupeDataRefMSQPrefix + $I->click("#element .Doe" . msq("uniqueData")); // stepKey: selectorReplaceOneParamDataRefMSQSuffix + $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->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-" . $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 1b466d4e0..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("") + * @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 dbe3ea054..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("") + * @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 ab6d241e3..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("") + * @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 60% rename from dev/tests/verification/Resources/SkippedTestNoIssues.txt rename to dev/tests/verification/Resources/SkippedTestWithIssueMustGetSkippedWithoutErrorExitCode.txt index b68823d39..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("") - * @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 new file mode 100644 index 000000000..c406af2d5 --- /dev/null +++ b/dev/tests/verification/Resources/XmlCommentedActionGroupTest.txt @@ -0,0 +1,57 @@ +<?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]: Action Group With comment block in arguments and action group body") + * @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"}) + * @param AcceptanceTester $I + * @return void + * @throws \Exception + */ + public function XmlCommentedActionGroupTest(AcceptanceTester $I) + { + $I->comment("Entering Action Group [actionGroup] XmlCommentedActionGroup"); + $I->comment("< > & \$abc \" abc ' <click stepKey=\"click\" userInput=\"\$\$createDataHook.firstname\$\$\" selector=\"#id\">/"); + $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 new file mode 100644 index 000000000..26563b7f4 --- /dev/null +++ b/dev/tests/verification/Resources/XmlCommentedTest.txt @@ -0,0 +1,90 @@ +<?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]: Test With comment blocks in root element 'tests', in annotations and in test body.") + * @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]'); + } + + /** + * @param AcceptanceTester $I + * @throws \Exception + */ + 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__); + } + } + + /** + * @param AcceptanceTester $I + * @throws \Exception + */ + public function _failed(AcceptanceTester $I) + { + $I->saveScreenshot(); // stepKey: saveScreenshot + } + + /** + * @Severity(level = SeverityLevel::CRITICAL) + * @Features({"TestModule"}) + * @param AcceptanceTester $I + * @return void + * @throws \Exception + */ + public function XmlCommentedTest(AcceptanceTester $I) + { + $I->comment("< > & \$abc \" abc ' <click stepKey=\"click\" userInput=\"\$\$createDataHook.firstname\$\$\" selector=\"#id_1\">/"); + $someVarDefinition = $I->grabValueFrom(); // stepKey: someVarDefinition + $I->acceptPopup(); // stepKey: acceptPopupKey1 + $I->comment("< > & \$abc \" abc ' <click stepKey=\"click\" userInput=\"\$\$createDataHook.firstname\$\$\" selector=\"#id_2\">/"); + $I->amOnPage("/test/url"); // stepKey: amOnPageKey1 + $I->appendField(".functionalTestSelector"); // stepKey: appendFieldKey1 + $I->comment("< > & \$abc \" abc ' <click stepKey=\"click\" userInput=\"\$\$createDataHook.firstname\$\$\" selector=\"#id_3\">/"); + $I->attachFile(".functionalTestSelector", "testFileAttachment"); // stepKey: attachFileKey1 + $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 665f06a05..946bf456e 100644 --- a/dev/tests/verification/Resources/functionalSuiteHooks.txt +++ b/dev/tests/verification/Resources/functionalSuiteHooks.txt @@ -2,7 +2,17 @@ namespace Group; +use Facebook\WebDriver\Remote\RemoteWebDriver; +use Facebook\WebDriver\Remote\DesiredCapabilities; 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; +use Facebook\WebDriver\Chrome\ChromeOptions; /** * Group class is Codeception Extension which is allowed to handle to all internal events. @@ -21,56 +31,78 @@ 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(); if ($this->preconditionFailure != null) { //if our preconditions fail, we need to mark all the tests as incomplete. - $e->getTest()->getMetadata()->setIncomplete($this->preconditionFailure); + $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 { - $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"); - $createFields['someKey'] = "dataHere"; + $this->getModuleForAction("amOnPage")->amOnPage("some.url"); // stepKey: before + $createOneFields['someKey'] = "dataHere"; PersistedObjectHandler::getInstance()->createEntity( - "create", + "createOne", "suite", - "createThis", - $createFields + "createEntityOne", + [], + $createOneFields ); - $webDriver->click(PersistedObjectHandler::getInstance()->retrieveEntityField('create', 'data', 'suite')); - $webDriver->see("John", msq("uniqueData") . "John"); - - // reset configuration and close session - $this->getModule('\Magento\FunctionalTestingFramework\Module\MagentoWebDriver')->_resetConfig(); - $webDriver->webDriver->close(); - $webDriver->webDriver = null; - + PersistedObjectHandler::getInstance()->createEntity( + "createTwo", + "suite", + "createEntityTwo", + ["createEntityOne"] + ); + PersistedObjectHandler::getInstance()->createEntity( + "createThree", + "suite", + "createEntityThree", + [] + ); + 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"); + $this->getModuleForAction("see")->see("John", msq("uniqueData") . "John"); // stepKey: seeFirstNameAC + print("Exiting Action Group [AC] actionGroupWithTwoArguments"); } 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"); } } @@ -80,7 +112,6 @@ class functionalSuiteHooks extends \Codeception\GroupObject $this->executePostConditions($e); } - private function executePostConditions(\Codeception\Event\TestEvent $e) { if ($this->currentTestRun == $this->testCount) { @@ -93,7 +124,7 @@ class functionalSuiteHooks extends \Codeception\GroupObject //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(); + return $cest->getResultAggregator(); }, $cest )); @@ -101,32 +132,106 @@ class functionalSuiteHooks extends \Codeception\GroupObject if (!empty($errors)) { foreach ($errors as $error) { - if ($error->failedTest()->getTestMethod() == $cest->getName()) { + if ($error->getTest()->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"); } } } - $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"); - $webDriver->deleteEntityByUrl("deleteThis"); - $webDriver->see("John", msq("uniqueData") . "John"); + $this->getModuleForAction("amOnPage")->amOnPage("some.url"); // stepKey: after + $this->getModuleForAction("deleteEntityByUrl")->deleteEntityByUrl("deleteThis"); // stepKey: delete + print("Entering Action Group [AC] actionGroupWithTwoArguments"); + $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) { + try { + $chromeOptions = new ChromeOptions(); + $capabilities = DesiredCapabilities::chrome(); + $remoteWebDriver = RemoteWebDriver::createBySessionID($session['id'], $wdHost, + $webDriverConfig['connection_timeout'], $webDriverConfig['request_timeout'], true, $capabilities); + $remoteWebDriver->quit(); + } catch (\Exception $exception) { + print("Failed trying to quit WebDriver session. Exception message: " . $exception->getMessage() . " Test execution will continue." . PHP_EOL); + // Session already closed so nothing to do + } + } + } + } + + /** + * 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; + } +} \ No newline at end of file diff --git a/dev/tests/verification/Resources/functionalSuiteWithComments.txt b/dev/tests/verification/Resources/functionalSuiteWithComments.txt new file mode 100644 index 000000000..a5a130345 --- /dev/null +++ b/dev/tests/verification/Resources/functionalSuiteWithComments.txt @@ -0,0 +1,217 @@ +<?php + +namespace Group; + +use Facebook\WebDriver\Remote\RemoteWebDriver; +use Facebook\WebDriver\Remote\DesiredCapabilities; +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; +use Facebook\WebDriver\Chrome\ChromeOptions; + +/** + * 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 functionalSuiteWithComments extends \Codeception\GroupObject +{ + public static $group = 'functionalSuiteWithComments'; + private $testCount = 1; + private $preconditionFailure = null; + 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(); + + 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(); + } + print("Comment in Before"); + $this->getModuleForAction("amOnPage")->amOnPage("some.url"); // stepKey: before + $createFields['someKey'] = "dataHere"; + PersistedObjectHandler::getInstance()->createEntity( + "create", + "suite", + "createThis", + [], + $createFields + ); + print("<click stepKey=\"comment with element\" userInput=\"helloworld\"/>"); + $this->getModuleForAction("click")->click(PersistedObjectHandler::getInstance()->retrieveEntityField('create', 'data', 'suite')); // stepKey: clickWithData + print("Entering Action Group [AC] actionGroupWithTwoArguments"); + $this->getModuleForAction("see")->see("John", msq("uniqueData") . "John"); // stepKey: seeFirstNameAC + print("Exiting Action Group [AC] actionGroupWithTwoArguments"); + } 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->getResultAggregator(); + }, + $cest + )); + $errors = $testResultObject->errors(); + + if (!empty($errors)) { + foreach ($errors as $error) { + if ($error->getTest()->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("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) { + try { + $chromeOptions = new ChromeOptions(); + $capabilities = DesiredCapabilities::chrome(); + $remoteWebDriver = RemoteWebDriver::createBySessionID($session['id'], $wdHost, + $webDriverConfig['connection_timeout'], $webDriverConfig['request_timeout'], true, $capabilities); + $remoteWebDriver->quit(); + } catch (\Exception $exception) { + print("Failed trying to quit WebDriver session. Exception message: " . $exception->getMessage() . " Test execution will continue." . PHP_EOL); + // Session already closed so nothing to do + } + } + } + } + + /** + * 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; + } +} \ No newline at end of file diff --git a/dev/tests/verification/TestModule/ActionGroup/BasicActionGroup.xml b/dev/tests/verification/TestModule/ActionGroup/BasicActionGroup.xml deleted file mode 100644 index 75ac71ef2..000000000 --- a/dev/tests/verification/TestModule/ActionGroup/BasicActionGroup.xml +++ /dev/null @@ -1,121 +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="actionGroupWithSkipReadinessActions"> - <comment userInput="ActionGroupSkipReadiness" stepKey="skip" skipReadiness="true"/> - </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..1c990b00a --- /dev/null +++ b/dev/tests/verification/TestModule/ActionGroup/BasicActionGroup/ActionGroupContainsStepKeyInArgValueActionGroup.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..f9466f178 --- /dev/null +++ b/dev/tests/verification/TestModule/ActionGroup/BasicActionGroup/ActionGroupReturningValueActionGroup.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..26a4f916b --- /dev/null +++ b/dev/tests/verification/TestModule/ActionGroup/BasicActionGroup/ActionGroupToExtendActionGroup.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..b6bcfe1e3 --- /dev/null +++ b/dev/tests/verification/TestModule/ActionGroup/BasicActionGroup/ActionGroupWithCreateDataActionGroup.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..f5094df59 --- /dev/null +++ b/dev/tests/verification/TestModule/ActionGroup/BasicActionGroup/ActionGroupWithDefaultArgumentAndStringSelectorParamActionGroup.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..4ac309788 --- /dev/null +++ b/dev/tests/verification/TestModule/ActionGroup/BasicActionGroup/ActionGroupWithEntityUsageActionGroup.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..ebd336fe3 --- /dev/null +++ b/dev/tests/verification/TestModule/ActionGroup/BasicActionGroup/ActionGroupWithMultipleParameterSelectorsFromArgumentActionGroup.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..8a2eeeb84 --- /dev/null +++ b/dev/tests/verification/TestModule/ActionGroup/BasicActionGroup/ActionGroupWithNestedArgumentActionGroup.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..83a54aa66 --- /dev/null +++ b/dev/tests/verification/TestModule/ActionGroup/BasicActionGroup/ActionGroupWithSectionAndDataActionGroup.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..5ed0f3612 --- /dev/null +++ b/dev/tests/verification/TestModule/ActionGroup/BasicActionGroup/ActionGroupWithSingleParameterSelectorFromArgumentActionGroup.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..1e8253470 --- /dev/null +++ b/dev/tests/verification/TestModule/ActionGroup/BasicActionGroup/ActionGroupWithStringUsageActionGroup.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..1850852ed --- /dev/null +++ b/dev/tests/verification/TestModule/ActionGroup/BasicActionGroup/ActionGroupWithTwoArgumentsActionGroup.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..bf2de1a42 --- /dev/null +++ b/dev/tests/verification/TestModule/ActionGroup/BasicActionGroup/ActionGroupWithoutArgumentsActionGroup.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..103380c37 --- /dev/null +++ b/dev/tests/verification/TestModule/ActionGroup/BasicActionGroup/ExtendBasicActionGroup.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..7af3be466 --- /dev/null +++ b/dev/tests/verification/TestModule/ActionGroup/BasicActionGroup/ExtendRemoveTestActionGroup.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..993efebf7 --- /dev/null +++ b/dev/tests/verification/TestModule/ActionGroup/BasicActionGroup/ExtendTestActionGroup.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..0025ccd16 --- /dev/null +++ b/dev/tests/verification/TestModule/ActionGroup/BasicActionGroup/ExtendedActionGroupReturningValueActionGroup.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..a3a0e378f --- /dev/null +++ b/dev/tests/verification/TestModule/ActionGroup/DeprecatedActionGroup.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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 e6a7bad00..000000000 --- a/dev/tests/verification/TestModule/ActionGroup/FunctionalActionGroup.xml +++ /dev/null @@ -1,94 +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> -</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..b6922c351 --- /dev/null +++ b/dev/tests/verification/TestModule/ActionGroup/FunctionalActionGroup/ActionGroupWithParametrizedSelectorsActionGroup.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..aaaf2a543 --- /dev/null +++ b/dev/tests/verification/TestModule/ActionGroup/FunctionalActionGroup/FunctionActionGroupWithStepKeyReferencesActionGroup.xml @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..8a9315afd --- /dev/null +++ b/dev/tests/verification/TestModule/ActionGroup/FunctionalActionGroup/FunctionalActionGroup.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..0311276be --- /dev/null +++ b/dev/tests/verification/TestModule/ActionGroup/FunctionalActionGroup/FunctionalActionGroupForMassMergeAfterActionGroup.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..f1e6ef7ba --- /dev/null +++ b/dev/tests/verification/TestModule/ActionGroup/FunctionalActionGroup/FunctionalActionGroupForMassMergeBeforeActionGroup.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..8cdc299b7 --- /dev/null +++ b/dev/tests/verification/TestModule/ActionGroup/FunctionalActionGroup/FunctionalActionGroupForMergeActionGroup.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..6fcfe90c0 --- /dev/null +++ b/dev/tests/verification/TestModule/ActionGroup/FunctionalActionGroup/FunctionalActionGroupNoDefaultActionGroup.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..fb931f551 --- /dev/null +++ b/dev/tests/verification/TestModule/ActionGroup/FunctionalActionGroup/FunctionalActionGroupWithDataActionGroup.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..0e039ba46 --- /dev/null +++ b/dev/tests/verification/TestModule/ActionGroup/FunctionalActionGroup/FunctionalActionGroupWithReturnValueActionGroup.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..79046116d --- /dev/null +++ b/dev/tests/verification/TestModule/ActionGroup/FunctionalActionGroup/FunctionalActionGroupWithTrickyArgumentActionGroup.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..0811df88d --- /dev/null +++ b/dev/tests/verification/TestModule/ActionGroup/FunctionalActionGroup/FunctionalActionGroupWithXmlAndPersistedDataActionGroup.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..8f3666a63 --- /dev/null +++ b/dev/tests/verification/TestModule/ActionGroup/FunctionalActionGroup/MergeActionGroupReturningValueActionGroup.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..8498c2231 --- /dev/null +++ b/dev/tests/verification/TestModule/ActionGroup/FunctionalActionGroup/SectionArgumentWithParameterizedSelectorActionGroup.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..2e88c800a --- /dev/null +++ b/dev/tests/verification/TestModule/ActionGroup/MergeFunctionalActionGroup/FunctionalActionGroupForMassMergeAfterActionGroup.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..52c3addc2 --- /dev/null +++ b/dev/tests/verification/TestModule/ActionGroup/MergeFunctionalActionGroup/FunctionalActionGroupForMassMergeBeforeActionGroup.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..f477214cf --- /dev/null +++ b/dev/tests/verification/TestModule/ActionGroup/MergeFunctionalActionGroup/FunctionalActionGroupForMergeActionGroup.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..4512ba0fa --- /dev/null +++ b/dev/tests/verification/TestModule/ActionGroup/MergeFunctionalActionGroup/MergeActionGroupReturningValueActionGroup.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..fa48d75fc --- /dev/null +++ b/dev/tests/verification/TestModule/ActionGroup/PersistenceActionGroup/DataPersistenceAppendingActionGroup.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..2cac7fe40 --- /dev/null +++ b/dev/tests/verification/TestModule/ActionGroup/PersistenceActionGroup/DataPersistenceSelfReferenceActionGroup.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..7483530aa --- /dev/null +++ b/dev/tests/verification/TestModule/ActionGroup/PersistenceActionGroup/PersistenceActionGroup.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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 new file mode 100644 index 000000000..eb3938eca --- /dev/null +++ b/dev/tests/verification/TestModule/ActionGroup/XmlCommentedActionGroup.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<!-- +/** + * Copyright 2019 Adobe + * All Rights Reserved. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="XmlCommentedActionGroup"> + <arguments> + <!-- Comments in arguments are not affecting test generation --> + <!-- <argument name="someArgument" defaultValue="ReplacementPerson" /> --> + <argument name="someArgument" defaultValue="ReplacementPerson" /> + </arguments> + + <!--< > & $abc " abc ' <click stepKey="click" userInput="$$createDataHook.firstname$$" selector="#id">/--> + <see selector="{{SampleSection.oneParamElement('test1')}}" userInput="{{someArgument.firstname}}" stepKey="seeFirstName" /> + </actionGroup> +</actionGroups> diff --git a/dev/tests/verification/TestModule/ActionGroup/XmlDuplicateActionGroup.xml b/dev/tests/verification/TestModule/ActionGroup/XmlDuplicateActionGroup.xml index fcc22acca..aa7453c2c 100644 --- a/dev/tests/verification/TestModule/ActionGroup/XmlDuplicateActionGroup.xml +++ b/dev/tests/verification/TestModule/ActionGroup/XmlDuplicateActionGroup.xml @@ -1,12 +1,12 @@ <?xml version="1.0" encoding="UTF-8" ?> <!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ +/** + * Copyright 2018 Adobe + * All Rights Reserved. + */ --> <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..8952b43f4 --- /dev/null +++ b/dev/tests/verification/TestModule/Data/DefaultPerson.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2022 Adobe + * All Rights Reserved. + */ +--> + +<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..e7886b451 --- /dev/null +++ b/dev/tests/verification/TestModule/Data/DeprecatedData.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> + +<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..be78df91b 100644 --- a/dev/tests/verification/TestModule/Data/ExtendedData.xml +++ b/dev/tests/verification/TestModule/Data/ExtendedData.xml @@ -1,13 +1,13 @@ <?xml version="1.0" encoding="UTF-8"?> <!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ +/** + * Copyright 2018 Adobe + * All Rights Reserved. + */ --> <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..b507dd17d 100644 --- a/dev/tests/verification/TestModule/Data/ParameterArrayData.xml +++ b/dev/tests/verification/TestModule/Data/ParameterArrayData.xml @@ -1,13 +1,13 @@ <?xml version="1.0" encoding="UTF-8"?> <!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ +/** + * Copyright 2017 Adobe + * All Rights Reserved. + */ --> <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..759816a65 100644 --- a/dev/tests/verification/TestModule/Data/PersistedReplacementData.xml +++ b/dev/tests/verification/TestModule/Data/PersistedReplacementData.xml @@ -1,14 +1,14 @@ <?xml version="1.0" encoding="UTF-8"?> <!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ +/** + * Copyright 2017 Adobe + * All Rights Reserved. + */ --> <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..c7d4bf0ac 100644 --- a/dev/tests/verification/TestModule/Data/ReplacementData.xml +++ b/dev/tests/verification/TestModule/Data/ReplacementData.xml @@ -1,14 +1,14 @@ <?xml version="1.0" encoding="UTF-8"?> <!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ +/** + * Copyright 2017 Adobe + * All Rights Reserved. + */ --> <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..06dacbd64 --- /dev/null +++ b/dev/tests/verification/TestModule/Data/TestData.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2022 Adobe + * All Rights Reserved. + */ +--> + +<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..c4f767094 --- /dev/null +++ b/dev/tests/verification/TestModule/Data/UniquePerson.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2022 Adobe + * All Rights Reserved. + */ +--> + +<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..c136f57fc --- /dev/null +++ b/dev/tests/verification/TestModule/Data/entity1.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2022 Adobe + * All Rights Reserved. + */ +--> + +<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..decdff7b7 --- /dev/null +++ b/dev/tests/verification/TestModule/Data/entity2.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2022 Adobe + * All Rights Reserved. + */ +--> + +<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..7e514089a --- /dev/null +++ b/dev/tests/verification/TestModule/Page/DeprecatedPage.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> + +<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 bf8f99615..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="TestModule_Magento" parameterized="true"> - <section name="SampleSection"/> - </page> - <page name="NoParamPage" url="/page.html" area="storefront" module="TestModule_Magento"> - <section name="SampleSection"/> - </page> - <page name="OneParamPage" url="/{{var1}}/page.html" area="storefront" module="TestModule_Magento" parameterized="true"> - <section name="SampleSection"/> - </page> - <page name="TwoParamPage" url="/{{var1}}/{{var2}}.html" area="storefront" module="TestModule_Magento" parameterized="true"> - <section name="SampleSection"/> - </page> - <page name="AdminPage" url="/backend" area="admin" module="TestModule_Magento"> - <section name="SampleSection"/> - </page> - <page name="AdminOneParamPage" url="/{{var1}}/page.html" area="admin" module="TestModule_Magento" parameterized="true"> - <section name="SampleSection"/> - </page> - <page name="ExternalPage" url="http://myFullUrl.com/" area="external" module="TestModule_Magento"> - <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..c34b7ec50 --- /dev/null +++ b/dev/tests/verification/TestModule/Page/SamplePage/AdminOneParamPage.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..572c77702 --- /dev/null +++ b/dev/tests/verification/TestModule/Page/SamplePage/AdminPage.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..3277290be --- /dev/null +++ b/dev/tests/verification/TestModule/Page/SamplePage/ExternalPage.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..35cedbfe6 --- /dev/null +++ b/dev/tests/verification/TestModule/Page/SamplePage/NoParamPage.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..643359c38 --- /dev/null +++ b/dev/tests/verification/TestModule/Page/SamplePage/OneParamPage.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..3bab4f619 --- /dev/null +++ b/dev/tests/verification/TestModule/Page/SamplePage/SamplePage.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..302de4401 --- /dev/null +++ b/dev/tests/verification/TestModule/Page/SamplePage/TwoParamPage.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..972b88cce --- /dev/null +++ b/dev/tests/verification/TestModule/Section/DeprecatedSection.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> + +<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..b9e3f6157 100644 --- a/dev/tests/verification/TestModule/Section/LocatorFunctionSection.xml +++ b/dev/tests/verification/TestModule/Section/LocatorFunctionSection.xml @@ -1,13 +1,13 @@ <?xml version="1.0" encoding="UTF-8"?> <!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ +/** + * Copyright 2017 Adobe + * All Rights Reserved. + */ --> <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 735c5fe7b..01e4ebbff 100644 --- a/dev/tests/verification/TestModule/Section/SampleSection.xml +++ b/dev/tests/verification/TestModule/Section/SampleSection.xml @@ -1,13 +1,13 @@ <?xml version="1.0" encoding="UTF-8"?> <!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ +/** + * Copyright 2017 Adobe + * All Rights Reserved. + */ --> <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"/> @@ -15,8 +15,10 @@ <element name="oneParamElement" type="button" selector="#element .{{var1}}" parameterized="true"/> <element name="twoParamElement" type="button" selector="#{{var1}} .{{var2}}" parameterized="true"/> <element name="threeParamElement" type="button" selector="#{{var1}}-{{var2}} .{{var3}}" parameterized="true"/> + <element name="fourParamElement" type="input" selector="//div[@name='{{arg1}}'][@class={{arg2}}][@data-element='{{arg3}}'][{{arg4}}]" parameterized="true"/> <element name="threeOneDuplicateParamElement" type="button" selector="#{{var1}}-{{var2}} .{{var1}} [{{var3}}]" parameterized="true"/> <element name="timeoutElement" type="button" selector="#foo" timeout="30"/> <element name="mergeElement" type="button" selector="#unMerge"/> + <element name="anotherTwoParamsElement" type="button" selector="(//div[@data-role='slide'])[{{arg1}}]/a[@data-element='link'][contains(@href,'{{arg2}}')]" parameterized="true"/> </section> </sections> diff --git a/dev/tests/verification/TestModule/Suite/ActionsInDifferentModulesSuite.xml b/dev/tests/verification/TestModule/Suite/ActionsInDifferentModulesSuite.xml new file mode 100644 index 000000000..56a4e820b --- /dev/null +++ b/dev/tests/verification/TestModule/Suite/ActionsInDifferentModulesSuite.xml @@ -0,0 +1,43 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> + +<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..4eab42dfe --- /dev/null +++ b/dev/tests/verification/TestModule/Suite/functionalSuite/functionalSuite1.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> + +<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..237c96d36 --- /dev/null +++ b/dev/tests/verification/TestModule/Suite/functionalSuite/functionalSuite2.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> + +<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/TestModule/Suite/functionalSuite/functionalSuiteWithComments.xml b/dev/tests/verification/TestModule/Suite/functionalSuite/functionalSuiteWithComments.xml new file mode 100644 index 000000000..ef4541d85 --- /dev/null +++ b/dev/tests/verification/TestModule/Suite/functionalSuite/functionalSuiteWithComments.xml @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> + +<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> + <comment userInput="afterBlock" stepKey="afterBlock"/> + </after> + </suite> +</suites> diff --git a/dev/tests/verification/TestModule/Suite/functionalSuiteExtends.xml b/dev/tests/verification/TestModule/Suite/functionalSuiteExtends.xml new file mode 100644 index 000000000..8c46a0430 --- /dev/null +++ b/dev/tests/verification/TestModule/Suite/functionalSuiteExtends.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> + +<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"/> + </include> + </suite> +</suites> diff --git a/dev/tests/verification/_suite/functionalSuiteHooks.xml b/dev/tests/verification/TestModule/Suite/functionalSuiteHooks.xml similarity index 58% rename from dev/tests/verification/_suite/functionalSuiteHooks.xml rename to dev/tests/verification/TestModule/Suite/functionalSuiteHooks.xml index f86f8c43c..88d26c9c6 100644 --- a/dev/tests/verification/_suite/functionalSuiteHooks.xml +++ b/dev/tests/verification/TestModule/Suite/functionalSuiteHooks.xml @@ -1,22 +1,30 @@ <?xml version="1.0" encoding="UTF-8"?> <!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ --> -<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="functionalSuiteHooks"> <include> <test name="IncludeTest"/> </include> <before> <amOnPage url="some.url" stepKey="before"/> - <createData entity="createThis" stepKey="create"> + <createData entity="createEntityOne" stepKey="createOne"> <field key="someKey">dataHere</field> </createData> - <click stepKey="clickWithData" userInput="$create.data$"/> + <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"/> 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..c4c179a6a --- /dev/null +++ b/dev/tests/verification/TestModule/Test/ActionGroupFunctionalTest/ActionGroupMergedViaInsertAfter.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..3bcd47d5b --- /dev/null +++ b/dev/tests/verification/TestModule/Test/ActionGroupFunctionalTest/ActionGroupMergedViaInsertBefore.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..4087b0820 --- /dev/null +++ b/dev/tests/verification/TestModule/Test/ActionGroupFunctionalTest/ActionGroupReturningValueTest.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..c973f512f --- /dev/null +++ b/dev/tests/verification/TestModule/Test/ActionGroupFunctionalTest/ActionGroupWithDataOverrideTest.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..d43d999dc --- /dev/null +++ b/dev/tests/verification/TestModule/Test/ActionGroupFunctionalTest/ActionGroupWithDataTest.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..21108ae7a --- /dev/null +++ b/dev/tests/verification/TestModule/Test/ActionGroupFunctionalTest/ActionGroupWithNoDefaultTest.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..7325f6706 --- /dev/null +++ b/dev/tests/verification/TestModule/Test/ActionGroupFunctionalTest/ActionGroupWithPersistedData.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..1330bbe21 --- /dev/null +++ b/dev/tests/verification/TestModule/Test/ActionGroupFunctionalTest/ActionGroupWithTopLevelPersistedData.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..a14c7867a --- /dev/null +++ b/dev/tests/verification/TestModule/Test/ActionGroupFunctionalTest/ArgumentWithSameNameAsElement.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..34f1c693c --- /dev/null +++ b/dev/tests/verification/TestModule/Test/ActionGroupFunctionalTest/BasicActionGroupTest.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..a1beb9230 --- /dev/null +++ b/dev/tests/verification/TestModule/Test/ActionGroupFunctionalTest/MergedActionGroupTest.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..452fcfaa0 --- /dev/null +++ b/dev/tests/verification/TestModule/Test/ActionGroupFunctionalTest/MultipleActionGroupsTest.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..b2335fc58 --- /dev/null +++ b/dev/tests/verification/TestModule/Test/ActionGroupFunctionalTest/PersistedAndXmlEntityArguments.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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 1417de556..000000000 --- a/dev/tests/verification/TestModule/Test/ActionGroupTest.xml +++ /dev/null @@ -1,166 +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="ActionGroupWithParameterizedElementWithHyphen"> - <actionGroup ref="SectionArgumentWithParameterizedSelector" stepKey="actionGroup"> - <argument name="section" value="SampleSection"/> - </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..2599faee7 --- /dev/null +++ b/dev/tests/verification/TestModule/Test/ActionGroupTest/ActionGroupContainsStepKeyInArgText.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..700c4090a --- /dev/null +++ b/dev/tests/verification/TestModule/Test/ActionGroupTest/ActionGroupToExtend.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..81e378e71 --- /dev/null +++ b/dev/tests/verification/TestModule/Test/ActionGroupTest/ActionGroupUsingCreateData.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..7e2c4fc3e --- /dev/null +++ b/dev/tests/verification/TestModule/Test/ActionGroupTest/ActionGroupUsingNestedArgument.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..5f331b3ab --- /dev/null +++ b/dev/tests/verification/TestModule/Test/ActionGroupTest/ActionGroupWithDefaultArgumentAndStringSelectorParam.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..c260d9b18 --- /dev/null +++ b/dev/tests/verification/TestModule/Test/ActionGroupTest/ActionGroupWithMultipleParameterSelectorsFromDefaultArgument.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..3ce79380c --- /dev/null +++ b/dev/tests/verification/TestModule/Test/ActionGroupTest/ActionGroupWithNoArguments.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..fe8d73c0e --- /dev/null +++ b/dev/tests/verification/TestModule/Test/ActionGroupTest/ActionGroupWithParameterizedElementWithHyphen.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..37e95e3ce --- /dev/null +++ b/dev/tests/verification/TestModule/Test/ActionGroupTest/ActionGroupWithParameterizedElementsWithStepKeyReferences.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..c8e0d05c5 --- /dev/null +++ b/dev/tests/verification/TestModule/Test/ActionGroupTest/ActionGroupWithPassedArgumentAndStringSelectorParam.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..25488134c --- /dev/null +++ b/dev/tests/verification/TestModule/Test/ActionGroupTest/ActionGroupWithSectionAndDataAsArguments.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..ed05cc00a --- /dev/null +++ b/dev/tests/verification/TestModule/Test/ActionGroupTest/ActionGroupWithSimpleDataUsageFromDefaultArgument.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..0df302ae8 --- /dev/null +++ b/dev/tests/verification/TestModule/Test/ActionGroupTest/ActionGroupWithSimpleDataUsageFromPassedArgument.xml @@ -0,0 +1,48 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..985748d5a --- /dev/null +++ b/dev/tests/verification/TestModule/Test/ActionGroupTest/ActionGroupWithSingleParameterSelectorFromDefaultArgument.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..0ef84bb47 --- /dev/null +++ b/dev/tests/verification/TestModule/Test/ActionGroupTest/ActionGroupWithSingleParameterSelectorFromPassedArgument.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..94343bde5 --- /dev/null +++ b/dev/tests/verification/TestModule/Test/ActionGroupTest/ActionGroupWithStepKeyReferences.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..1132cd41d --- /dev/null +++ b/dev/tests/verification/TestModule/Test/ActionGroupTest/ExtendedActionGroup.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..9c6ab1ead --- /dev/null +++ b/dev/tests/verification/TestModule/Test/ActionGroupTest/ExtendedActionGroupReturningValueTest.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..16079a0f0 --- /dev/null +++ b/dev/tests/verification/TestModule/Test/ActionGroupTest/ExtendedChildActionGroupReturningValueTest.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..c9da9e036 --- /dev/null +++ b/dev/tests/verification/TestModule/Test/ActionGroupTest/ExtendedRemoveActionGroup.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..1ea477af4 100644 --- a/dev/tests/verification/TestModule/Test/AssertTest.xml +++ b/dev/tests/verification/TestModule/Test/AssertTest.xml @@ -1,13 +1,13 @@ <?xml version="1.0" encoding="UTF-8"?> <!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ +/** + * Copyright 2018 Adobe + * All Rights Reserved. + */ --> <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 79% rename from dev/tests/verification/TestModule/Test/BasicFunctionalTest.xml rename to dev/tests/verification/TestModule/Test/BasicFunctionalTest/BasicFunctionalTest.xml index d120e5c31..2ee1687f1 100644 --- a/dev/tests/verification/TestModule/Test/BasicFunctionalTest.xml +++ b/dev/tests/verification/TestModule/Test/BasicFunctionalTest/BasicFunctionalTest.xml @@ -1,13 +1,12 @@ <?xml version="1.0" encoding="UTF-8"?> <!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ --> - <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"/> @@ -24,7 +23,7 @@ </after> <comment stepKey="basicCommentWithNoData" userInput="{{emptyData.noData}}"/> <comment stepKey="basicCommentWithDefinitelyNoData" userInput="{{emptyData.definitelyNoData}}"/> - <comment stepKey="ReadinessCheckSkipped" userInput="skipReadiness" skipReadiness="true"/> + <comment stepKey="basicCommentWithData" userInput="seeComment"/> <grabValueFrom stepKey="someVarDefinition"/> <acceptPopup stepKey="acceptPopupKey1"/> <amOnPage stepKey="amOnPageKey1" url="/test/url"/> @@ -50,8 +49,14 @@ <dontSeeElementInDOM selector=".functionalTestSelector" stepKey="dontSeeElementInDOMKey1"/> <dontSeeInCurrentUrl url="/functionalUrl" stepKey="dontSeeInCurrentUrlKey1"/> <dontSeeInField selector=".functionalTestSelector" stepKey="dontSeeInFieldKey1" /> - <dontSeeInPageSource userInput="someInput" stepKey="dontSeeInPageSourceKey1"/> - <dontSeeInSource html=""<myHtmlHere>"" stepKey="dontSeeInSourceKey1"/> + <dontSeeInPageSource html="Cosmo Kramer" stepKey="dontSeeInPageSourceKey1"/> + <dontSeeInPageSource html="<p>Jerry Seinfeld</p>" stepKey="dontSeeInPageSourceKey2"/> + <dontSeeInPageSource userInput="Cosmo Kramer" stepKey="dontSeeInPageSourceKey3"/> + <dontSeeInPageSource userInput="<p>Jerry Seinfeld</p>" stepKey="dontSeeInPageSourceKey4"/> + <dontSeeInPageSource userInput="bar" html="foo" stepKey="dontSeeInPageSourceKey5"/> + <dontSeeInPageSource userInput="<p>bar</p>" html="<p>foo</p>" stepKey="dontSeeInPageSourceKey6"/> + <dontSeeInSource html="Cosmo Kramer" stepKey="dontSeeInSourceKey1"/> + <dontSeeInSource html="<p>Jerry Seinfeld</p>" stepKey="dontSeeInSourceKey2"/> <dontSeeInTitle userInput="someInput" stepKey="dontSeeInTitleKey1"/> <dontSeeLink userInput="someInput" url="/functionalUrl" stepKey="dontSeeLinkKey1" /> <dontSeeOptionIsSelected selector=".functionalTestSelector" userInput="someInput" stepKey="dontSeeOptionIsSelectedKey1" /> @@ -63,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"/> @@ -95,9 +108,12 @@ <seeElementInDOM selector=".functionalTestSelector" stepKey="seeElementInDOMKey1"/> <seeInCurrentUrl url="/functionalUrl" stepKey="seeInCurrentUrlKey1"/> <seeInField selector=".functionalTestSelector" userInput="someInput" stepKey="seeInFieldKey1" /> - <seeInPageSource html=""<myHtmlHere>"" stepKey="seeInPageSourceKey1"/> + <seeInField selector=".functionalTestSelector" userInput="{{_CREDS.someKey}}" stepKey="seeInFieldKey2" /> + <seeInPageSource html="Home Page" stepKey="seeInPageSourceKey1"/> + <seeInPageSource html="<h1 class="page-title">" stepKey="seeInPageSourceKey2"/> <seeInPopup userInput="someInput" stepKey="seeInPopupKey1"/> - <seeInSource html=""<myHtmlHere>"" stepKey="seeInSourceKey1"/> + <seeInSource html="Home Page" stepKey="seeInSourceKey1"/> + <seeInSource html="<h1 class="page-title">" stepKey="seeInSourceKey2"/> <seeInTitle userInput="someInput" stepKey="seeInTitleKey1"/> <seeLink userInput="someInput" url="/functionalUrl" stepKey="seeLinkKey1" /> <seeNumberOfElements selector=".functionalTestSelector" stepKey="seeNumberOfElementsKey1"/> @@ -122,14 +138,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/MagentoCliTest.xml b/dev/tests/verification/TestModule/Test/BasicFunctionalTest/MagentoCliTest.xml new file mode 100644 index 000000000..edf5179a1 --- /dev/null +++ b/dev/tests/verification/TestModule/Test/BasicFunctionalTest/MagentoCliTest.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2023 Adobe + * All Rights Reserved. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="MagentoCliTest"> + <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"/> + </test> +</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..4bd715d54 --- /dev/null +++ b/dev/tests/verification/TestModule/Test/BasicFunctionalTest/MergeMassViaInsertAfter.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..dcd86b81c --- /dev/null +++ b/dev/tests/verification/TestModule/Test/BasicFunctionalTest/MergeMassViaInsertBefore.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..890d9087e 100644 --- a/dev/tests/verification/TestModule/Test/CharacterReplacementTest.xml +++ b/dev/tests/verification/TestModule/Test/CharacterReplacementTest.xml @@ -1,13 +1,13 @@ <?xml version="1.0" encoding="UTF-8"?> <!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ +/** + * Copyright 2018 Adobe + * All Rights Reserved. + */ --> <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..0c4fb0f39 100644 --- a/dev/tests/verification/TestModule/Test/DataActionsTest.xml +++ b/dev/tests/verification/TestModule/Test/DataActionsTest.xml @@ -1,25 +1,23 @@ <?xml version="1.0" encoding="UTF-8"?> <!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ +/** + * Copyright 2018 Adobe + * All Rights Reserved. + */ --> <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 ca3577373..41581466e 100644 --- a/dev/tests/verification/TestModule/Test/DataReplacementTest.xml +++ b/dev/tests/verification/TestModule/Test/DataReplacementTest.xml @@ -1,13 +1,13 @@ <?xml version="1.0" encoding="UTF-8"?> <!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ +/** + * Copyright 2018 Adobe + * All Rights Reserved. + */ --> <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"/> @@ -39,5 +39,45 @@ <fillField stepKey="insertZero" selector=".selector" userInput="{{simpleData.favoriteIndex}}"/> <magentoCLI stepKey="insertCommand" command="do something {{uniqueData.lastname}} with uniqueness"/> + + <seeInPageSource html="StringBefore {{simpleData.firstname}} StringAfter" stepKey="htmlReplace1"/> + <seeInPageSource html="#{{simpleData.firstname}}" stepKey="htmlReplace2"/> + <seeInPageSource html="StringBefore {{uniqueData.firstname}} StringAfter" stepKey="htmlReplace3"/> + <seeInPageSource html="#{{uniqueData.firstname}}" stepKey="htmlReplace4"/> + <seeInPageSource html="#{{uniqueData.firstname}}#{{uniqueData.firstname}}" stepKey="htmlReplace5"/> + <seeInPageSource html="StringBefore {{uniqueData.lastname}} StringAfter" stepKey="htmlReplace6"/> + <seeInPageSource html="#{{uniqueData.lastname}}" stepKey="htmlReplace7"/> + <seeInPageSource html="{{SampleSection.simpleElement}}" stepKey="htmlReplace8"/> + <seeInPageSource html="StringBefore {{SampleSection.simpleElement}} StringAfter" stepKey="htmlReplace9"/> + + <dontSeeInPageSource html="StringBefore {{simpleData.firstname}} StringAfter" stepKey="htmlReplace10"/> + <dontSeeInPageSource html="#{{simpleData.firstname}}" stepKey="htmlReplace11"/> + <dontSeeInPageSource html="StringBefore {{uniqueData.firstname}} StringAfter" stepKey="htmlReplace12"/> + <dontSeeInPageSource html="#{{uniqueData.firstname}}" stepKey="htmlReplace13"/> + <dontSeeInPageSource html="#{{uniqueData.firstname}}#{{uniqueData.firstname}}" stepKey="htmlReplace14"/> + <dontSeeInPageSource html="StringBefore {{uniqueData.lastname}} StringAfter" stepKey="htmlReplace15"/> + <dontSeeInPageSource html="#{{uniqueData.lastname}}" stepKey="htmlReplace16"/> + <dontSeeInPageSource html="{{SampleSection.simpleElement}}" stepKey="htmlReplace17"/> + <dontSeeInPageSource html="StringBefore {{SampleSection.simpleElement}} StringAfter" stepKey="htmlReplace18"/> + + <seeInSource html="StringBefore {{simpleData.firstname}} StringAfter" stepKey="htmlReplace19"/> + <seeInSource html="#{{simpleData.firstname}}" stepKey="htmlReplace20"/> + <seeInSource html="StringBefore {{uniqueData.firstname}} StringAfter" stepKey="htmlReplace21"/> + <seeInSource html="#{{uniqueData.firstname}}" stepKey="htmlReplace22"/> + <seeInSource html="#{{uniqueData.firstname}}#{{uniqueData.firstname}}" stepKey="htmlReplace23"/> + <seeInSource html="StringBefore {{uniqueData.lastname}} StringAfter" stepKey="htmlReplace24"/> + <seeInSource html="#{{uniqueData.lastname}}" stepKey="htmlReplace25"/> + <seeInSource html="{{SampleSection.simpleElement}}" stepKey="htmlReplace26"/> + <seeInSource html="StringBefore {{SampleSection.simpleElement}} StringAfter" stepKey="htmlReplace27"/> + + <dontSeeInSource html="StringBefore {{simpleData.firstname}} StringAfter" stepKey="htmlReplace28"/> + <dontSeeInSource html="#{{simpleData.firstname}}" stepKey="htmlReplace29"/> + <dontSeeInSource html="StringBefore {{uniqueData.firstname}} StringAfter" stepKey="htmlReplace30"/> + <dontSeeInSource html="#{{uniqueData.firstname}}" stepKey="htmlReplace31"/> + <dontSeeInSource html="#{{uniqueData.firstname}}#{{uniqueData.firstname}}" stepKey="htmlReplace32"/> + <dontSeeInSource html="StringBefore {{uniqueData.lastname}} StringAfter" stepKey="htmlReplace33"/> + <dontSeeInSource html="#{{uniqueData.lastname}}" stepKey="htmlReplace34"/> + <dontSeeInSource html="{{SampleSection.simpleElement}}" stepKey="htmlReplace35"/> + <dontSeeInSource html="StringBefore {{SampleSection.simpleElement}} StringAfter" stepKey="htmlReplace36"/> </test> </tests> \ No newline at end of file diff --git a/dev/tests/verification/TestModule/Test/DeprecatedEntitiesTest.xml b/dev/tests/verification/TestModule/Test/DeprecatedEntitiesTest.xml new file mode 100644 index 000000000..1dfba8bdd --- /dev/null +++ b/dev/tests/verification/TestModule/Test/DeprecatedEntitiesTest.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> + +<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..84220d172 --- /dev/null +++ b/dev/tests/verification/TestModule/Test/DeprecatedTest.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> + +<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/ExecuteJsTest.xml b/dev/tests/verification/TestModule/Test/ExecuteJsTest.xml index b361cdd28..94b4550a8 100644 --- a/dev/tests/verification/TestModule/Test/ExecuteJsTest.xml +++ b/dev/tests/verification/TestModule/Test/ExecuteJsTest.xml @@ -1,13 +1,13 @@ <?xml version="1.0" encoding="UTF-8"?> <!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ +/** + * Copyright 2018 Adobe + * All Rights Reserved. + */ --> <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..7c1579920 100644 --- a/dev/tests/verification/TestModule/Test/ExtendedDataTest.xml +++ b/dev/tests/verification/TestModule/Test/ExtendedDataTest.xml @@ -1,13 +1,13 @@ <?xml version="1.0" encoding="UTF-8"?> <!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ +/** + * Copyright 2018 Adobe + * All Rights Reserved. + */ --> <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 9d50f7197..000000000 --- a/dev/tests/verification/TestModule/Test/ExtendedFunctionalTest.xml +++ /dev/null @@ -1,161 +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> -</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..5a07c9b50 --- /dev/null +++ b/dev/tests/verification/TestModule/Test/ExtendedFunctionalTest/ChildExtendedTestAddHooks.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..ef4c3d773 --- /dev/null +++ b/dev/tests/verification/TestModule/Test/ExtendedFunctionalTest/ChildExtendedTestMerging.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..345c7e02d --- /dev/null +++ b/dev/tests/verification/TestModule/Test/ExtendedFunctionalTest/ChildExtendedTestNoParent.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..3786edf73 --- /dev/null +++ b/dev/tests/verification/TestModule/Test/ExtendedFunctionalTest/ChildExtendedTestRemoveAction.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..d64e8c600 --- /dev/null +++ b/dev/tests/verification/TestModule/Test/ExtendedFunctionalTest/ChildExtendedTestRemoveHookAction.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..28532d05b --- /dev/null +++ b/dev/tests/verification/TestModule/Test/ExtendedFunctionalTest/ChildExtendedTestReplace.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..774529f4a --- /dev/null +++ b/dev/tests/verification/TestModule/Test/ExtendedFunctionalTest/ChildExtendedTestReplaceHook.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..849c7ae0f --- /dev/null +++ b/dev/tests/verification/TestModule/Test/ExtendedFunctionalTest/ExtendedChildTestInSuite.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..5cfe1e31a --- /dev/null +++ b/dev/tests/verification/TestModule/Test/ExtendedFunctionalTest/ExtendedChildTestNotInSuite.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..1fb1fff7c --- /dev/null +++ b/dev/tests/verification/TestModule/Test/ExtendedFunctionalTest/ExtendedTestRelatedToSuiteParentTest.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..d618be4fe --- /dev/null +++ b/dev/tests/verification/TestModule/Test/ExtendedFunctionalTest/ExtendingSkippedTest.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..96dd4982e --- /dev/null +++ b/dev/tests/verification/TestModule/Test/ExtendedFunctionalTest/ParentExtendedTest.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..65b22fa99 --- /dev/null +++ b/dev/tests/verification/TestModule/Test/ExtendedFunctionalTest/ParentExtendedTestNoHooks.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..c5e06b0ba --- /dev/null +++ b/dev/tests/verification/TestModule/Test/ExtendedFunctionalTest/SkippedParent.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..7c08a67c8 --- /dev/null +++ b/dev/tests/verification/TestModule/Test/GroupSkipGenerationTest.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..7932a8880 100644 --- a/dev/tests/verification/TestModule/Test/HookActionsTest.xml +++ b/dev/tests/verification/TestModule/Test/HookActionsTest.xml @@ -1,21 +1,17 @@ <?xml version="1.0" encoding="UTF-8"?> <!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ +/** + * Copyright 2018 Adobe + * All Rights Reserved. + */ --> <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..5036010a0 100644 --- a/dev/tests/verification/TestModule/Test/LocatorFunctionTest.xml +++ b/dev/tests/verification/TestModule/Test/LocatorFunctionTest.xml @@ -1,13 +1,13 @@ <?xml version="1.0" encoding="UTF-8"?> <!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ +/** + * Copyright 2018 Adobe + * All Rights Reserved. + */ --> <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..b8f2df4bb --- /dev/null +++ b/dev/tests/verification/TestModule/Test/MergeFunctionalTest/BasicMergeTest.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..08de5f789 --- /dev/null +++ b/dev/tests/verification/TestModule/Test/MergeFunctionalTest/MergeMassViaInsertAfter.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..a61853fff --- /dev/null +++ b/dev/tests/verification/TestModule/Test/MergeFunctionalTest/MergeMassViaInsertBefore.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..92ee33080 --- /dev/null +++ b/dev/tests/verification/TestModule/Test/MergeFunctionalTest/MergeSkip.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..ddaa1dea2 --- /dev/null +++ b/dev/tests/verification/TestModule/Test/MergeFunctionalTest/MergedActionGroupReturningValueTest.xml @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..d5b9fdc18 --- /dev/null +++ b/dev/tests/verification/TestModule/Test/MergeFunctionalTest/MergedReferencesTest.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..d73df597f 100644 --- a/dev/tests/verification/TestModule/Test/PageReplacementTest.xml +++ b/dev/tests/verification/TestModule/Test/PageReplacementTest.xml @@ -1,13 +1,13 @@ <?xml version="1.0" encoding="UTF-8"?> <!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ +/** + * Copyright 2018 Adobe + * All Rights Reserved. + */ --> <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..18b2edf9f 100644 --- a/dev/tests/verification/TestModule/Test/ParameterArrayTest.xml +++ b/dev/tests/verification/TestModule/Test/ParameterArrayTest.xml @@ -1,13 +1,13 @@ <?xml version="1.0" encoding="UTF-8"?> <!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ +/** + * Copyright 2018 Adobe + * All Rights Reserved. + */ --> <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 8fc8fd19a..51c62bead 100644 --- a/dev/tests/verification/TestModule/Test/PersistedReplacementTest.xml +++ b/dev/tests/verification/TestModule/Test/PersistedReplacementTest.xml @@ -1,13 +1,13 @@ <?xml version="1.0" encoding="UTF-8"?> <!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ +/** + * Copyright 2018 Adobe + * All Rights Reserved. + */ --> <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"/> @@ -24,5 +24,21 @@ <searchAndMultiSelectOption stepKey="parameterArrayReplacement" selector="#selector" parameterArray="[$createdData.firstname$, $createdData.lastname$]"/> <fillField stepKey="allTypesMixed" selector="#selector" userInput="{{simpleData.firstname}} $createdData.firstname$ stringLiteral"/> <searchAndMultiSelectOption stepKey="parameterArrayMixed" selector="#selector" parameterArray="[$createdData.firstname$, {{simpleData.firstname}}, stringLiteral]"/> + + <seeInPageSource html="StringBefore $createdData.firstname$ StringAfter" stepKey="htmlReplace1"/> + <seeInPageSource html="StringBefore $$createData1.firstname$$ StringAfter" stepKey="htmlReplace2"/> + <seeInPageSource html="#{{_ENV.MAGENTO_BASE_URL}}#$createdData.firstname$" stepKey="htmlReplace3"/> + + <dontSeeInPageSource html="StringBefore $createdData.firstname$ StringAfter" stepKey="htmlReplace4"/> + <dontSeeInPageSource html="StringBefore $$createData1.firstname$$ StringAfter" stepKey="htmlReplace5"/> + <dontSeeInPageSource html="#{{_ENV.MAGENTO_BASE_URL}}#$createdData.firstname$" stepKey="htmlReplace6"/> + + <seeInSource html="StringBefore $createdData.firstname$ StringAfter" stepKey="htmlReplace7"/> + <seeInSource html="StringBefore $$createData1.firstname$$ StringAfter" stepKey="htmlReplace8"/> + <seeInSource html="#{{_ENV.MAGENTO_BASE_URL}}#$createdData.firstname$" stepKey="htmlReplace9"/> + + <dontSeeInSource html="StringBefore $createdData.firstname$ StringAfter" stepKey="htmlReplace10"/> + <dontSeeInSource html="StringBefore $$createData1.firstname$$ StringAfter" stepKey="htmlReplace11"/> + <dontSeeInSource html="#{{_ENV.MAGENTO_BASE_URL}}#$createdData.firstname$" stepKey="htmlReplace12"/> </test> </tests> diff --git a/dev/tests/verification/TestModule/Test/PersistenceActionGroupAppendingTest.xml b/dev/tests/verification/TestModule/Test/PersistenceActionGroupAppendingTest.xml index 20a9b6a28..96bbc2434 100644 --- a/dev/tests/verification/TestModule/Test/PersistenceActionGroupAppendingTest.xml +++ b/dev/tests/verification/TestModule/Test/PersistenceActionGroupAppendingTest.xml @@ -1,13 +1,13 @@ <?xml version="1.0" encoding="UTF-8"?> <!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ +/** + * Copyright 2018 Adobe + * All Rights Reserved. + */ --> <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..09a1515d4 100644 --- a/dev/tests/verification/TestModule/Test/PersistenceCustomFieldsTest.xml +++ b/dev/tests/verification/TestModule/Test/PersistenceCustomFieldsTest.xml @@ -1,37 +1,24 @@ <?xml version="1.0" encoding="UTF-8"?> <!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ +/** + * Copyright 2018 Adobe + * All Rights Reserved. + */ --> <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.xml deleted file mode 100644 index 92f10ddd0..000000000 --- a/dev/tests/verification/TestModule/Test/SampleSuite2Test.xml +++ /dev/null @@ -1,26 +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="additionalExcludeTest2"> - <annotations> - <group value="exclude"/> - </annotations> - <amOnPage stepKey="testOnPage" url="/someUrl"/> - <see stepKey="seeThePage" selector=".someSelector"/> - <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/AdditionalExcludeTest2.xml b/dev/tests/verification/TestModule/Test/SampleSuite2Test/AdditionalExcludeTest2.xml new file mode 100644 index 000000000..0b68a7f05 --- /dev/null +++ b/dev/tests/verification/TestModule/Test/SampleSuite2Test/AdditionalExcludeTest2.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="additionalExcludeTest2"> + <annotations> + <group value="exclude"/> + </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/SampleSuite2Test/AdditionalIncludeTest2.xml b/dev/tests/verification/TestModule/Test/SampleSuite2Test/AdditionalIncludeTest2.xml new file mode 100644 index 000000000..51342376f --- /dev/null +++ b/dev/tests/verification/TestModule/Test/SampleSuite2Test/AdditionalIncludeTest2.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..929133295 --- /dev/null +++ b/dev/tests/verification/TestModule/Test/SampleSuiteTest/AdditionalTest.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..b4221ee5a --- /dev/null +++ b/dev/tests/verification/TestModule/Test/SampleSuiteTest/ExcludeTest.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..f58154079 --- /dev/null +++ b/dev/tests/verification/TestModule/Test/SampleSuiteTest/ExcludeTest2.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..959b9f72d --- /dev/null +++ b/dev/tests/verification/TestModule/Test/SampleSuiteTest/IncludeActionsInDifferentModulesTest.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..89fd27ce4 --- /dev/null +++ b/dev/tests/verification/TestModule/Test/SampleSuiteTest/IncludeTest.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..13fd197aa --- /dev/null +++ b/dev/tests/verification/TestModule/Test/SampleSuiteTest/IncludeTest2.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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 new file mode 100644 index 000000000..70392cd92 --- /dev/null +++ b/dev/tests/verification/TestModule/Test/SecretCredentialDataTest.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2019 Adobe + * All Rights Reserved. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="secretCredentialDataTest"> + <createData entity="_defaultProduct" stepKey="createProductWithFieldOverridesUsingHardcodedData1"> + <field key="qty">123</field> + <field key="price">12.34</field> + </createData> + <createData entity="_defaultProduct" stepKey="createProductWithFieldOverridesUsingSecretCredData1"> + <field key="qty">{{_CREDS.payment_authorizenet_trans_key}}</field> + <field key="price">{{_CREDS.carriers_dhl_account_eu}}</field> + </createData> + + <fillField selector="{{AdminLoginFormSection.username}}" userInput="Hardcoded" stepKey="fillFieldUsingHardCodedData1"/> + <fillField selector="{{AdminLoginFormSection.username}}" userInput="{{_CREDS.carriers_dhl_id_eu}}" stepKey="fillFieldUsingSecretCredData1"/> + + <magentoCLI command="config:set cms/wysiwyg/enabled 0" stepKey="magentoCliUsingHardcodedData1"/> + <magentoCLI command="config:set cms/wysiwyg/enabled {{_CREDS.payment_authorizenet_login}}" stepKey="magentoCliUsingSecretCredData1"/> + </test> +</tests> diff --git a/dev/tests/verification/TestModule/Test/SectionReplacementTest.xml b/dev/tests/verification/TestModule/Test/SectionReplacementTest.xml index 9daef72cd..f50b17aa1 100644 --- a/dev/tests/verification/TestModule/Test/SectionReplacementTest.xml +++ b/dev/tests/verification/TestModule/Test/SectionReplacementTest.xml @@ -1,13 +1,13 @@ <?xml version="1.0" encoding="UTF-8"?> <!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ +/** + * Copyright 2018 Adobe + * All Rights Reserved. + */ --> <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}}"/> @@ -50,5 +50,8 @@ <click stepKey="selectorReplaceTwoParamElements" selector="{{SampleSection.oneParamElement('1')}}{{SampleSection.oneParamElement('2')}}"/> <click stepKey="selectorReplaceTwoParamMixedTypes" selector="{{SampleSection.oneParamElement('1')}}{{SampleSection.oneParamElement({$data})}}"/> + + <click stepKey="selectorParamWithEmptyString" selector="{{SampleSection.anotherTwoParamsElement('1', '')}}"/> + <click stepKey="selectorParamWithASpace" selector="{{SampleSection.anotherTwoParamsElement('1', ' ')}}"/> </test> </tests> 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..5dd4ec300 --- /dev/null +++ b/dev/tests/verification/TestModule/Test/SkippedTest/SkippedTest.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..e7a4aea0f --- /dev/null +++ b/dev/tests/verification/TestModule/Test/SkippedTest/SkippedTestTwoIssues.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..bf7f3a4a7 --- /dev/null +++ b/dev/tests/verification/TestModule/Test/SkippedTest/SkippedTestWithHooks.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..50097ff31 --- /dev/null +++ b/dev/tests/verification/TestModule/Test/SkippedTest/SkippedTestWithIssueMustGetSkippedWithoutErrorExitCode.xml @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2022 Adobe + * All Rights Reserved. + */ +--> +<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 new file mode 100644 index 000000000..dea37c5d9 --- /dev/null +++ b/dev/tests/verification/TestModule/Test/XmlCommentedActionGroupTest.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2019 Adobe + * All Rights Reserved. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="XmlCommentedActionGroupTest"> + <annotations> + <severity value="CRITICAL"/> + <title value="Action Group With comment block in arguments and action group body"/> + </annotations> + <actionGroup ref="XmlCommentedActionGroup" stepKey="actionGroup"/> + </test> +</tests> diff --git a/dev/tests/verification/TestModule/Test/XmlCommentedTest.xml b/dev/tests/verification/TestModule/Test/XmlCommentedTest.xml new file mode 100644 index 000000000..0726f8c28 --- /dev/null +++ b/dev/tests/verification/TestModule/Test/XmlCommentedTest.xml @@ -0,0 +1,42 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2019 Adobe + * All Rights Reserved. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <!--< > & $abc " abc ' <click stepKey="click" userInput="$$createDataHook.firstname$$" selector="#id">/--> + <test name="XmlCommentedTest"> + <annotations> + <!-- Comments in Test annotations are not affecting test generation --> + <!-- <severity value="BLOCKER"/> --> + <severity value="CRITICAL"/> + <title value="Test With comment blocks in root element 'tests', in annotations and in test body."/> + </annotations> + <before> + <!--< > & $abc " abc ' <click stepKey="click" userInput="$$createDataHook.firstname$$" selector="#id_1">/--> + <amOnPage url="/beforeUrl" stepKey="beforeAmOnPageKey"/> + <!--< > & $abc " abc ' <click stepKey="click" userInput="$$createDataHook.firstname$$" selector="#id_2">/--> + </before> + <after> + <!--< > & $abc " abc ' <click stepKey="click" userInput="$$createDataHook.firstname$$" selector="#id_1">/--> + <amOnPage url="/afterUrl" stepKey="afterAmOnPageKey"/> + <!--< > & $abc " abc ' <click stepKey="click" userInput="$$createDataHook.firstname$$" selector="#id_2">/--> + </after> + + <!--< > & $abc " abc ' <click stepKey="click" userInput="$$createDataHook.firstname$$" selector="#id_1">/--> + <grabValueFrom stepKey="someVarDefinition"/> + <acceptPopup stepKey="acceptPopupKey1"/> + + <!--< > & $abc " abc ' <click stepKey="click" userInput="$$createDataHook.firstname$$" selector="#id_2">/--> + <amOnPage stepKey="amOnPageKey1" url="/test/url"/> + <appendField selector=".functionalTestSelector" stepKey="appendFieldKey1" /> + + <!--< > & $abc " abc ' <click stepKey="click" userInput="$$createDataHook.firstname$$" selector="#id_3">/--> + <attachFile userInput="testFileAttachment" selector=".functionalTestSelector" stepKey="attachFileKey1" /> +<!-- <cancelPopup stepKey="cancelPopupKey1"/>--> + <checkOption selector=".functionalTestSelector" stepKey="checkOptionKey1"/> + </test> +</tests> 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..ce0cd43fb --- /dev/null +++ b/dev/tests/verification/TestModule/Test/XmlDuplicateTest/BasicDupedActionTest.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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 92% rename from dev/tests/verification/TestModule/Test/XmlDuplicateTest.xml rename to dev/tests/verification/TestModule/Test/XmlDuplicateTest/XmlDuplicateTest.xml index d3e8ee7b1..63a8f47d0 100644 --- a/dev/tests/verification/TestModule/Test/XmlDuplicateTest.xml +++ b/dev/tests/verification/TestModule/Test/XmlDuplicateTest/XmlDuplicateTest.xml @@ -1,13 +1,12 @@ <?xml version="1.0" encoding="UTF-8"?> <!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ --> - <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"/> @@ -66,8 +65,8 @@ <dontSeeInField stepKey="dntsinfld2"/> <dontSeeInFormFields selector="1" stepKey="dntsinffld1"/> <dontSeeInFormFields selector="1" stepKey="dntsinffld2"/> - <dontSeeInPageSource stepKey="dntsinpgs1"/> - <dontSeeInPageSource stepKey="dntsinpgs2"/> + <dontSeeInPageSource html="1" stepKey="dntsinpgs1"/> + <dontSeeInPageSource html="1" stepKey="dntsinpgs2"/> <dontSeeInSource html="1" stepKey="dntsinsource1"/> <dontSeeInSource html="1" stepKey="dntsinsource2"/> <dontSeeInTitle stepKey="dntsintitle1"/> @@ -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"/> @@ -282,8 +279,8 @@ <dontSeeInField stepKey="dntsinfld2"/> <dontSeeInFormFields selector="1" stepKey="dntsinffld1"/> <dontSeeInFormFields selector="1" stepKey="dntsinffld2"/> - <dontSeeInPageSource stepKey="dntsinpgs1"/> - <dontSeeInPageSource stepKey="dntsinpgs2"/> + <dontSeeInPageSource html="1" stepKey="dntsinpgs1"/> + <dontSeeInPageSource html="1" stepKey="dntsinpgs2"/> <dontSeeInSource html="1" stepKey="dntsinsource1"/> <dontSeeInSource html="1" stepKey="dntsinsource2"/> <dontSeeInTitle stepKey="dntsintitle1"/> @@ -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"/> @@ -497,8 +492,8 @@ <dontSeeInField stepKey="dntsinfld2"/> <dontSeeInFormFields selector="1" stepKey="dntsinffld1"/> <dontSeeInFormFields selector="1" stepKey="dntsinffld2"/> - <dontSeeInPageSource stepKey="dntsinpgs1"/> - <dontSeeInPageSource stepKey="dntsinpgs2"/> + <dontSeeInPageSource html="1" stepKey="dntsinpgs1"/> + <dontSeeInPageSource html="1" stepKey="dntsinpgs2"/> <dontSeeInSource html="1" stepKey="dntsinsource1"/> <dontSeeInSource html="1" stepKey="dntsinsource2"/> <dontSeeInTitle stepKey="dntsintitle1"/> @@ -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..b3fa6c512 100644 --- a/dev/tests/verification/TestModuleMerged/Data/MergeData.xml +++ b/dev/tests/verification/TestModuleMerged/Data/MergeData.xml @@ -1,14 +1,14 @@ <?xml version="1.0" encoding="UTF-8"?> <!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ +/** + * Copyright 2018 Adobe + * All Rights Reserved. + */ --> <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..1d4e5a3c2 100644 --- a/dev/tests/verification/TestModuleMerged/Section/MergeSection.xml +++ b/dev/tests/verification/TestModuleMerged/Section/MergeSection.xml @@ -1,13 +1,13 @@ <?xml version="1.0" encoding="UTF-8"?> <!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ +/** + * Copyright 2018 Adobe + * All Rights Reserved. + */ --> <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 71% rename from dev/tests/verification/TestModuleMerged/Test/MergeFunctionalTest.xml rename to dev/tests/verification/TestModuleMerged/Test/MergeFunctionalTest/BasicMergeTest.xml index 912854cc4..c3b5c63ee 100644 --- a/dev/tests/verification/TestModuleMerged/Test/MergeFunctionalTest.xml +++ b/dev/tests/verification/TestModuleMerged/Test/MergeFunctionalTest/BasicMergeTest.xml @@ -1,13 +1,12 @@ <?xml version="1.0" encoding="UTF-8"?> <!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ --> - <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..021edb7bc --- /dev/null +++ b/dev/tests/verification/TestModuleMerged/Test/MergeFunctionalTest/MergeSkip.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> +<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..d0ee32507 100644 --- a/dev/tests/verification/TestModuleMerged/Test/MergeXmlDuplicateTest.xml +++ b/dev/tests/verification/TestModuleMerged/Test/MergeXmlDuplicateTest.xml @@ -1,13 +1,13 @@ <?xml version="1.0" encoding="UTF-8"?> <!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ +/** + * Copyright 2018 Adobe + * All Rights Reserved. + */ --> <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/ActionGroupGenerationTest.php b/dev/tests/verification/Tests/ActionGroupGenerationTest.php index d95e572ef..0a2427f41 100644 --- a/dev/tests/verification/Tests/ActionGroupGenerationTest.php +++ b/dev/tests/verification/Tests/ActionGroupGenerationTest.php @@ -1,8 +1,9 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ + namespace tests\verification\Tests; use tests\util\MftfTestCase; @@ -191,9 +192,9 @@ public function testActionGroupWithArgContainingStepKey() * @throws \Exception * @throws \Magento\FunctionalTestingFramework\Exceptions\TestReferenceException */ - public function testActionGroupWithSkipReadiness() + public function testActionGroupWithSectionAndDataArguments() { - $this->generateAndCompareTest('ActionGroupSkipReadiness'); + $this->generateAndCompareTest('ActionGroupWithSectionAndDataAsArguments'); } /** @@ -206,4 +207,26 @@ public function testActionGroupWithHyphen() { $this->generateAndCompareTest('ActionGroupWithParameterizedElementWithHyphen'); } + + /** + * Test generation of a test referencing an action group with xml comment in arguments and action group body. + * + * @throws \Exception + * @throws \Magento\FunctionalTestingFramework\Exceptions\TestReferenceException + */ + public function testActionGroupWithXmlComments() + { + $this->generateAndCompareTest('XmlCommentedActionGroupTest'); + } + + /** + * Test generation of a test referencing an action group with selectors referencing stepKeys. + * + * @throws \Exception + * @throws \Magento\FunctionalTestingFramework\Exceptions\TestReferenceException + */ + public function testActionGroupWithActionStepKeyReferencesInSelectors() + { + $this->generateAndCompareTest('ActionGroupWithParameterizedElementsWithStepKeyReferences'); + } } diff --git a/dev/tests/verification/Tests/ActionGroupMergeGenerationTest.php b/dev/tests/verification/Tests/ActionGroupMergeGenerationTest.php index 0361bec75..3645f68cd 100644 --- a/dev/tests/verification/Tests/ActionGroupMergeGenerationTest.php +++ b/dev/tests/verification/Tests/ActionGroupMergeGenerationTest.php @@ -1,8 +1,8 @@ <?php - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ +/** + * Copyright 2017 Adobe + * All Rights Reserved. + */ namespace tests\verification\Tests; diff --git a/dev/tests/verification/Tests/ActionGroupWithReturnGenerationTest.php b/dev/tests/verification/Tests/ActionGroupWithReturnGenerationTest.php new file mode 100644 index 000000000..edbdff24a --- /dev/null +++ b/dev/tests/verification/Tests/ActionGroupWithReturnGenerationTest.php @@ -0,0 +1,53 @@ +<?php +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ + +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/AssertGenerationTest.php b/dev/tests/verification/Tests/AssertGenerationTest.php index cc604e907..c26c3b5bf 100644 --- a/dev/tests/verification/Tests/AssertGenerationTest.php +++ b/dev/tests/verification/Tests/AssertGenerationTest.php @@ -1,8 +1,9 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ + namespace tests\verification\Tests; use tests\util\MftfTestCase; diff --git a/dev/tests/verification/Tests/BasicCestGenerationTest.php b/dev/tests/verification/Tests/BasicCestGenerationTest.php index b03cbc487..9d5049ddb 100644 --- a/dev/tests/verification/Tests/BasicCestGenerationTest.php +++ b/dev/tests/verification/Tests/BasicCestGenerationTest.php @@ -1,8 +1,9 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ + namespace tests\verification\Tests; use tests\util\MftfTestCase; @@ -10,6 +11,7 @@ class BasicCestGenerationTest extends MftfTestCase { /** + * BasicFunctionalTest: * Tests flat generation of a hardcoded test file with no external references. * * @throws \Exception @@ -19,4 +21,56 @@ 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 + * - test body + * - test before and after blocks + * - annotations block + * + * @throws \Exception + * @throws \Magento\FunctionalTestingFramework\Exceptions\TestReferenceException + */ + public function testWithXmlComments() + { + $this->generateAndCompareTest('XmlCommentedTest'); + } + + /** + * Tests magentoCLI and magentoCLISecret commands with env 'MAGENTO_CLI_WAIT_TIMEOUT' set + * + * @throws \Exception + * @throws \Magento\FunctionalTestingFramework\Exceptions\TestReferenceException + */ + public function testMagentoCli() + { + putenv("MAGENTO_CLI_WAIT_TIMEOUT=45"); + $this->generateAndCompareTest('MagentoCliTest'); + putenv("MAGENTO_CLI_WAIT_TIMEOUT"); + } } diff --git a/dev/tests/verification/Tests/DataActionsTest.php b/dev/tests/verification/Tests/DataActionsTest.php index b5ad4a659..b10f75b21 100644 --- a/dev/tests/verification/Tests/DataActionsTest.php +++ b/dev/tests/verification/Tests/DataActionsTest.php @@ -1,8 +1,9 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2018 Adobe + * All Rights Reserved. */ + namespace tests\verification\Tests; use tests\util\MftfTestCase; diff --git a/dev/tests/verification/Tests/DeprecatedTest.php b/dev/tests/verification/Tests/DeprecatedTest.php new file mode 100644 index 000000000..3fb60bfbc --- /dev/null +++ b/dev/tests/verification/Tests/DeprecatedTest.php @@ -0,0 +1,34 @@ +<?php +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ + +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/ExecuteJsTest.php b/dev/tests/verification/Tests/ExecuteJsTest.php index 67a5f3c6f..1ff3899c3 100644 --- a/dev/tests/verification/Tests/ExecuteJsTest.php +++ b/dev/tests/verification/Tests/ExecuteJsTest.php @@ -1,8 +1,9 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2018 Adobe + * All Rights Reserved. */ + namespace tests\verification\Tests; use tests\util\MftfTestCase; diff --git a/dev/tests/verification/Tests/ExtendedDataTest.php b/dev/tests/verification/Tests/ExtendedDataTest.php index 291b6cb7f..127c0ceaa 100644 --- a/dev/tests/verification/Tests/ExtendedDataTest.php +++ b/dev/tests/verification/Tests/ExtendedDataTest.php @@ -1,8 +1,9 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2018 Adobe + * All Rights Reserved. */ + namespace tests\verification\Tests; use tests\util\MftfTestCase; diff --git a/dev/tests/verification/Tests/ExtendedGenerationTest.php b/dev/tests/verification/Tests/ExtendedGenerationTest.php index e7bb0a879..bfa28fe58 100644 --- a/dev/tests/verification/Tests/ExtendedGenerationTest.php +++ b/dev/tests/verification/Tests/ExtendedGenerationTest.php @@ -1,8 +1,9 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2018 Adobe + * All Rights Reserved. */ + namespace tests\verification\Tests; use Magento\FunctionalTestingFramework\Test\Handlers\TestObjectHandler; @@ -118,4 +119,15 @@ public function testExtendingSkippedGeneration() { $this->generateAndCompareTest('ExtendingSkippedTest'); } + + /** + * Tests extending and removing parent steps test generation. + * + * @throws \Exception + * @throws \Magento\FunctionalTestingFramework\Exceptions\TestReferenceException + */ + public function testExtendingAndRemovingStepsGeneration() + { + $this->generateAndCompareTest('ExtendedChildTestNotInSuite'); + } } diff --git a/dev/tests/verification/Tests/GroupSkipGenerationTest.php b/dev/tests/verification/Tests/GroupSkipGenerationTest.php new file mode 100644 index 000000000..512297945 --- /dev/null +++ b/dev/tests/verification/Tests/GroupSkipGenerationTest.php @@ -0,0 +1,23 @@ +<?php +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ + +namespace tests\verification\Tests; + +use tests\util\MftfTestCase; + +class GroupSkipGenerationTest extends MftfTestCase +{ + /** + * Tests group skip test generation + * + * @throws \Exception + * @throws \Magento\FunctionalTestingFramework\Exceptions\TestReferenceException + */ + public function testGroupSkipGenerationTest() + { + $this->generateAndCompareTest('GroupSkipGenerationTest'); + } +} diff --git a/dev/tests/verification/Tests/HookActionsTest.php b/dev/tests/verification/Tests/HookActionsTest.php index 2f290d5de..0f2bca8c2 100644 --- a/dev/tests/verification/Tests/HookActionsTest.php +++ b/dev/tests/verification/Tests/HookActionsTest.php @@ -1,8 +1,9 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2018 Adobe + * All Rights Reserved. */ + namespace tests\verification\Tests; use tests\util\MftfTestCase; diff --git a/dev/tests/verification/Tests/LocatorFunctionGenerationTest.php b/dev/tests/verification/Tests/LocatorFunctionGenerationTest.php index 13deb1416..067e49237 100644 --- a/dev/tests/verification/Tests/LocatorFunctionGenerationTest.php +++ b/dev/tests/verification/Tests/LocatorFunctionGenerationTest.php @@ -1,8 +1,9 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ + namespace tests\verification\Tests; use tests\util\MftfTestCase; diff --git a/dev/tests/verification/Tests/MergedGenerationTest.php b/dev/tests/verification/Tests/MergedGenerationTest.php index c336cd8d8..9b1c42051 100644 --- a/dev/tests/verification/Tests/MergedGenerationTest.php +++ b/dev/tests/verification/Tests/MergedGenerationTest.php @@ -1,8 +1,9 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2018 Adobe + * All Rights Reserved. */ + namespace tests\verification\Tests; use Magento\FunctionalTestingFramework\DataGenerator\Handlers\DataObjectHandler; diff --git a/dev/tests/verification/Tests/ParameterArrayTest.php b/dev/tests/verification/Tests/ParameterArrayTest.php index c65027d42..760a19096 100644 --- a/dev/tests/verification/Tests/ParameterArrayTest.php +++ b/dev/tests/verification/Tests/ParameterArrayTest.php @@ -1,8 +1,9 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ + namespace tests\verification\Tests; use tests\util\MftfTestCase; diff --git a/dev/tests/verification/Tests/PersistenceGenerationTest.php b/dev/tests/verification/Tests/PersistenceGenerationTest.php index 6944d75a0..2aa367990 100644 --- a/dev/tests/verification/Tests/PersistenceGenerationTest.php +++ b/dev/tests/verification/Tests/PersistenceGenerationTest.php @@ -1,8 +1,9 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2018 Adobe + * All Rights Reserved. */ + namespace tests\verification\Tests; use tests\util\MftfTestCase; diff --git a/dev/tests/verification/Tests/ReferenceReplacementGenerationTest.php b/dev/tests/verification/Tests/ReferenceReplacementGenerationTest.php index ef5c0bce6..c3db74f51 100644 --- a/dev/tests/verification/Tests/ReferenceReplacementGenerationTest.php +++ b/dev/tests/verification/Tests/ReferenceReplacementGenerationTest.php @@ -1,8 +1,9 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ + namespace tests\verification\Tests; use Magento\FunctionalTestingFramework\Exceptions\TestReferenceException; diff --git a/dev/tests/verification/Tests/ResilientGenerationTest.php b/dev/tests/verification/Tests/ResilientGenerationTest.php new file mode 100644 index 000000000..a3b78b446 --- /dev/null +++ b/dev/tests/verification/Tests/ResilientGenerationTest.php @@ -0,0 +1,286 @@ +<?php +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ + +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, null); + + $property = new \ReflectionProperty(DirSetupUtil::class, "DIR_CONTEXT"); + $property->setAccessible(true); + $property->setValue(null, []); + + $property = new \ReflectionProperty(SuiteObjectHandler::class, "instance"); + $property->setAccessible(true); + $property->setValue(null, null); + + $property = new \ReflectionProperty(TestObjectHandler::class, "testObjectHandler"); + $property->setAccessible(true); + $property->setValue(null, 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, null); + + $property = new \ReflectionProperty(DirSetupUtil::class, "DIR_CONTEXT"); + $property->setAccessible(true); + $property->setValue(null, []); + + $property = new \ReflectionProperty(SuiteObjectHandler::class, "instance"); + $property->setAccessible(true); + $property->setValue(null, null); + + $property = new \ReflectionProperty(TestObjectHandler::class, "testObjectHandler"); + $property->setAccessible(true); + $property->setValue(null, 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 86828e9a5..4a92f3ab8 100644 --- a/dev/tests/verification/Tests/SchemaValidationTest.php +++ b/dev/tests/verification/Tests/SchemaValidationTest.php @@ -1,13 +1,14 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2018 Adobe + * All Rights Reserved. */ + 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 +20,11 @@ class SchemaValidationTest extends MftfTestCase */ public function testInvalidTestSchema() { - AspectMock::double(MftfApplicationConfig::class, ['debugEnabled' => true]); + $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 +37,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/SecretCredentialDataTestCest.php b/dev/tests/verification/Tests/SecretCredentialDataTestCest.php new file mode 100644 index 000000000..a9ab00567 --- /dev/null +++ b/dev/tests/verification/Tests/SecretCredentialDataTestCest.php @@ -0,0 +1,63 @@ +<?php +/** + * Copyright 2023 Adobe + * All Rights Reserved. + */ + +namespace Magento\FunctionalTestingFramework\Tests\Verification; + +use Magento\FunctionalTestingFramework\AcceptanceTester; + +/** + */ +class SecretCredentialDataTestCest +{ + /** + * @Features({"AdminNotification"}) + * @param AcceptanceTester $I + * @return void + * @throws \Exception + */ + public function secretCredentialDataTest(AcceptanceTester $I) + { + $createProductWithFieldOverridesUsingHardcodedData1Fields['qty'] = "123"; + + $createProductWithFieldOverridesUsingHardcodedData1Fields['price'] = "12.34"; + + $I->comment("[createProductWithFieldOverridesUsingHardcodedData1] create '_defaultProduct' entity"); + $I->createEntity( + "createProductWithFieldOverridesUsingHardcodedData1", + "test", + "_defaultProduct", + [], + $createProductWithFieldOverridesUsingHardcodedData1Fields + ); + + $createProductWithFieldOverridesUsingSecretCredData1Fields['qty'] = + $I->getSecret("payment_authorizenet_trans_key"); + + $createProductWithFieldOverridesUsingSecretCredData1Fields['price'] = + $I->getSecret("carriers_dhl_account_eu"); + + $I->comment("[createProductWithFieldOverridesUsingSecretCredData1] create '_defaultProduct' entity"); + $I->createEntity( + "createProductWithFieldOverridesUsingSecretCredData1", + "test", + "_defaultProduct", + [], + $createProductWithFieldOverridesUsingSecretCredData1Fields + ); + + $I->fillField("#username", "Hardcoded"); // stepKey: fillFieldUsingHardCodedData1 + $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 " . + $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..d7e17bb26 100644 --- a/dev/tests/verification/Tests/SkippedGenerationTest.php +++ b/dev/tests/verification/Tests/SkippedGenerationTest.php @@ -1,8 +1,9 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2018 Adobe + * All Rights Reserved. */ + namespace tests\verification\Tests; use tests\util\MftfTestCase; @@ -43,13 +44,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..62944e02d --- /dev/null +++ b/dev/tests/verification/Tests/StaticCheck/DeprecationStaticCheckTest.php @@ -0,0 +1,61 @@ +<?php +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ + +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; +use ReflectionClass; + +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); + $reflectionClass = new ReflectionClass(StaticChecksList::class); + $reflectionClass->setStaticPropertyValue('errorFilesPath', 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 + { + $reflectionClass = new ReflectionClass(StaticChecksList::class); + $reflectionClass->setStaticPropertyValue('errorFilesPath', 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..daf7364ba --- /dev/null +++ b/dev/tests/verification/Tests/StaticCheck/PauseActionStaticCheckTest.php @@ -0,0 +1,64 @@ +<?php +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ + +namespace tests\verification\Tests; + +use Exception; +use Magento\FunctionalTestingFramework\StaticCheck\PauseActionUsageCheck; +use Magento\FunctionalTestingFramework\StaticCheck\StaticChecksList; +use Magento\FunctionalTestingFramework\Suite\Handlers\SuiteObjectHandler; +use ReflectionProperty; +use Symfony\Component\Console\Input\InputInterface; +use tests\util\MftfStaticTestCase; +use ReflectionClass; + +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); + $reflectionClass = new ReflectionClass(StaticChecksList::class); + $reflectionClass->setStaticPropertyValue('errorFilesPath', 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 + { + $reflectionClass = new ReflectionClass(StaticChecksList::class); + $reflectionClass->setStaticPropertyValue('errorFilesPath', null); + } +} diff --git a/dev/tests/verification/Tests/SuiteGenerationTest.php b/dev/tests/verification/Tests/SuiteGenerationTest.php index 52b34b3ef..ef6d5be91 100644 --- a/dev/tests/verification/Tests/SuiteGenerationTest.php +++ b/dev/tests/verification/Tests/SuiteGenerationTest.php @@ -1,17 +1,19 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ 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 */ @@ -285,11 +321,167 @@ public function testSuiteGenerationSingleRun() $this->assertEquals($expectedManifest, file_get_contents(self::getManifestFilePath())); } + /** + * Test extends tests generation in a suite + */ + public function testSuiteGenerationWithExtends() + { + $groupName = 'suiteExtends'; + + $expectedFileNames = 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 . + $groupName . + DIRECTORY_SEPARATOR; + + // Validate tests have been generated + $dirContents = array_diff(scandir($suiteResultBaseDir), ['..', '.']); + + foreach ($expectedFileNames as $expectedFileName) { + $this->assertTrue(in_array($expectedFileName, $dirContents)); + $this->assertFileEquals( + self::RESOURCES_PATH . DIRECTORY_SEPARATOR + . substr($expectedFileName, 0, strlen($expectedFileName)-4) + . ".txt", + $suiteResultBaseDir . $expectedFileName + ); + } + } + + /** + * Test comments generated during suite generation + */ + public function testSuiteCommentsGeneration() + { + $groupName = 'functionalSuiteWithComments'; + + $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); + + // 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 + ); + } + /** * revert any changes made to config.yml * remove _generated directory */ - public function tearDown() + public function tearDown(): void { DirSetupUtil::rmdirRecursive(self::GENERATE_RESULT_DIR); @@ -298,12 +490,16 @@ public function tearDown() $fileSystem->remove( self::CONFIG_YML_FILE ); + + $property = new \ReflectionProperty(DirSetupUtil::class, "DIR_CONTEXT"); + $property->setAccessible(true); + $property->setValue(null, []); } /** * Remove yml if created during tests and did not exist before */ - public static function tearDownAfterClass() + public static function tearDownAfterClass(): void { TestLoggingUtil::getInstance()->clearMockLoggingUtil(); } @@ -312,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..d6030e4fd --- /dev/null +++ b/dev/tests/verification/Tests/SuiteTestReferences.php @@ -0,0 +1,68 @@ +<?php +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ + +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/Tests/XmlDuplicateGenerationTest.php b/dev/tests/verification/Tests/XmlDuplicateGenerationTest.php index e9f65b9aa..5950e84a7 100644 --- a/dev/tests/verification/Tests/XmlDuplicateGenerationTest.php +++ b/dev/tests/verification/Tests/XmlDuplicateGenerationTest.php @@ -1,15 +1,16 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2018 Adobe + * All Rights Reserved. */ + namespace tests\verification\Tests; use Magento\FunctionalTestingFramework\Test\Handlers\ActionGroupObjectHandler; use Magento\FunctionalTestingFramework\Test\Handlers\TestObjectHandler; use tests\util\MftfTestCase; -class XmlDuplicateGerationTest extends MftfTestCase +class XmlDuplicateGenerationTest extends MftfTestCase { const XML_DUPLICATE_TEST = 'XmlDuplicateTest'; const XML_DUPLICATE_ACTIONGROUP = 'xmlDuplicateActionGroup'; diff --git a/dev/tests/verification/_suite/functionalSuite.xml b/dev/tests/verification/_suite/functionalSuite.xml deleted file mode 100644 index 439445c24..000000000 --- a/dev/tests/verification/_suite/functionalSuite.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. - */ ---> - -<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> -</suites> diff --git a/docs/configuration.md b/docs/configuration.md new file mode 100644 index 000000000..de7fd5859 --- /dev/null +++ b/docs/configuration.md @@ -0,0 +1,436 @@ +# Configuration + +The `*.env` file provides additional configuration for the Magento Functional Testing Framework (MFTF). +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 MFTF can function correctly. + +### MAGENTO_BASE_URL + +The root URL of the Magento application under test. + +Example: + +```conf +MAGENTO_BASE_URL=http://magento2.vagrant251 +``` + +<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`][]. +</div> + +### MAGENTO_BACKEND_NAME + +The path to the Magento Admin page. + +Example: + +```conf +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 + +Example: + +```conf +MAGENTO_ADMIN_USERNAME=admin +``` + +## Advanced 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 + +Sets a default value for the `timezone` attribute of a [`generateDate` action][generateDate]. +This value is applied when a test step does not specify a time zone. +For the complete list of available time zones, refer to [List of Supported Timezones][timezones]. + +Default: `America/Los_Angeles`. + +Example: + +```conf +DEFAULT_TIMEZONE=UTC +``` + +### SELENIUM + +The `SELENIUM_*` values form the URL of a custom Selenium server for running testing. + +Default Selenium URL: `http://127.0.0.1:4444/wd/hub` + +And the default configuration: + +```conf +SELENIUM_HOST=127.0.0.1 +SELENIUM_PORT=4444 +SELENIUM_PROTOCOL=http +SELENIUM_PATH=/wd/hub +``` + +<div class="bs-callout bs-callout-warning" markdown="1"> +`SELENIUM_*` values are required if you are running Selenium on an external system. +If you change the configuration of the external Selenium server, you must update these values. +</div> + +#### SELENIUM_HOST + +Override the default Selenium server host. + +Example: + +```conf +SELENIUM_HOST=user:pass@ondemand.saucelabs.com +``` + +#### SELENIUM_PORT + +Override the default Selenium server port. + +Example: + +```conf +SELENIUM_PORT=443 +``` + +#### SELENIUM_PROTOCOL + +Override the default Selenium server protocol. + +Example: + +```conf +SELENIUM_PROTOCOL=https +``` + +#### SELENIUM_PATH + +Override the default Selenium server path. + +Example: + +```conf +SELENIUM_PATH=/wd/hub +``` + +### MAGENTO_RESTAPI + +These `MAGENTO_RESTAPI_*` values are optional and can be used in cases when your Magento instance has a different API path than the one in `MAGENTO_BASE_URL`. + +```conf +MAGENTO_RESTAPI_SERVER_HOST +MAGENTO_RESTAPI_SERVER_PORT +``` + +#### MAGENTO_RESTAPI_SERVER_HOST + +The protocol and the host of the REST API server path. + +Example: + +```conf +MAGENTO_RESTAPI_SERVER_HOST=http://localhost +``` + +#### MAGENTO_RESTAPI_SERVER_PORT + +The port part of the API path. + +Example: + +```conf +MAGENTO_RESTAPI_SERVER_PORT=5000 +``` + +### \*_BP + +Settings to override base paths for the framework. +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 +TESTS_BP +FW_BP +TESTS_MODULES_PATH +``` + +#### MAGENTO_BP + +The path to a local Magento codebase. +It enables the [`bin/mftf`][mftf] commands such as `run` and `generate` to parse all modules of the Magento codebase for MFTF tests. + +```conf +MAGENTO_BP=~/magento2/ +``` + +#### TESTS_BP + +BP is an acronym for _Base Path_. +The path to where MFTF supplementary files are located in the Magento codebase. + +Example: + +```conf +TESTS_BP=~/magento2ce/dev/tests/acceptance +``` + +#### FW_BP + +The path to MFTF. +FW_BP is an acronym for _FrameWork Base Path_. + +Example: + +```conf +FW_BP=~/magento/magento2-functional-testing-framework +``` + +### TESTS_MODULE_PATH + +The path to where the MFTF modules mirror Magento modules. + +Example: + +```conf +TESTS_MODULE_PATH=~/magento2/dev/tests/acceptance/tests/functional/Magento +``` + +### MODULE_ALLOWLIST + +Use for a new module. +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_ALLOWLIST=Magento_Framework,Magento_ConfigurableProductWishlist,Magento_ConfigurableProductCatalogSearch +``` + +### MAGENTO_CLI_COMMAND_PATH + +Path to the Magento CLI command entry point. + +Default: `dev/tests/acceptance/utils/command.php`. +It points to `MAGENTO_BASE_URL` + `dev/tests/acceptance/utils/command.php` + +Modify the default value: + +- for non-default Magento installation +- when using a subdirectory in the `MAGENTO_BASE_URL` + +Example: `dev/tests/acceptance/utils/command.php` + +### BROWSER + +Override the default browser performing the tests. + +Default: Chrome + +Example: + +```conf +BROWSER=firefox +``` + +### CREDENTIAL_VAULT_ADDRESS + +The Api address for a vault server. + +Default: http://127.0.0.1:8200 + +Example: + +```conf +# Default api address for local vault dev server +CREDENTIAL_VAULT_ADDRESS=http://127.0.0.1:8200 +``` + +### CREDENTIAL_VAULT_SECRET_BASE_PATH + +Vault secret engine base path. + +Default: secret + +Example: + +```conf +# Default base path for kv secret engine in local vault dev server +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]: https://php.net/manual/en/timezones.php diff --git a/etc/config/.credentials.example b/etc/config/.credentials.example index ea8b03480..fe6dd19a9 100644 --- a/etc/config/.credentials.example +++ b/etc/config/.credentials.example @@ -1,75 +1,77 @@ -#carriers/fedex/account= -#carriers/fedex/meter_number= -#carriers/fedex/key= -#carriers/fedex/password= - -#carriers/ups/password= -#carriers/ups/username= -#carriers/ups/access_license_number= -#carriers/ups/shipper_number= - -#carriers/usps/userid= -#carriers/usps/password= - -#carriers_dhl_id_us= -#carriers_dhl_password_us= -#carriers_dhl_account_us= - -#carriers_dhl_id_eu= -#carriers_dhl_password_eu= -#carriers_dhl_account_eu= - - -#payment_authorizenet_login= -#payment_authorizenet_trans_key= -#payment_authorizenet_trans_md5= - -#authorizenet_fraud_review_login= -#authorizenet_fraud_review_trans_key= -#authorizenet_fraud_review_md5= - -#braintree_enabled_fraud_merchant_account_id= -#braintree_enabled_fraud_merchant_id= -#braintree_enabled_fraud_public_key= -#braintree_enabled_fraud_private_key= - -#braintree_disabled_fraud_merchant_account_id= -#braintree_disabled_fraud_merchant_id= -#braintree_disabled_fraud_public_key= -#braintree_disabled_fraud_private_key= - -#payment/paypal_group_all_in_one/wpp_usuk/wpp_required_settings/wpp_and_express_checkout/business_account= -#payment/paypal_group_all_in_one/wpp_usuk/wpp_required_settings/wpp_and_express_checkout/api_username= -#payment/paypal_group_all_in_one/wpp_usuk/wpp_required_settings/wpp_and_express_checkout/api_password= -#payment/paypal_group_all_in_one/wpp_usuk/wpp_required_settings/wpp_and_express_checkout/api_signature= -#payment/paypal_express/merchant_id= - -#payflow_pro_fraud_protection_enabled_business_account= -#payflow_pro_fraud_protection_enabled_partner= -#payflow_pro_fraud_protection_enabled_user= -#payflow_pro_fraud_protection_enabled_pwd= -#payflow_pro_fraud_protection_enabled_vendor= - -#payflow_pro_business_account= -#payflow_pro_partner= -#payflow_pro_user= -#payflow_pro_pwd= -#payflow_pro_vendor= - -#payflow_link_business_account_email= -#payflow_link_partner= -#payflow_link_user= -#payflow_link_password= -#payflow_link_vendor= - -#payment/paypal_group_all_in_one/payments_pro_hosted_solution_with_express_checkout/pphs_required_settings/pphs_required_settings_pphs/business_account= -#payment/paypal_group_all_in_one/payments_pro_hosted_solution_with_express_checkout/pphs_required_settings/pphs_required_settings_pphs/api_username= -#payment/paypal_group_all_in_one/payments_pro_hosted_solution_with_express_checkout/pphs_required_settings/pphs_required_settings_pphs/api_password= -#payment/paypal_group_all_in_one/payments_pro_hosted_solution_with_express_checkout/pphs_required_settings/pphs_required_settings_pphs/api_signature= - -#payment/paypal_alternative_payment_methods/express_checkout_us/express_checkout_required/express_checkout_required_express_checkout/business_account= -#payment/paypal_alternative_payment_methods/express_checkout_us/express_checkout_required/express_checkout_required_express_checkout/api_username= -#payment/paypal_alternative_payment_methods/express_checkout_us/express_checkout_required/express_checkout_required_express_checkout/api_password= -#payment/paypal_alternative_payment_methods/express_checkout_us/express_checkout_required/express_checkout_required_express_checkout/api_signature= - -#fraud_protection/signifyd/api_key= \ No newline at end of file +magento/tfa/OTP_SHARED_SECRET +magento/MAGENTO_ADMIN_PASSWORD + +#magento/carriers_fedex_account= +#magento/carriers_fedex_meter_number= +#magento/carriers_fedex_key= +#magento/carriers_fedex_password= + +#magento/carriers_ups_password= +#magento/carriers_ups_username= +#magento/carriers_ups_access_license_number= +#magento/carriers_ups_shipper_number= + +#magento/carriers_usps_userid= +#magento/carriers_usps_password= + +#magento/carriers_dhl_id_us= +#magento/carriers_dhl_password_us= +#magento/carriers_dhl_account_us= + +#magento/carriers_dhl_id_eu= +#magento/carriers_dhl_password_eu= +#magento/carriers_dhl_account_eu= + + +#magento/payment_authorizenet_login= +#magento/payment_authorizenet_trans_key= +#magento/payment_authorizenet_trans_md5= + +#magento/authorizenet_fraud_review_login= +#magento/authorizenet_fraud_review_trans_key= +#magento/authorizenet_fraud_review_md5= + +#magento/braintree_enabled_fraud_merchant_account_id= +#magento/braintree_enabled_fraud_merchant_id= +#magento/braintree_enabled_fraud_public_key= +#magento/braintree_enabled_fraud_private_key= + +#magento/braintree_disabled_fraud_merchant_account_id= +#magento/braintree_disabled_fraud_merchant_id= +#magento/braintree_disabled_fraud_public_key= +#magento/braintree_disabled_fraud_private_key= + +#magento/payment_paypal_group_all_in_one_wpp_usuk_wpp_required_settings_wpp_and_express_checkout_business_account= +#magento/payment_paypal_group_all_in_one_wpp_usuk_wpp_required_settings_wpp_and_express_checkout_api_username= +#magento/payment_paypal_group_all_in_one_wpp_usuk_wpp_required_settings_wpp_and_express_checkout_api_password= +#magento/payment_paypal_group_all_in_one_wpp_usuk_wpp_required_settings_wpp_and_express_checkout_api_signature= +#magento/payment_paypal_express_merchant_id= + +#magento/payflow_pro_fraud_protection_enabled_business_account= +#magento/payflow_pro_fraud_protection_enabled_partner= +#magento/payflow_pro_fraud_protection_enabled_user= +#magento/payflow_pro_fraud_protection_enabled_pwd= +#magento/payflow_pro_fraud_protection_enabled_vendor= + +#magento/payflow_pro_business_account= +#magento/payflow_pro_partner= +#magento/payflow_pro_user= +#magento/payflow_pro_pwd= +#magento/payflow_pro_vendor= + +#magento/payflow_link_business_account_email= +#magento/payflow_link_partner= +#magento/payflow_link_user= +#magento/payflow_link_password= +#magento/payflow_link_vendor= + +#magento/payment_paypal_group_all_in_one_payments_pro_hosted_solution_with_express_checkout_pphs_required_settings_pphs_required_settings_pphs_business_account= +#magento/payment_paypal_group_all_in_one_payments_pro_hosted_solution_with_express_checkout_pphs_required_settings_pphs_required_settings_pphs_api_username= +#magento/payment_paypal_group_all_in_one_payments_pro_hosted_solution_with_express_checkout_pphs_required_settings_pphs_required_settings_pphs_api_password= +#magento/payment_paypal_group_all_in_one_payments_pro_hosted_solution_with_express_checkout_pphs_required_settings_pphs_required_settings_pphs_api_signature= + +#magento/payment_paypal_alternative_payment_methods_express_checkout_us_express_checkout_required_express_checkout_required_express_checkout_business_account= +#magento/payment_paypal_alternative_payment_methods_express_checkout_us_express_checkout_required_express_checkout_required_express_checkout_api_username= +#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= + diff --git a/etc/config/.env.example b/etc/config/.env.example index 17ea384ca..b3ec2ad41 100644 --- a/etc/config/.env.example +++ b/etc/config/.env.example @@ -4,10 +4,12 @@ #*** Set the base URL for your Magento instance ***# 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_URL=http://admin.example.com/ + #*** Set the Admin Username and Password for your Magento instance ***# MAGENTO_BACKEND_NAME=admin MAGENTO_ADMIN_USERNAME=admin -MAGENTO_ADMIN_PASSWORD=123123q #*** Path to CLI entry point and command parameter name. Uncomment and change if folder structure differs from standard Magento installation #MAGENTO_CLI_COMMAND_PATH=dev/tests/acceptance/utils/command.php @@ -18,13 +20,25 @@ 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 +WINDOW_WIDTH=1920 +WINDOW_HEIGHT=1080 #*** Uncomment and set host & port if your dev environment needs different value other than MAGENTO_BASE_URL for Rest API Requests ***# -#MAGENTO_RESTAPI_SERVER_HOST= -#MAGENTO_RESTAPI_SERVER_PORT= +#MAGENTO_RESTAPI_SERVER_HOST=restapi.magento.com +#MAGENTO_RESTAPI_SERVER_PORT=8080 +#MAGENTO_RESTAPI_SERVER_PROTOCOL=https + +#*** 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= @@ -35,9 +49,35 @@ 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,Magento_ConfigurableProductWishlist,Magento_ConfigurableProductCatalogSearch +MODULE_ALLOWLIST=Magento_Framework,ConfigurableProductWishlist,ConfigurableProductCatalogSearch #CUSTOM_MODULE_PATHS= #*** Bool property which allows the user to toggle debug output during test execution #MFTF_DEBUG= + +#*** Bool property which allows the user to generate and run tests marked as skipped +#ALLOW_SKIPPED=true + +#*** Default timeout for wait actions +WAIT_TIMEOUT=60 + +#*** Default timeout for 'magentoCLI' and 'magentoCLISecret' command +MAGENTO_CLI_WAIT_TIMEOUT=60 + +#*** 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/codeception.dist.yml b/etc/config/codeception.dist.yml index 273ede0b0..54858dd6a 100755 --- a/etc/config/codeception.dist.yml +++ b/etc/config/codeception.dist.yml @@ -7,21 +7,25 @@ paths: data: tests/_data support: src/Magento/FunctionalTestingFramework envs: etc/_envs + output: tests/_output settings: + silent: true colors: true memory_limit: 1024M extensions: enabled: - - Codeception\Extension\RunFailed + - Magento\FunctionalTestingFramework\Codeception\Subscriber\Console - Magento\FunctionalTestingFramework\Extension\TestContextExtension - - Magento\FunctionalTestingFramework\Allure\Adapter\MagentoAllureAdapter + - Qameta\Allure\Codeception\AllureCodeception config: - Yandex\Allure\Adapter\AllureAdapter: - deletePreviousResults: true + Qameta\Allure\Codeception\AllureCodeception: + deletePreviousResults: false outputDirectory: allure-results ignoredAnnotations: - env - zephyrId - useCaseId + Magento\FunctionalTestingFramework\Extension\TestContextExtension: + driver: \Magento\FunctionalTestingFramework\Module\MagentoWebDriver params: - - .env \ No newline at end of file + - .env diff --git a/etc/config/command.php b/etc/config/command.php index b24bafd31..a7b84563f 100644 --- a/etc/config/command.php +++ b/etc/config/command.php @@ -1,37 +1,72 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2018 Adobe + * All Rights Reserved. */ -if (isset($_POST['command'])) { - $command = urldecode($_POST['command']); - if (array_key_exists("arguments", $_POST)) { - $arguments = urldecode($_POST['arguments']); - } else { - $arguments = null; - } - $php = PHP_BINDIR ? PHP_BINDIR . '/php' : 'php'; - $valid = validateCommand($command); - if ($valid) { - exec( - escapeCommand($php . ' -f ../../../../bin/magento ' . $command) . " $arguments" ." 2>&1", - $output, - $exitCode - ); - if ($exitCode == 0) { - http_response_code(202); +require_once __DIR__ . '/../../../../app/bootstrap.php'; + +if (!empty($_POST['token']) && !empty($_POST['command'])) { + $magentoObjectManagerFactory = \Magento\Framework\App\Bootstrap::createObjectManagerFactory(BP, $_SERVER); + $magentoObjectManager = $magentoObjectManagerFactory->create($_SERVER); + $tokenModel = $magentoObjectManager->get(\Magento\Integration\Model\Oauth\Token::class); + + $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)) { + $php = PHP_BINDIR ? PHP_BINDIR . '/php' : 'php'; + $magentoBinary = $php . ' -f ../../../../bin/magento'; + $valid = validateCommand($magentoBinary, $command); + if ($valid) { + $fullCommand = escapeshellcmd($magentoBinary . " $command" . " $arguments"); + $process = Symfony\Component\Process\Process::fromShellCommandline($fullCommand); + $process->setIdleTimeout($timeout); + $process->setTimeout(0); + $idleTimeout = false; + try { + $process->run(); + $output = $process->getOutput(); + if (!$process->isSuccessful()) { + $failureOutput = $process->getErrorOutput(); + if (!empty($failureOutput)) { + $output = $failureOutput; + } + } + if (empty($output)) { + $output = "CLI did not return output."; + } + + } catch (Symfony\Component\Process\Exception\ProcessTimedOutException $exception) { + $output = "CLI command timed out, no output available."; + $idleTimeout = true; + } + + $exitCode = $process->getExitCode(); + + if ($process->isSuccessful() || $idleTimeout) { + http_response_code(202); + } else { + http_response_code(500); + } + + // Suppress file paths from output + echo suppressFilePaths($output); } else { - http_response_code(500); + http_response_code(403); + echo "Given command not found valid in Magento CLI Command list."; } - echo implode("\n", $output); } else { - http_response_code(403); - echo "Given command not found valid in Magento CLI Command list."; + http_response_code(401); + echo("Command not unauthorized."); } } else { http_response_code(412); - echo("Command parameter is not set."); + echo("Required parameters are not set."); } /** @@ -55,13 +90,13 @@ function escapeCommand($command) /** * Checks magento list of CLI commands for given $command. Does not check command parameters, just base command. + * @param string $magentoBinary * @param string $command * @return bool */ -function validateCommand($command) +function validateCommand($magentoBinary, $command) { - $php = PHP_BINDIR ? PHP_BINDIR . '/php' : 'php'; - exec($php . ' -f ../../../../bin/magento list', $commandList); + exec($magentoBinary . ' list', $commandList); // Trim list of commands after first whitespace $commandList = array_map("trimAfterWhitespace", $commandList); return in_array(trimAfterWhitespace($command), $commandList); @@ -76,3 +111,23 @@ function trimAfterWhitespace($string) { return strtok($string, ' '); } + +/** + * Suppress file paths in string. + * @param string $string + * @return string + */ +function suppressFilePaths(string $string): string +{ + // Match file paths on both *nix and Windows system + $filePathPattern = '~(?:[A-Za-z]:[\\\/]|\\\\|\/)\S+~'; + $replacement = '[suppressed_path]'; + + preg_match_all($filePathPattern, $string, $matches); + if (!empty($matches)) { + foreach ($matches[0] as $match) { + $string = str_replace($match, $replacement, $string); + } + } + return $string; +} diff --git a/etc/config/functional.suite.dist.yml b/etc/config/functional.suite.dist.yml index da05a13e3..25cad8342 100644 --- a/etc/config/functional.suite.dist.yml +++ b/etc/config/functional.suite.dist.yml @@ -7,36 +7,35 @@ # Perform tests in browser using the WebDriver or PhpBrowser. # If you need both WebDriver and PHPBrowser tests - create a separate suite. -class_name: AcceptanceTester +actor: AcceptanceTester namespace: Magento\FunctionalTestingFramework modules: enabled: - \Magento\FunctionalTestingFramework\Module\MagentoWebDriver - - \Magento\FunctionalTestingFramework\Helper\Acceptance - - \Magento\FunctionalTestingFramework\Helper\MagentoFakerData - - \Magento\FunctionalTestingFramework\Module\MagentoRestDriver: - url: "%MAGENTO_BASE_URL%/rest/default/V1/" - username: "%MAGENTO_ADMIN_USERNAME%" - password: "%MAGENTO_ADMIN_PASSWORD%" - depends: PhpBrowser - part: Json - \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%" + backend_url: "%MAGENTO_BACKEND_BASE_URL%" backend_name: "%MAGENTO_BACKEND_NAME%" browser: 'chrome' restart: true - window_size: 1280x1024 + window_size: 1920x1080 username: "%MAGENTO_ADMIN_USERNAME%" password: "%MAGENTO_ADMIN_PASSWORD%" - pageload_timeout: 30 - host: %SELENIUM_HOST% - port: %SELENIUM_PORT% - protocol: %SELENIUM_PROTOCOL% - path: %SELENIUM_PATH% + 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=1920,1080", "--disable-extensions", "--enable-automation", "--disable-gpu", "--enable-Passthrough", "--disable-dev-shm-usage", "--disable-component-update", "--disable-features=OptimizationHints","--disable-background-networking","--disable-domain-reliability","--disable-breakpad"] diff --git a/etc/di.xml b/etc/di.xml index 60faed3a0..2e9283561 100644 --- a/etc/di.xml +++ b/etc/di.xml @@ -1,14 +1,14 @@ <?xml version="1.0"?> <!-- /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ --> <!-- 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|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|waitForElementClickable|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 deleted file mode 100644 index d23af52da..000000000 --- a/src/Magento/FunctionalTestingFramework/Allure/Adapter/MagentoAllureAdapter.php +++ /dev/null @@ -1,120 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -namespace Magento\FunctionalTestingFramework\Allure\Adapter; - -use Magento\FunctionalTestingFramework\Data\Argument\Interpreter\NullType; -use Magento\FunctionalTestingFramework\Suite\Handlers\SuiteObjectHandler; -use Yandex\Allure\Adapter\AllureAdapter; -use Yandex\Allure\Adapter\Event\StepStartedEvent; -use Codeception\Event\SuiteEvent; -use Codeception\Event\StepEvent; - -/** - * Class MagentoAllureAdapter - * - * Extends AllureAdapter to provide further information for allure reports - * - * @package Magento\FunctionalTestingFramework\Allure - */ - -class MagentoAllureAdapter extends AllureAdapter -{ - /** - * Array of group values passed to test runner command - * - * @return string - */ - private function getGroup() - { - if ($this->options['groups'] != null) { - return $this->options['groups'][0]; - } - return null; - } - - /** - * Override of parent method to set suitename as suitename and group name concatenated - * - * @param SuiteEvent $suiteEvent - * @return void - */ - public function suiteBefore(SuiteEvent $suiteEvent) - { - $changeSuiteEvent = $suiteEvent; - - if ($this->getGroup() != null) { - $suite = $suiteEvent->getSuite(); - $suiteName = ($suite->getName()) . "\\" . $this->sanitizeGroupName($this->getGroup()); - - call_user_func(\Closure::bind( - function () use ($suite, $suiteName) { - $suite->name = $suiteName; - }, - null, - $suite - )); - - //change suiteEvent - $changeSuiteEvent = new SuiteEvent( - $suiteEvent->getSuite(), - $suiteEvent->getResult(), - $suiteEvent->getSettings() - ); - } - // call parent function - parent::suiteBefore($changeSuiteEvent); - } - - /** - * Function which santizes any group names changed by the framework for execution in order to consolidate reporting. - * - * @param string $group - * @return string - */ - private function sanitizeGroupName($group) - { - $suiteNames = array_keys(SuiteObjectHandler::getInstance()->getAllObjects()); - $exactMatch = in_array($group, $suiteNames); - - // if this is an existing suite name we dont' need to worry about changing it - if ($exactMatch || strpos($group, "_") === false) { - return $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); - $originalName = implode("_", $groupNameSplit); - - // confirm our original name is one of the existing suite names otherwise just return the original group name - $originalName = in_array($originalName, $suiteNames) ? $originalName : $group; - return $originalName; - } - - /** - * Override of parent method, only different to prevent replacing of . to • - * - * @param StepEvent $stepEvent - * @return void - */ - public function stepBefore(StepEvent $stepEvent) - { - //Hard set to 200; we don't expose this config in MFTF - $argumentsLength = 200; - $stepAction = $stepEvent->getStep()->getHumanizedActionWithoutArguments(); - $stepArgs = $stepEvent->getStep()->getArgumentsAsString($argumentsLength); - - if (!trim($stepAction)) { - $stepAction = $stepEvent->getStep()->getMetaStep()->getHumanizedActionWithoutArguments(); - $stepArgs = $stepEvent->getStep()->getMetaStep()->getArgumentsAsString($argumentsLength); - } - - $stepName = $stepAction . ' ' . $stepArgs; - - $this->emptyStep = false; - $this->getLifecycle()->fire(new StepStartedEvent($stepName)); - } -} diff --git a/src/Magento/FunctionalTestingFramework/Allure/AllureHelper.php b/src/Magento/FunctionalTestingFramework/Allure/AllureHelper.php new file mode 100644 index 000000000..4221b5620 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Allure/AllureHelper.php @@ -0,0 +1,82 @@ +<?php +/** + * Copyright 2019 Adobe + * All Rights Reserved. + */ + +declare(strict_types=1); + +namespace Magento\FunctionalTestingFramework\Allure; + +use Qameta\Allure\Allure; +use Qameta\Allure\Io\DataSourceInterface; + +class AllureHelper +{ + /** + * Adds attachment to the current step. + * + * @param mixed $data + * @param string $caption + * + * @return void + */ + public static function addAttachmentToCurrentStep($data, $caption): void + { + if (!is_string($data)) { + try { + $data = serialize($data); + } catch (\Exception $exception) { + throw new \Exception($data->getMessage()); + } + } + if (@file_exists($data) && is_file($data)) { + Allure::attachmentFile($caption, $data); + } else { + Allure::attachment($caption, $data); + } + } + + /** + * 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): void + { + if (!is_string($data)) { + $data = serialize($data); + } + if (@file_exists($data) && is_file($data)) { + Allure::attachmentFile($caption, $data); + } else { + Allure::attachment($caption, $data); + } + } + + /** + * @param DataSourceInterface $dataSource + * @param string $name + * @param string|null $type + * @param string|null $fileExtension + * @return void + */ + public static function doAddAttachment( + DataSourceInterface $dataSource, + string $name, + ?string $type = null, + ?string $fileExtension = null, + ): void { + $attachment = Allure::getConfig() + ->getResultFactory() + ->createAttachment() + ->setName($name) + ->setType($type) + ->setFileExtension($fileExtension); + Allure::getLifecycle()->addAttachment($attachment, $dataSource); + } +} diff --git a/src/Magento/FunctionalTestingFramework/Allure/Event/AddUniqueAttachmentEvent.php b/src/Magento/FunctionalTestingFramework/Allure/Event/AddUniqueAttachmentEvent.php new file mode 100644 index 000000000..a478f13f3 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Allure/Event/AddUniqueAttachmentEvent.php @@ -0,0 +1,107 @@ +<?php +/** + * Copyright 2019 Adobe + * All Rights Reserved. + */ + +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..3f1646790 100644 --- a/src/Magento/FunctionalTestingFramework/Code/Reader/ClassReader.php +++ b/src/Magento/FunctionalTestingFramework/Code/Reader/ClassReader.php @@ -1,8 +1,9 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ + namespace Magento\FunctionalTestingFramework\Code\Reader; class ClassReader implements ClassReaderInterface @@ -13,6 +14,7 @@ class ClassReader implements ClassReaderInterface * @param string $className * @return array|null * @throws \ReflectionException + * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ public function getConstructor($className) { @@ -24,9 +26,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/Code/Reader/ClassReaderInterface.php b/src/Magento/FunctionalTestingFramework/Code/Reader/ClassReaderInterface.php index ff49f1d37..1aa43ca11 100644 --- a/src/Magento/FunctionalTestingFramework/Code/Reader/ClassReaderInterface.php +++ b/src/Magento/FunctionalTestingFramework/Code/Reader/ClassReaderInterface.php @@ -1,7 +1,7 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ namespace Magento\FunctionalTestingFramework\Code\Reader; diff --git a/src/Magento/FunctionalTestingFramework/Codeception/Module/Sequence.php b/src/Magento/FunctionalTestingFramework/Codeception/Module/Sequence.php new file mode 100644 index 000000000..94277af25 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Codeception/Module/Sequence.php @@ -0,0 +1,69 @@ +<?php +/** + * Copyright 2024 Adobe + * All Rights Reserved. + */ + +declare(strict_types=1); + +namespace Magento\FunctionalTestingFramework\Codeception\Module; + +use Codeception\Module; +use Codeception\Exception\ModuleException; +use Codeception\TestInterface; + +/** + * Class Sequence + * Implemented here as a replacement for codeception/module-sequence due to PHP 8.4 deprecation errors. + * This class can be removed when PHP 8.4 compatibility is updated in codeception/module-sequence. + */ +class Sequence extends Module +{ + /** + * @var array<int|string,string> + */ + public static array $hash = [];// phpcs:ignore + + /** + * @var array<int|string,string> + */ + public static array $suiteHash = [];// phpcs:ignore + + /** + * @var string + */ + public static string $prefix = '';// phpcs:ignore + + /** + * @var array<string, string> + */ + protected array $config = ['prefix' => '{id}_'];// phpcs:ignore + + /** + * Initialise method + * @return void + */ + public function _initialize(): void + { + static::$prefix = $this->config['prefix']; + } + + /** + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * after method + * @return void + */ + public function _after(TestInterface $test): void + { + self::$hash = []; + } + + /** + * after suite method + * @return void + */ + public function _afterSuite(): void + { + self::$suiteHash = []; + } +} diff --git a/src/Magento/FunctionalTestingFramework/Codeception/Subscriber/Console.php b/src/Magento/FunctionalTestingFramework/Codeception/Subscriber/Console.php new file mode 100644 index 000000000..ddf3d7bc3 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Codeception/Subscriber/Console.php @@ -0,0 +1,252 @@ +<?php +/** + * Copyright 2019 Adobe + * All Rights Reserved. + */ + +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. + * + * @var array + */ + private $testFiles = []; + + /** + * Action group step key. + * + * @var null|string + */ + 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. + * + * @param array $extensionOptions + * @param array $options + * + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + 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): void + { + $test = $e->getTest(); + $testReflection = new \ReflectionClass($test); + + 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. + * + * @param StepEvent $e + * @return void + */ + public function beforeStep(StepEvent $e): void + { + if ($this->silent or !$this->steps or !$e->getTest() instanceof ScenarioDriven) { + 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) { + $this->message(' ' . $metaStep->getPrefix()) + ->style('bold') + ->append($metaStep->__toString()) + ->writeln(); + } + $this->metaStep = $metaStep; + + $this->printStepKeys($e->getStep()); + } + + /** + * If step failed we move back from action group to test scope + * + * @param StepEvent $e + * @return void + */ + public function afterStep(StepEvent $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; + } + } + + /** + * Print output to cli with stepKey. + * + * @param Step $step + * @return void + * @SuppressWarnings(PHPMD) + */ + private function printStepKeys(Step $step) + { + if ($step instanceof Comment and $step->__toString() === '') { + return; // don't print empty comments + } + + $stepKey = $this->retrieveStepKey($step); + + $isActionGroup = (strpos($step->__toString(), ActionGroupObject::ACTION_GROUP_CONTEXT_START) !== false); + if ($isActionGroup) { + preg_match(TestGenerator::ACTION_GROUP_STEP_KEY_REGEX, $step->__toString(), $matches); + if (!empty($matches['actionGroupStepKey'])) { + $this->actionGroupStepKey = ucfirst($matches['actionGroupStepKey']); + } + } + + if (strpos($step->__toString(), ActionGroupObject::ACTION_GROUP_CONTEXT_END) !== false) { + $this->actionGroupStepKey = null; + return; + } + + $msg = $this->message(); + if ($this->metaStep || ($this->actionGroupStepKey !== null && !$isActionGroup)) { + $msg->append(' '); + } + if ($stepKey !== null) { + $msg->append(OutputFormatter::escape("[" . $stepKey . "] ")); + $msg->style('bold'); + } + + if (!$this->metaStep) { + $msg->style('bold'); + } + + $stepString = str_replace( + [ActionGroupObject::ACTION_GROUP_CONTEXT_START, ActionGroupObject::ACTION_GROUP_CONTEXT_END], + '', + $step->toString(1000) + ); + + $msg->append(OutputFormatter::escape($stepString)); + if ($isActionGroup) { + $msg->style('comment'); + } + if ($this->metaStep || ($this->actionGroupStepKey !== null && !$isActionGroup)) { + $msg->style('info'); + } + $msg->writeln(); + } + + /** + * Message instance. + * + * @param string $string + * @return Message + */ + private function message($string = '') + { + return $this->messageFactory->message($string); + } + + /** + * Reading stepKey from file. + * + * @param Step $step + * @return string|null + */ + private function retrieveStepKey(Step $step) + { + $stepKey = null; + $stepLine = $step->getLineNumber(); + $filePath = $step->getFilePath(); + $stepLine = $stepLine - 1; + + if (!array_key_exists($filePath, $this->testFiles)) { + $this->testFiles[$filePath] = explode(PHP_EOL, file_get_contents($filePath)); + } + + preg_match(TestGenerator::ACTION_STEP_KEY_REGEX, $this->testFiles[$filePath][$stepLine], $matches); + if (!empty($matches['stepKey'])) { + $stepKey = $matches['stepKey']; + } + + if ($this->actionGroupStepKey !== null) { + $stepKey = str_replace($this->actionGroupStepKey, '', $stepKey); + } + + $stepKey = $stepKey === '[]' ? null : $stepKey; + + return $stepKey; + } +} diff --git a/src/Magento/FunctionalTestingFramework/Composer/AbstractComposer.php b/src/Magento/FunctionalTestingFramework/Composer/AbstractComposer.php new file mode 100644 index 000000000..7b9373720 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Composer/AbstractComposer.php @@ -0,0 +1,87 @@ +<?php +/** + * Copyright 2019 Adobe + * All Rights Reserved. + */ + +namespace Magento\FunctionalTestingFramework\Composer; + +use Composer\IO\BufferIO; + +/** + * Abstract Composer Handler + */ +abstract class AbstractComposer +{ + const TEST_MODULE_PACKAGE_TYPE = 'magento2-functional-test-module'; + const MAGENTO_MODULE_PACKAGE_TYPE = 'magento2-module'; + + const MODULE_NAME_IN_SUGGEST_REGEX_INDEX = 'module_name'; + const MODULE_NAME_IN_SUGGEST_REGEX = '/type:\s*' + . self::MAGENTO_MODULE_PACKAGE_TYPE + . '\s*,\s*name:\s*(?<' + . self::MODULE_NAME_IN_SUGGEST_REGEX_INDEX + . '>[^,\s]+_[^,\s]+)/'; + + /**#@+ + * Composer package array keys + */ + const PACKAGE_NAME = 'name'; + const PACKAGE_TYPE = 'type'; + const PACKAGE_VERSION = 'version'; + const PACKAGE_DESCRIPTION = 'description'; + const PACKAGE_INSTALLEDPATH = 'installedPath'; + const PACKAGE_REQUIRES = 'requires'; + const PACKAGE_DEVREQUIRES = 'devRequires'; + const PACKAGE_SUGGESTS = 'suggests'; + const PACKAGE_SUGGESTED_MAGENTO_MODULES = 'suggestedMagentoModules'; + /**#@-*/ + + /** + * @var \Composer\Composer + */ + protected $composer; + + /** + * @param string $composerFile + */ + public function __construct($composerFile) + { + $this->composer = \Composer\Factory::create(new BufferIO(), $composerFile); + } + + /** + * Get composer + * + * @return \Composer\Composer + */ + protected function getComposer() + { + return $this->composer; + } + + /** + * Parse input array and return all suggested magento module names, i.e. an example "suggest" in composer.json + * + * "suggest": { + * "magento/module-backend": "type: magento2-module, name: Magento_Backend, version: ~100.0.0", + * "magento/module-store": "type: magento2-module, name: Magento_Store, version: ~100.0.0" + * } + * + * @param array $suggests + * @return array + */ + protected function parseSuggestsForMagentoModuleNames($suggests) + { + $magentoModuleNames = []; + foreach ($suggests as $suggest) { + // Expecting pattern - type: magento2-module, name: Magento_Store, version: ~100.0.0 + preg_match(self::MODULE_NAME_IN_SUGGEST_REGEX, $suggest, $match); + if (isset($match[self::MODULE_NAME_IN_SUGGEST_REGEX_INDEX])) { + $magentoModuleNames[] = $match[self::MODULE_NAME_IN_SUGGEST_REGEX_INDEX]; + } + } + + return array_unique($magentoModuleNames); + } +} diff --git a/src/Magento/FunctionalTestingFramework/Composer/ComposerInstall.php b/src/Magento/FunctionalTestingFramework/Composer/ComposerInstall.php new file mode 100644 index 000000000..e375c0306 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Composer/ComposerInstall.php @@ -0,0 +1,103 @@ +<?php +/** + * Copyright 2019 Adobe + * All Rights Reserved. + */ + +namespace Magento\FunctionalTestingFramework\Composer; + +use Composer\Package\CompletePackageInterface; + +/** + * Class ComposerInstaller handles information and dependencies for composer installed packages + */ +class ComposerInstall extends AbstractComposer +{ + /** + * @var \Composer\Package\Locker + */ + private $locker; + + /** + * Determines if package is a mftf test package + * + * @param string $packageName + * @return boolean + */ + public function isMftfTestPackage($packageName) + { + return $this->isInstalledPackageOfType($packageName, self::TEST_MODULE_PACKAGE_TYPE); + } + + /** + * Determines if package is a magento package + * + * @param string $packageName + * @return boolean + */ + public function isMagentoPackage($packageName) + { + return $this->isInstalledPackageOfType($packageName, self::MAGENTO_MODULE_PACKAGE_TYPE); + } + + /** + * Determines if an installed package is of a certain type + * + * @param string $packageName + * @param string $packageType + * @return boolean + */ + public function isInstalledPackageOfType($packageName, $packageType) + { + /** @var CompletePackageInterface $package */ + foreach ($this->getLocker()->getLockedRepository()->getPackages() as $package) { + if (($package->getName() === $packageName) && ($package->getType() === $packageType)) { + return true; + } + } + return false; + } + + /** + * Collect all installed mftf test packages from composer lock + * + * @return array + */ + public function getInstalledTestPackages() + { + $packages = []; + /** @var CompletePackageInterface $package */ + foreach ($this->getLocker()->getLockedRepository()->getPackages() as $package) { + if ($package->getType() === self::TEST_MODULE_PACKAGE_TYPE) { + $packages[$package->getName()] = [ + self::PACKAGE_NAME => $package->getName(), + self::PACKAGE_TYPE => $package->getType(), + self::PACKAGE_VERSION => $package->getPrettyVersion(), + self::PACKAGE_DESCRIPTION => $package->getDescription(), + self::PACKAGE_SUGGESTS => $package->getSuggests(), + self::PACKAGE_REQUIRES => $package->getRequires(), + self::PACKAGE_DEVREQUIRES => $package->getDevRequires(), + self::PACKAGE_SUGGESTED_MAGENTO_MODULES => $this->parseSuggestsForMagentoModuleNames( + $package->getSuggests() + ), + self::PACKAGE_INSTALLEDPATH => $this->getComposer()->getInstallationManager() + ->getInstallPath($package) + ]; + } + } + return $packages; + } + + /** + * Load locker + * + * @return \Composer\Package\Locker + */ + private function getLocker() + { + if (!$this->locker) { + $this->locker = $this->getComposer()->getLocker(); + } + return $this->locker; + } +} diff --git a/src/Magento/FunctionalTestingFramework/Composer/ComposerPackage.php b/src/Magento/FunctionalTestingFramework/Composer/ComposerPackage.php new file mode 100644 index 000000000..e6bb46f90 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Composer/ComposerPackage.php @@ -0,0 +1,162 @@ +<?php +/** + * Copyright 2019 Adobe + * All Rights Reserved. + */ + +namespace Magento\FunctionalTestingFramework\Composer; + +/** + * Class ComposerPackage contains composer json information in a MFTF test package + */ +class ComposerPackage extends AbstractComposer +{ + /** + * @var \Composer\Package\CompletePackage + */ + private $rootPackage; + + /** + * Retrieve package name from composer json + * + * @return string + */ + public function getName() + { + /** @var \Composer\Package\CompletePackage $package */ + $package = $this->getRootPackage(); + return $package->getPrettyName(); + } + + /** + * Retrieve package type from composer json + * + * @return string + */ + public function getType() + { + /** @var \Composer\Package\CompletePackage $package */ + $package = $this->getRootPackage(); + return $package->getType(); + } + + /** + * Retrieve package version from composer json + * + * @return string + */ + public function getVersion() + { + /** @var \Composer\Package\CompletePackage $package */ + $package = $this->getRootPackage(); + return $package->getPrettyVersion(); + } + + /** + * Retrieve package description from composer json + * + * @return string + */ + public function getDescription() + { + /** @var \Composer\Package\CompletePackage $package */ + $package = $this->getRootPackage(); + return $package->getDescription(); + } + + /** + * Retrieve package require from composer json + * + * @return array + */ + public function getRequires() + { + /** @var \Composer\Package\CompletePackage $package */ + $package = $this->getRootPackage(); + return $package->getRequires(); + } + + /** + * Retrieve package dev require from composer json + * + * @return array + */ + public function getDevRequires() + { + /** @var \Composer\Package\CompletePackage $package */ + $package = $this->getRootPackage(); + return $package->getDevRequires(); + } + + /** + * Retrieve package suggest from composer json + * + * @return array + */ + public function getSuggests() + { + /** @var \Composer\Package\CompletePackage $package */ + $package = $this->getRootPackage(); + return $package->getSuggests(); + } + + /** + * Retrieve magento module names in package's suggest + * + * @return array + */ + public function getSuggestedMagentoModules() + { + return $this->parseSuggestsForMagentoModuleNames($this->getSuggests()); + } + + /** + * Determines if package is a mftf test package + * + * @return boolean + */ + public function isMftfTestPackage() + { + return $this->getType() === self::TEST_MODULE_PACKAGE_TYPE; + } + + /** + * Retrieve packages require for given package name and version + * + * @param string $name + * @param string $version + * @return array + */ + public function getRequiresForPackage($name, $version) + { + /** @var \Composer\Package\CompletePackage $package */ + $package = $this->getComposer()->getRepositoryManager()->findPackage($name, $version); + return $package->getRequires(); + } + + /** + * Check if a package is required in composer json + * + * @param string $packageName + * @return boolean + */ + public function isPackageRequiredInComposerJson($packageName) + { + return (in_array($packageName, array_keys($this->getRequires())) + || in_array($packageName, array_keys($this->getDevRequires())) + ); + } + + /** + * Get root package + * + * @return \Composer\Package\RootPackageInterface + */ + public function getRootPackage() + { + if (!$this->rootPackage) { + $this->rootPackage = $this->getComposer()->getPackage(); + } + return $this->rootPackage; + } +} diff --git a/src/Magento/FunctionalTestingFramework/Config/Converter.php b/src/Magento/FunctionalTestingFramework/Config/Converter.php index dcc67e5cf..97f493beb 100644 --- a/src/Magento/FunctionalTestingFramework/Config/Converter.php +++ b/src/Magento/FunctionalTestingFramework/Config/Converter.php @@ -1,8 +1,9 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ + namespace Magento\FunctionalTestingFramework\Config; use Magento\FunctionalTestingFramework\ObjectManager\Config\Mapper\ArgumentParser; @@ -83,6 +84,7 @@ public function convert($source) * @param \DOMNodeList|array $elements * @return array * @SuppressWarnings(PHPMD.CyclomaticComplexity) + * @TODO ported magento code - to be refactored later */ protected function convertXml($elements) { @@ -90,7 +92,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; } @@ -118,7 +120,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]; } } @@ -155,9 +157,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; } } @@ -173,7 +175,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 4350633e7..6d19105db 100644 --- a/src/Magento/FunctionalTestingFramework/Config/Converter/Dom/Flat.php +++ b/src/Magento/FunctionalTestingFramework/Config/Converter/Dom/Flat.php @@ -1,8 +1,9 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ + namespace Magento\FunctionalTestingFramework\Config\Converter\Dom; use Magento\FunctionalTestingFramework\Config\Dom\ArrayNodeConfig; @@ -41,7 +42,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; } } @@ -70,13 +71,14 @@ protected function getNodeAttributes(\DOMNode $node) * @return string|array * @throws \UnexpectedValueException * @SuppressWarnings(PHPMD.CyclomaticComplexity) + * Revisited to reduce cyclomatic complexity, left unrefactored for readability */ 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; @@ -106,8 +108,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/ConverterInterface.php b/src/Magento/FunctionalTestingFramework/Config/ConverterInterface.php index 3f3c0d87a..8350cc671 100644 --- a/src/Magento/FunctionalTestingFramework/Config/ConverterInterface.php +++ b/src/Magento/FunctionalTestingFramework/Config/ConverterInterface.php @@ -1,8 +1,9 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ + namespace Magento\FunctionalTestingFramework\Config; /** diff --git a/src/Magento/FunctionalTestingFramework/Config/Data.php b/src/Magento/FunctionalTestingFramework/Config/Data.php index a34ec1bf3..fdcf8d52e 100644 --- a/src/Magento/FunctionalTestingFramework/Config/Data.php +++ b/src/Magento/FunctionalTestingFramework/Config/Data.php @@ -1,7 +1,7 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ namespace Magento\FunctionalTestingFramework\Config; @@ -56,7 +56,7 @@ public function merge(array $config) * @param null|mixed $default * @return array|mixed|null */ - public function get($path = null, $default = null) + public function get(mixed $path = null, mixed $default = null) { if ($path === null) { return $this->data; @@ -94,7 +94,7 @@ public function setFileName($fileName) * @param string|null $scope * @return void */ - public function load($scope = null) + public function load(?string $scope = null) { $this->merge( $this->reader->read($scope) diff --git a/src/Magento/FunctionalTestingFramework/Config/DataInterface.php b/src/Magento/FunctionalTestingFramework/Config/DataInterface.php index 6af873001..6f8e94d7d 100644 --- a/src/Magento/FunctionalTestingFramework/Config/DataInterface.php +++ b/src/Magento/FunctionalTestingFramework/Config/DataInterface.php @@ -1,8 +1,9 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ + namespace Magento\FunctionalTestingFramework\Config; /** @@ -26,7 +27,7 @@ public function merge(array $config); * @param mixed|null $default * @return mixed|null */ - public function get($key = null, $default = null); + public function get(mixed $key = null, mixed $default = null); // @codingStandardsIgnoreEnd /** @@ -35,7 +36,7 @@ public function get($key = null, $default = null); * @param string|null $scope * @return void */ - public function load($scope = null); + public function load(?string $scope = null); /** * Set name of the config file diff --git a/src/Magento/FunctionalTestingFramework/Config/Dom.php b/src/Magento/FunctionalTestingFramework/Config/Dom.php index 566fbd5b3..276a1f7c1 100644 --- a/src/Magento/FunctionalTestingFramework/Config/Dom.php +++ b/src/Magento/FunctionalTestingFramework/Config/Dom.php @@ -1,7 +1,7 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ namespace Magento\FunctionalTestingFramework\Config; @@ -145,6 +145,7 @@ protected function mergeNode(\DOMElement $node, $parentPath) * @return void * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.NPathComplexity) + * @TODO Ported magento code - to be refactored later */ protected function mergeMatchingNode(\DomElement $node, $parentPath, $matchedNode, $path) { @@ -206,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; } /** @@ -245,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; } } @@ -272,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..ad3f125c6 100644 --- a/src/Magento/FunctionalTestingFramework/Config/Dom/ArrayNodeConfig.php +++ b/src/Magento/FunctionalTestingFramework/Config/Dom/ArrayNodeConfig.php @@ -1,8 +1,9 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ + namespace Magento\FunctionalTestingFramework\Config\Dom; /** @@ -64,7 +65,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 +85,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..f003e280e 100644 --- a/src/Magento/FunctionalTestingFramework/Config/Dom/NodeMergingConfig.php +++ b/src/Magento/FunctionalTestingFramework/Config/Dom/NodeMergingConfig.php @@ -1,8 +1,9 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ + namespace Magento\FunctionalTestingFramework\Config\Dom; /** @@ -44,7 +45,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..787d4fa42 100644 --- a/src/Magento/FunctionalTestingFramework/Config/Dom/NodePathMatcher.php +++ b/src/Magento/FunctionalTestingFramework/Config/Dom/NodePathMatcher.php @@ -1,8 +1,9 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ + namespace Magento\FunctionalTestingFramework\Config\Dom; /** @@ -17,7 +18,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/Dom/ValidationException.php b/src/Magento/FunctionalTestingFramework/Config/Dom/ValidationException.php index 18498c8b7..12b86cec0 100644 --- a/src/Magento/FunctionalTestingFramework/Config/Dom/ValidationException.php +++ b/src/Magento/FunctionalTestingFramework/Config/Dom/ValidationException.php @@ -1,7 +1,7 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ namespace Magento\FunctionalTestingFramework\Config\Dom; diff --git a/src/Magento/FunctionalTestingFramework/Config/FileResolver/Mask.php b/src/Magento/FunctionalTestingFramework/Config/FileResolver/Mask.php index e3f29f954..35102f48b 100644 --- a/src/Magento/FunctionalTestingFramework/Config/FileResolver/Mask.php +++ b/src/Magento/FunctionalTestingFramework/Config/FileResolver/Mask.php @@ -1,7 +1,7 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ namespace Magento\FunctionalTestingFramework\Config\FileResolver; @@ -28,7 +28,7 @@ class Mask implements FileResolverInterface * * @param ModuleResolver|null $moduleResolver */ - public function __construct(ModuleResolver $moduleResolver = null) + public function __construct(?ModuleResolver $moduleResolver = null) { if ($moduleResolver) { $this->moduleResolver = $moduleResolver; diff --git a/src/Magento/FunctionalTestingFramework/Config/FileResolver/Module.php b/src/Magento/FunctionalTestingFramework/Config/FileResolver/Module.php index 7cff19c87..cc6a57cd0 100644 --- a/src/Magento/FunctionalTestingFramework/Config/FileResolver/Module.php +++ b/src/Magento/FunctionalTestingFramework/Config/FileResolver/Module.php @@ -1,7 +1,7 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ namespace Magento\FunctionalTestingFramework\Config\FileResolver; @@ -27,7 +27,7 @@ class Module implements FileResolverInterface * @param ModuleResolver|null $moduleResolver * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ - public function __construct(ModuleResolver $moduleResolver = null) + public function __construct(?ModuleResolver $moduleResolver = null) { $this->moduleResolver = ModuleResolver::getInstance(); } diff --git a/src/Magento/FunctionalTestingFramework/Config/FileResolver/Primary.php b/src/Magento/FunctionalTestingFramework/Config/FileResolver/Primary.php index 2376b3cf1..6f0803720 100644 --- a/src/Magento/FunctionalTestingFramework/Config/FileResolver/Primary.php +++ b/src/Magento/FunctionalTestingFramework/Config/FileResolver/Primary.php @@ -1,13 +1,15 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ 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..d0c278c5c 100644 --- a/src/Magento/FunctionalTestingFramework/Config/FileResolver/Root.php +++ b/src/Magento/FunctionalTestingFramework/Config/FileResolver/Root.php @@ -1,15 +1,17 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ 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/FileResolverInterface.php b/src/Magento/FunctionalTestingFramework/Config/FileResolverInterface.php index f1baed853..fefe5228d 100644 --- a/src/Magento/FunctionalTestingFramework/Config/FileResolverInterface.php +++ b/src/Magento/FunctionalTestingFramework/Config/FileResolverInterface.php @@ -1,8 +1,9 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ + namespace Magento\FunctionalTestingFramework\Config; /** diff --git a/src/Magento/FunctionalTestingFramework/Config/MftfApplicationConfig.php b/src/Magento/FunctionalTestingFramework/Config/MftfApplicationConfig.php index eeabdf117..4efc72e83 100644 --- a/src/Magento/FunctionalTestingFramework/Config/MftfApplicationConfig.php +++ b/src/Magento/FunctionalTestingFramework/Config/MftfApplicationConfig.php @@ -1,19 +1,38 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2018 Adobe + * All Rights Reserved. */ + namespace Magento\FunctionalTestingFramework\Config; use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; +use Magento\FunctionalTestingFramework\Filter\FilterList; class MftfApplicationConfig { + /** + * MFTF Execution Phases + */ const GENERATION_PHASE = "generation"; const EXECUTION_PHASE = "execution"; const UNIT_TEST_PHASE = "testing"; const MFTF_PHASES = [self::GENERATION_PHASE, self::EXECUTION_PHASE, self::UNIT_TEST_PHASE]; + /** + * Mftf debug levels + */ + const LEVEL_DEFAULT = "default"; + const LEVEL_DEVELOPER = "developer"; + 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 * @@ -36,11 +55,17 @@ class MftfApplicationConfig private $verboseEnabled; /** - * Determines whether the user would like to execute mftf in a verbose run. + * String which identifies the current debug level of mftf execution * + * @var string + */ + private $debugLevel; + + /** + * Boolean which allows MFTF to fully generate skipped tests * @var boolean */ - private $debugEnabled; + private $allowSkipped; /** * MftfApplicationConfig Singelton Instance @@ -55,14 +80,18 @@ class MftfApplicationConfig * @param boolean $forceGenerate * @param string $phase * @param boolean $verboseEnabled - * @param boolean $debugEnabled + * @param string $debugLevel + * @param boolean $allowSkipped + * @param array $filters * @throws TestFrameworkException */ private function __construct( $forceGenerate = false, $phase = self::EXECUTION_PHASE, $verboseEnabled = null, - $debugEnabled = null + $debugLevel = self::LEVEL_DEFAULT, + $allowSkipped = false, + $filters = [] ) { $this->forceGenerate = $forceGenerate; @@ -72,7 +101,18 @@ private function __construct( $this->phase = $phase; $this->verboseEnabled = $verboseEnabled; - $this->debugEnabled = $debugEnabled; + 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: + $this->debugLevel = self::LEVEL_DEFAULT; + break; + default: + $this->debugLevel = self::LEVEL_DEVELOPER; + } + $this->allowSkipped = $allowSkipped; + $this->filterList = new FilterList($filters); } /** @@ -82,14 +122,30 @@ private function __construct( * @param boolean $forceGenerate * @param string $phase * @param boolean $verboseEnabled - * @param boolean $debugEnabled + * @param string $debugLevel + * @param boolean $allowSkipped + * @param array $filters * @return void + * @throws TestFrameworkException */ - public static function create($forceGenerate, $phase, $verboseEnabled, $debugEnabled) - { - if (self::$MFTF_APPLICATION_CONTEXT == null) { + public static function create( + $forceGenerate = false, + $phase = self::EXECUTION_PHASE, + $verboseEnabled = null, + $debugLevel = self::LEVEL_DEFAULT, + $allowSkipped = false, + $filters = [] + ) { + if (self::$MFTF_APPLICATION_CONTEXT === null) { self::$MFTF_APPLICATION_CONTEXT = - new MftfApplicationConfig($forceGenerate, $phase, $verboseEnabled, $debugEnabled); + new MftfApplicationConfig( + $forceGenerate, + $phase, + $verboseEnabled, + $debugLevel, + $allowSkipped, + $filters + ); } } @@ -97,13 +153,14 @@ public static function create($forceGenerate, $phase, $verboseEnabled, $debugEna * This function returns an instance of the MftfApplicationConfig which is created once the application starts. * * @return MftfApplicationConfig + * @throws TestFrameworkException */ 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(); } @@ -132,14 +189,23 @@ public function verboseEnabled() } /** - * Returns a boolean indicating whether the user has indicated a debug run, which will lengthy validation - * with some extra error messaging to be run + * Returns a string which indicates the debug level of mftf execution. + * + * @return string + */ + public function getDebugLevel() + { + return $this->debugLevel; + } + + /** + * Returns a boolean indicating whether mftf is generating skipped tests. * * @return boolean */ - public function debugEnabled() + public function allowSkipped() { - return $this->debugEnabled ?? getenv('MFTF_DEBUG'); + return $this->allowSkipped ?? getenv('ALLOW_SKIPPED'); } /** @@ -151,4 +217,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/MftfDom.php b/src/Magento/FunctionalTestingFramework/Config/MftfDom.php index 2a4c5f8b6..133c8074d 100644 --- a/src/Magento/FunctionalTestingFramework/Config/MftfDom.php +++ b/src/Magento/FunctionalTestingFramework/Config/MftfDom.php @@ -1,7 +1,7 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2018 Adobe + * All Rights Reserved. */ namespace Magento\FunctionalTestingFramework\Config; @@ -59,4 +59,18 @@ public function merge($xml, $filename = null, $exceptionCollector = null) $dom = $this->initDom($xml, $filename, $exceptionCollector); $this->mergeNode($dom->documentElement, ''); } + + /** + * Checks if the filename given ends with the correct suffix. + * @param string $filename + * @param string $suffix + * @return boolean + */ + public function checkFilenameSuffix($filename, $suffix) + { + if (substr_compare($filename, $suffix, -strlen($suffix)) === 0) { + return true; + } + return false; + } } diff --git a/src/Magento/FunctionalTestingFramework/Config/Reader.php b/src/Magento/FunctionalTestingFramework/Config/Reader.php index 0c996f14c..e32277891 100644 --- a/src/Magento/FunctionalTestingFramework/Config/Reader.php +++ b/src/Magento/FunctionalTestingFramework/Config/Reader.php @@ -1,8 +1,9 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ + namespace Magento\FunctionalTestingFramework\Config; /** @@ -48,9 +49,9 @@ public function __construct( $this->fileName = $fileName; $this->idAttributes = array_replace($this->idAttributes, $idAttributes); $this->schemaFile = $schemaLocator->getSchema(); - $this->isValidated = $validationState->isValidated(); - $this->perFileSchema = $schemaLocator->getPerFileSchema() && - $this->isValidated ? $schemaLocator->getPerFileSchema() : null; + $isValidated = $validationState->isValidated(); + $this->perFileSchema = $schemaLocator->getPerFileSchema() && $isValidated ? + $schemaLocator->getPerFileSchema() : null; $this->domDocumentClass = $domDocumentClass; $this->defaultScope = $defaultScope; } diff --git a/src/Magento/FunctionalTestingFramework/Config/Reader/Filesystem.php b/src/Magento/FunctionalTestingFramework/Config/Reader/Filesystem.php index 4c3aa581b..75a367199 100644 --- a/src/Magento/FunctionalTestingFramework/Config/Reader/Filesystem.php +++ b/src/Magento/FunctionalTestingFramework/Config/Reader/Filesystem.php @@ -1,11 +1,13 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ + namespace Magento\FunctionalTestingFramework\Config\Reader; use Magento\FunctionalTestingFramework\Config\MftfApplicationConfig; +use Magento\FunctionalTestingFramework\Exceptions\FastFailException; use Magento\FunctionalTestingFramework\Util\Logger\LoggingUtil; /** @@ -123,7 +125,7 @@ public function __construct( * @param string|null $scope * @return array */ - public function read($scope = null) + public function read(?string $scope = null) { $scope = $scope ?: $this->defaultScope; $fileList = $this->fileResolver->get($this->fileName, $scope); @@ -146,7 +148,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 +160,7 @@ protected function readFiles($fileList) } else { $configMerger->merge($content); } - if (MftfApplicationConfig::getConfig()->debugEnabled()) { + if (strcasecmp($debugLevel, MftfApplicationConfig::LEVEL_DEVELOPER) === 0) { $this->validateSchema($configMerger, $fileList->getFilename()); } } catch (\Magento\FunctionalTestingFramework\Config\Dom\ValidationException $e) { @@ -208,7 +211,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] ); @@ -226,13 +229,20 @@ protected function verifyFileEmpty($content, $fileName) * @throws \Exception * @return void */ - protected function validateSchema($configMerger, $filename = null) + protected function validateSchema($configMerger, ?string $filename = null) { if ($this->validationState->isValidationRequired()) { $errors = []; if ($configMerger && !$configMerger->validate($this->schemaFile, $errors)) { - $message = $filename ? $filename . PHP_EOL . "Invalid Document \n" : PHP_EOL . "Invalid Document \n"; - throw new \Exception($message . implode("\n", $errors)); + foreach ($errors as $error) { + $error = str_replace(PHP_EOL, "", $error); + LoggingUtil::getInstance()->getLogger(Filesystem::class)->criticalFailure( + "Schema validation error ", + ($filename ? [ "file"=> $filename, "error" => $error]: ["error" => $error]), + true + ); + } + 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 728c4cb3a..6b5cc08dc 100644 --- a/src/Magento/FunctionalTestingFramework/Config/Reader/MftfFilesystem.php +++ b/src/Magento/FunctionalTestingFramework/Config/Reader/MftfFilesystem.php @@ -1,7 +1,7 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2018 Adobe + * All Rights Reserved. */ namespace Magento\FunctionalTestingFramework\Config\Reader; @@ -24,6 +24,7 @@ public function readFiles($fileList) $exceptionCollector = new ExceptionCollector(); /** @var \Magento\FunctionalTestingFramework\Test\Config\Dom $configMerger */ $configMerger = null; + $debugLevel = MftfApplicationConfig::getConfig()->getDebugLevel(); foreach ($fileList as $key => $content) { //check if file is empty and continue to next if it is if (!parent::verifyFileEmpty($content, $fileList->getFilename())) { @@ -40,7 +41,8 @@ public function readFiles($fileList) } else { $configMerger->merge($content, $fileList->getFilename(), $exceptionCollector); } - if (MftfApplicationConfig::getConfig()->debugEnabled()) { + // run per file validation with generate:tests -d + if (strcasecmp($debugLevel, MftfApplicationConfig::LEVEL_DEVELOPER) === 0) { $this->validateSchema($configMerger, $fileList->getFilename()); } } catch (\Magento\FunctionalTestingFramework\Config\Dom\ValidationException $e) { @@ -48,8 +50,10 @@ public function readFiles($fileList) } } $exceptionCollector->throwException(); - if ($fileList->valid()) { - $this->validateSchema($configMerger, $fileList->getFilename()); + + //run validation on merged file with generate:tests + if (strcasecmp($debugLevel, MftfApplicationConfig::LEVEL_DEFAULT) === 0) { + $this->validateSchema($configMerger); } $output = []; diff --git a/src/Magento/FunctionalTestingFramework/Config/ReaderInterface.php b/src/Magento/FunctionalTestingFramework/Config/ReaderInterface.php index 9e64a6715..5b83e8959 100644 --- a/src/Magento/FunctionalTestingFramework/Config/ReaderInterface.php +++ b/src/Magento/FunctionalTestingFramework/Config/ReaderInterface.php @@ -1,9 +1,7 @@ <?php /** - * Reader responsible for retrieving provided scope of configuration from storage - * - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ namespace Magento\FunctionalTestingFramework\Config; @@ -19,5 +17,5 @@ interface ReaderInterface * @param string|null $scope * @return array */ - public function read($scope = null); + public function read(?string $scope = null); } diff --git a/src/Magento/FunctionalTestingFramework/Config/ReplacerInterface.php b/src/Magento/FunctionalTestingFramework/Config/ReplacerInterface.php index ce77a6c42..d08f589ad 100644 --- a/src/Magento/FunctionalTestingFramework/Config/ReplacerInterface.php +++ b/src/Magento/FunctionalTestingFramework/Config/ReplacerInterface.php @@ -1,7 +1,7 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ namespace Magento\FunctionalTestingFramework\Config; diff --git a/src/Magento/FunctionalTestingFramework/Config/SchemaLocator.php b/src/Magento/FunctionalTestingFramework/Config/SchemaLocator.php index a92f536a3..c53b9e7ca 100644 --- a/src/Magento/FunctionalTestingFramework/Config/SchemaLocator.php +++ b/src/Magento/FunctionalTestingFramework/Config/SchemaLocator.php @@ -1,11 +1,14 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ 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/SchemaLocatorInterface.php b/src/Magento/FunctionalTestingFramework/Config/SchemaLocatorInterface.php index 98ef8e73e..402e6b507 100644 --- a/src/Magento/FunctionalTestingFramework/Config/SchemaLocatorInterface.php +++ b/src/Magento/FunctionalTestingFramework/Config/SchemaLocatorInterface.php @@ -1,7 +1,7 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ namespace Magento\FunctionalTestingFramework\Config; diff --git a/src/Magento/FunctionalTestingFramework/Config/ValidationState.php b/src/Magento/FunctionalTestingFramework/Config/ValidationState.php index f08dc141a..75559e1f1 100644 --- a/src/Magento/FunctionalTestingFramework/Config/ValidationState.php +++ b/src/Magento/FunctionalTestingFramework/Config/ValidationState.php @@ -1,7 +1,7 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ namespace Magento\FunctionalTestingFramework\Config; @@ -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/Config/ValidationStateInterface.php b/src/Magento/FunctionalTestingFramework/Config/ValidationStateInterface.php index 2bcb2adf0..2f79dc8b8 100644 --- a/src/Magento/FunctionalTestingFramework/Config/ValidationStateInterface.php +++ b/src/Magento/FunctionalTestingFramework/Config/ValidationStateInterface.php @@ -1,8 +1,9 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ + namespace Magento\FunctionalTestingFramework\Config; /** diff --git a/src/Magento/FunctionalTestingFramework/Console/BaseGenerateCommand.php b/src/Magento/FunctionalTestingFramework/Console/BaseGenerateCommand.php index d4044c74b..0ed452363 100644 --- a/src/Magento/FunctionalTestingFramework/Console/BaseGenerateCommand.php +++ b/src/Magento/FunctionalTestingFramework/Console/BaseGenerateCommand.php @@ -1,21 +1,79 @@ <?php // @codingStandardsIgnoreFile /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2018 Adobe + * All Rights Reserved. */ + declare(strict_types = 1); 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. * @@ -28,6 +86,22 @@ protected function configure() 'r', InputOption::VALUE_NONE, 'remove previous generated suites and tests' + )->addOption( + "force", + 'f', + InputOption::VALUE_NONE, + 'force generation and running of tests regardless of Magento Instance Configuration' + )->addOption( + "allow-skipped", + 'a', + InputOption::VALUE_NONE, + 'Allows MFTF to generate and run skipped tests.' + )->addOption( + 'debug', + 'd', + InputOption::VALUE_OPTIONAL, + 'Run extra validation when generating and running tests.', + MftfApplicationConfig::LEVEL_DEFAULT ); } @@ -37,10 +111,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); @@ -49,4 +124,270 @@ 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 FastFailException + */ + protected function getTestAndSuiteConfiguration(array $tests) + { + $testConfiguration['tests'] = null; + $testConfiguration['suites'] = null; + $testsReferencedInSuites = SuiteObjectHandler::getInstance()->getAllTestReferences(); + $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) { + $suiteToTestPair[] = "$suite:$test"; + } + } + // configuration for tests + else { + $testConfiguration['tests'][] = $test; + } + } + // configuration for suites + foreach ($suiteToTestPair as $pair) { + list($suite, $test) = explode(":", $pair); + $testConfiguration['suites'][$suite][] = $test; + } + $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 9fa28dcc5..8f87d079b 100644 --- a/src/Magento/FunctionalTestingFramework/Console/BuildProjectCommand.php +++ b/src/Magento/FunctionalTestingFramework/Console/BuildProjectCommand.php @@ -1,12 +1,14 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2018 Adobe + * All Rights Reserved. */ + declare(strict_types = 1); namespace Magento\FunctionalTestingFramework\Console; +use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; use Magento\FunctionalTestingFramework\Util\Logger\LoggingUtil; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\ArrayInput; @@ -17,6 +19,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 @@ -26,8 +29,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. @@ -40,6 +43,7 @@ class BuildProjectCommand extends Command * Configures the current command. * * @return void + * @throws TestFrameworkException */ protected function configure() { @@ -49,9 +53,9 @@ protected function configure() "upgrade", 'u', InputOption::VALUE_NONE, - 'upgrade existing MFTF tests according to last major release requiements' + '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); @@ -63,12 +67,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([]); @@ -90,23 +93,29 @@ 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); - $process->run( + $codeceptReturnCode = $process->run( function ($type, $buffer) use ($output) { - if ($output->isVerbose()) { - $output->write($buffer); - } + $output->write($buffer); } ); + if ($codeceptReturnCode !== 0) { + throw new TestFrameworkException( + "The codecept build command failed unexpectedly. Please see the above output for more details." + ); + } + 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; } /** @@ -114,6 +123,7 @@ function ($type, $buffer) use ($output) { * * @param OutputInterface $output * @return void + * @throws TestFrameworkException */ private function generateConfigFiles(OutputInterface $output) { @@ -121,49 +131,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."); } - if ($output->isVerbose()) { - $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.'); - if ($output->isVerbose()) { - $output->writeln("functional.suite.yml applied to " . - TESTS_BP . DIRECTORY_SEPARATOR . 'tests' . DIRECTORY_SEPARATOR . 'functional.suite.yml'); - } + $output->writeln("functional.suite.yml applied to " . + 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..2c301e02f 100644 --- a/src/Magento/FunctionalTestingFramework/Console/CleanProjectCommand.php +++ b/src/Magento/FunctionalTestingFramework/Console/CleanProjectCommand.php @@ -1,12 +1,15 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2018 Adobe + * All Rights Reserved. */ + 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\InputInterface; use Symfony\Component\Console\Input\InputOption; @@ -16,20 +19,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 +40,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 +100,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..ecc313af9 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Console/Codecept/CodeceptCommandUtil.php @@ -0,0 +1,69 @@ +<?php +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ + +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..6dce75a63 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Console/CodeceptRunCommand.php @@ -0,0 +1,62 @@ +<?php +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ + +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():void + { + $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 362b29296..33b1188ac 100644 --- a/src/Magento/FunctionalTestingFramework/Console/CommandList.php +++ b/src/Magento/FunctionalTestingFramework/Console/CommandList.php @@ -1,7 +1,7 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2018 Adobe + * All Rights Reserved. */ declare(strict_types=1); @@ -11,6 +11,7 @@ /** * Class CommandList has a list of commands. * @codingStandardsIgnoreFile + * @SuppressWarnings(PHPMD) */ class CommandList implements CommandListInterface { @@ -28,15 +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(), - 'setup:env' => new SetupEnvCommand(), - 'upgrade:tests' => new UpgradeTestsCommand(), + '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/CommandListInterface.php b/src/Magento/FunctionalTestingFramework/Console/CommandListInterface.php index 050f7e3c0..2b4f0525c 100644 --- a/src/Magento/FunctionalTestingFramework/Console/CommandListInterface.php +++ b/src/Magento/FunctionalTestingFramework/Console/CommandListInterface.php @@ -1,8 +1,9 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2018 Adobe + * All Rights Reserved. */ + namespace Magento\FunctionalTestingFramework\Console; /** diff --git a/src/Magento/FunctionalTestingFramework/Console/DoctorCommand.php b/src/Magento/FunctionalTestingFramework/Console/DoctorCommand.php new file mode 100644 index 000000000..28e681a3f --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Console/DoctorCommand.php @@ -0,0 +1,210 @@ +<?php +/** + * Copyright 2019 Adobe + * All Rights Reserved. + */ + +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..49069d3f0 100644 --- a/src/Magento/FunctionalTestingFramework/Console/GenerateDevUrnCommand.php +++ b/src/Magento/FunctionalTestingFramework/Console/GenerateDevUrnCommand.php @@ -1,14 +1,16 @@ <?php // @codingStandardsIgnoreFile /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2018 Adobe + * All Rights Reserved. */ -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 +20,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 +37,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 +56,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 +85,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 +119,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/GenerateSuiteCommand.php b/src/Magento/FunctionalTestingFramework/Console/GenerateSuiteCommand.php index 815133e4d..9644fd754 100644 --- a/src/Magento/FunctionalTestingFramework/Console/GenerateSuiteCommand.php +++ b/src/Magento/FunctionalTestingFramework/Console/GenerateSuiteCommand.php @@ -1,13 +1,17 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2018 Adobe + * All Rights Reserved. */ + declare(strict_types = 1); 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; @@ -42,7 +46,20 @@ protected function configure() */ protected function execute(InputInterface $input, OutputInterface $output) { + $force = $input->getOption('force'); + $debug = $input->getOption('debug') ?? MftfApplicationConfig::LEVEL_DEVELOPER; // for backward compatibility $remove = $input->getOption('remove'); + $verbose = $output->isVerbose(); + $allowSkipped = $input->getOption('allow-skipped'); + + // Set application configuration so we can references the user options in our framework + MftfApplicationConfig::create( + $force, + MftfApplicationConfig::GENERATION_PHASE, + $verbose, + $debug, + $allowSkipped + ); // Remove previous GENERATED_DIR if --remove option is used if ($remove) { @@ -51,13 +68,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) { + } + } + + 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("Suites Generated"); + $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..578eb09c7 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Console/GenerateTestFailedCommand.php @@ -0,0 +1,187 @@ +<?php +/** + * Copyright 2021 Adobe + * All Rights Reserved. + */ + +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 fcd977060..f27191e1d 100644 --- a/src/Magento/FunctionalTestingFramework/Console/GenerateTestsCommand.php +++ b/src/Magento/FunctionalTestingFramework/Console/GenerateTestsCommand.php @@ -1,26 +1,71 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2018 Adobe + * All Rights Reserved. */ + declare(strict_types = 1); 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_STANDALONE = 'dev/tests/_output/test-dependencies.json'; + const TEST_DEPENDENCY_FILE_LOCATION_EMBEDDED = 'dev/tests/acceptance/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. * @@ -29,33 +74,57 @@ class GenerateTestsCommand extends BaseGenerateCommand protected function configure() { $this->setName('generate:tests') - ->setDescription('This command generates all test files and suites based on xml declarations') + ->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( - "force", - 'f', - InputOption::VALUE_NONE, - 'force generation of tests regardless of Magento Instance Configuration' + )->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( - 'debug', - 'd', - InputOption::VALUE_NONE, - 'run extra validation when generating tests' + '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(); @@ -66,80 +135,162 @@ 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'); + $json = $input->getOption('tests'); // for backward compatibility $force = $input->getOption('force'); - $time = $input->getOption('time') * 60 * 1000; // convert from minutes to milliseconds - $debug = $input->getOption('debug'); + $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 + 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); + } if ($json !== null && !json_decode($json)) { // 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()); } - 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); + $this->removeGeneratedDirectory($output, $verbose); } - $testConfiguration = $this->createTestConfiguration($json, $tests, $force, $debug, $verbose); + 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); + } - if (empty($tests)) { SuiteGenerator::getInstance()->generateAllSuites($testManifest); + + $testManifest->generate(); + + SuiteGenerator::getInstance()->generateTestgroupmembership($testManifest); + } 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); + + return 1; } - $testManifest->generate(); + // check test dependencies log command + if (!empty($log)) { + if ($log === "testEntityJson") { + $this->getTestEntityJson($filterList ??[], $tests); + $testDependencyFileLocation = self::TEST_DEPENDENCY_FILE_LOCATION_EMBEDDED; + if (isset($_ENV['MAGENTO_BP'])) { + $testDependencyFileLocation = self::TEST_DEPENDENCY_FILE_LOCATION_STANDALONE; + } + $output->writeln( + "Test dependencies file created, Located in: " . $testDependencyFileLocation + ); + } 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; + } } /** * Function which builds up a configuration including test and suites for consumption of Magento generation methods. * - * @param string $json - * @param array $tests - * @param boolean $force - * @param boolean $debug - * @param boolean $verbose + * @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, array $tests, bool $force, bool $debug, bool $verbose) - { - // set our application configuration so we can references the user options in our framework - MftfApplicationConfig::create( - $force, - MftfApplicationConfig::GENERATION_PHASE, - $verbose, - $debug - ); - + private function createTestConfiguration( + $json, + array $tests + ) { $testConfiguration = []; $testConfiguration['tests'] = $tests; $testConfiguration['suites'] = []; @@ -151,7 +302,20 @@ private function createTestConfiguration($json, array $tests, bool $force, bool $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; @@ -182,4 +346,277 @@ 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 $filterList, array $tests = []) + { + $testDependencies = $this->getTestDependencies($filterList, $tests); + $this->array2Json($testDependencies); + } + + /** + * Function responsible for getting test dependencies in array + * @param array $filterList + * @param array $tests + * @return array + * @throws FastFailException + * @throws TestFrameworkException + * @throws XmlException + */ + public function getTestDependencies(array $filterList, 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, + $filterList, + $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) + { + $testDependencyFileLocation = self::TEST_DEPENDENCY_FILE_LOCATION_EMBEDDED; + if (isset($_ENV['MAGENTO_BP'])) { + $testDependencyFileLocation = self::TEST_DEPENDENCY_FILE_LOCATION_STANDALONE; + } + $testDependencyFileLocationDir = dirname($testDependencyFileLocation); + if (!is_dir($testDependencyFileLocationDir)) { + mkdir($testDependencyFileLocationDir, 0777, true); + } + $file = fopen($testDependencyFileLocation, '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 new file mode 100644 index 000000000..ac49af2a8 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Console/RunManifestCommand.php @@ -0,0 +1,189 @@ +<?php +/** + * Copyright 2019 Adobe + * All Rights Reserved. + */ + +declare(strict_types = 1); + +namespace Magento\FunctionalTestingFramework\Console; + +use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; +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 +{ + /** + * The return code. Determined by all tests that run. + * + * @var integer + */ + private $returnCode = 0; + + /** + * A list of tests that failed. + * Eg: "tests/functional/tests/MFTF/_generated/default/AdminLoginTestCest.php:AdminLoginTest" + * + * @var string[] + */ + private $failedTests = []; + + /** + * Path for a failed test + * + * @var string + */ + private $testsFailedFile; + + /** + * Configure the run:manifest command. + * + * @return void + */ + protected function configure() + { + $this->setName("run:manifest") + ->setDescription("runs a manifest file") + ->addArgument("path", InputArgument::REQUIRED, "path to a manifest file"); + } + + /** + * Executes the run:manifest command. + * + * @param InputInterface $input + * @param OutputInterface $output + * @throws TestFrameworkException + * @return integer + */ + 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)) { + throw new TestFrameworkException("Could not find file $path. Check the path and try again."); + } + + $manifestFile = file($path, FILE_IGNORE_NEW_LINES); + + // Delete the Codeception failed file just in case it exists from any previous test runs + $this->deleteFailedFile(); + + for ($line = 0; $line < count($manifestFile); $line++) { + if (empty($manifestFile[$line])) { + continue; + } + + if ($line === count($manifestFile) - 1) { + $this->runManifestLine($manifestFile[$line], $output, true); + } else { + $this->runManifestLine($manifestFile[$line], $output); + } + + $this->aggregateFailed(); + } + + if (!empty($this->failedTests)) { + $this->deleteFailedFile(); + $this->writeFailedFile(); + } + + return $this->returnCode; + } + + /** + * Runs a test (or group) line from the manifest file + * + * @param string $manifestLine + * @param OutputInterface $output + * @param boolean $exit + * @return void + * @throws \Exception + * + * @SuppressWarnings(PHPMD.UnusedFormalParameter) Need this because of the unused $type variable in the closure + */ + private function runManifestLine($manifestLine, $output, $exit = false) + { + 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); + } + + /** + * Keeps track of any tests that failed while running the manifest file. + * + * Each codecept command executions overwrites the failed file. Since we are running multiple codecept commands, + * we need to hold on to any failures in order to write a final failed file containing all tests. + * + * @return void + */ + private function aggregateFailed() + { + if (file_exists($this->testsFailedFile)) { + $currentFile = file($this->testsFailedFile, FILE_IGNORE_NEW_LINES); + $this->failedTests = array_merge( + $this->failedTests, + $currentFile + ); + } + } + + /** + * Delete the Codeception failed file. + * + * @return void + */ + private function deleteFailedFile() + { + if (file_exists($this->testsFailedFile)) { + unlink($this->testsFailedFile); + } + } + + /** + * Writes any tests that failed to the Codeception failed file. + * + * @return void + */ + private function writeFailedFile() + { + foreach ($this->failedTests as $test) { + 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 097d7dfd8..029a8d0e5 100644 --- a/src/Magento/FunctionalTestingFramework/Console/RunTestCommand.php +++ b/src/Magento/FunctionalTestingFramework/Console/RunTestCommand.php @@ -1,12 +1,18 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2018 Adobe + * All Rights Reserved. */ + declare(strict_types = 1); 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; use Symfony\Component\Console\Input\InputInterface; @@ -15,8 +21,18 @@ use Symfony\Component\Process\Process; use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; +/** + * @SuppressWarnings(PHPMD) + */ class RunTestCommand extends BaseGenerateCommand { + /** + * The return code. Determined by all tests that run. + * + * @var integer + */ + private $returnCode = 0; + /** * Configures the current command. * @@ -26,18 +42,26 @@ protected function configure() { $this->setName("run:test") ->setDescription("generation and execution of test(s) defined in xml") - ->addArgument( + ->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( - "force", - 'f', + )->addOption( + 'skip-generate', + 'k', InputOption::VALUE_NONE, - 'force generation of tests regardless of Magento Instance Configuration' + "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(); } @@ -46,17 +70,19 @@ protected function configure() * * @param InputInterface $input * @param OutputInterface $output - * @return integer|null|void + * @return integer * @throws \Exception - * - * @SuppressWarnings(PHPMD.UnusedLocalVariable) */ - protected function execute(InputInterface $input, OutputInterface $output) + 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'); + $debug = $input->getOption('debug') ?? MftfApplicationConfig::LEVEL_DEVELOPER; // for backward compatibility + $allowSkipped = $input->getOption('allow-skipped'); + $verbose = $output->isVerbose(); if ($skipGeneration and $remove) { // "skip-generate" and "remove" options cannot be used at the same time @@ -65,30 +91,197 @@ protected function execute(InputInterface $input, OutputInterface $output) ); } + // Set application configuration so we can references the user options in our framework + MftfApplicationConfig::create( + $force, + MftfApplicationConfig::EXECUTION_PHASE, + $verbose, + $debug, + $allowSkipped + ); + + 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'); $args = [ - '--tests' => json_encode([ - 'tests' => $tests, - 'suites' => null - ]), + '--tests' => $testConfiguration, '--force' => $force, - '--remove' => $remove + '--remove' => $remove, + '--debug' => $debug, + '--allow-skipped' => $allowSkipped, + '-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, $input); + } + + if (isset($testConfigArray['suites'])) { + $this->runTestsInSuite($testConfigArray['suites'], $output, $input); + } + + // Add all failed tests in 'failed' file + $this->applyAllFailed(); + + return max($this->returnCode, $generationErrorCode); + } + + /** + * Run tests not referenced in suites + * + * @param array $tests + * @param OutputInterface $output + * @param InputInterface $input + * @return void + * @throws TestFrameworkException + * @throws \Exception + */ + private function runTests(array $tests, OutputInterface $output, InputInterface $input) + { + $xml = ($input->getOption('xml')) ? '--xml' : ""; + $noAnsi = ($input->getOption('no-ansi')) ? '--no-ansi' : ""; + 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 ; + + 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 + ); + } + + 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, $noAnsi)); + } + if (!empty($xml)) { + $this->movingXMLFileFromSourceToDestination($xml, $testName, $output); + } + // Save failed tests + $this->appendRunFailed(); + } + } + + /** + * Run tests referenced in suites within suites' context. + * + * @param array $suitesConfig + * @param OutputInterface $output + * @param InputInterface $input + * @return void + * @throws \Exception + */ + private function runTestsInSuite(array $suitesConfig, OutputInterface $output, InputInterface $input) + { + $xml = ($input->getOption('xml')) ? '--xml' : ""; + $noAnsi = ($input->getOption('no-ansi')) ? '--no-ansi' : ""; + 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; } - // we only generate relevant tests here so we can execute "all tests" - $codeceptionCommand = realpath(PROJECT_ROOT . '/vendor/bin/codecept') . " run functional --verbose --steps"; + $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}"; - $process = new Process($codeceptionCommand); + $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, $noAnsi)); + } + if (!empty($xml)) { + $this->movingXMLFileFromSourceToDestination($xml, $suite, $output); + } + // Save failed tests + $this->appendRunFailed(); + } + } + + /** + * Runs the codeception test command and returns exit code + * + * @param string $command + * @param OutputInterface $output + * @return integer + * + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + private function executeTestCommand(string $command, OutputInterface $output, $noAnsi) + { + $process = Process::fromShellCommandline($command); $process->setWorkingDirectory(TESTS_BP); $process->setIdleTimeout(600); $process->setTimeout(0); - $process->run( - function ($type, $buffer) use ($output) { - $output->write($buffer); - } - ); + + return $process->run(function ($type, $buffer) use ($output, $noAnsi) { + $buffer = $this->disableAnsiColorCodes($buffer, $noAnsi); + $output->write($buffer); + }); + } + + /** + * @param string $buffer + * @param string $noAnsi + * @return string + */ + private function disableAnsiColorCodes($buffer, $noAnsi) :string + { + if (empty($noAnsi)) { + return $buffer; + } + $pattern = "/\x1B\[([0-9]{1,2}(;[0-9]{1,2})*)?[m|K]/"; + // Use preg_replace to remove ANSI escape codes from the string + return preg_replace($pattern, '', $buffer); } } diff --git a/src/Magento/FunctionalTestingFramework/Console/RunTestFailedCommand.php b/src/Magento/FunctionalTestingFramework/Console/RunTestFailedCommand.php new file mode 100644 index 000000000..774c6ba9c --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Console/RunTestFailedCommand.php @@ -0,0 +1,169 @@ +<?php +/** + * Copyright 2018 Adobe + * All Rights Reserved. + */ + +declare(strict_types = 1); + +namespace Magento\FunctionalTestingFramework\Console; + +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Process\Process; + +class RunTestFailedCommand extends BaseGenerateCommand +{ + const DEFAULT_TEST_GROUP = 'default'; + + /** + * @var string + */ + private $testsReRunFile = "rerun_tests"; + + /** + * @var array + */ + private $failedList = []; + + /** + * Configures the current command. + * + * @return void + */ + protected function configure() + { + $this->setName('run:failed') + ->setDescription('Execute a set of tests referenced via failed file'); + + 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 + { + $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; + } + $returnCode = 0; + 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; + } + $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($this->testsFailedFile) + ); + } + } + + foreach ($this->failedList as $test) { + $this->writeFailedTestToFile($test, $this->testsFailedFile); + } + + return $returnCode; + } + + /** + * Returns a list of tests/suites which should have an additional run. + * + * @param array $failedTests + * @return array + */ + private function filterTestsForExecution(array $failedTests): array + { + $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; + } + } + } + } + + return $testsOrGroupsToRerun; + } + + /** + * Returns an array of tests read from the failed test file in _output + * + * @param string $filePath + * @return array + */ + private function readFailedTestFile(string $filePath): array + { + $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) + { + 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/RunTestGroupCommand.php b/src/Magento/FunctionalTestingFramework/Console/RunTestGroupCommand.php index 3098569fb..9bacc5c19 100644 --- a/src/Magento/FunctionalTestingFramework/Console/RunTestGroupCommand.php +++ b/src/Magento/FunctionalTestingFramework/Console/RunTestGroupCommand.php @@ -1,22 +1,22 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2018 Adobe + * All Rights Reserved. */ + declare(strict_types = 1); 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,17 +28,20 @@ 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', InputOption::VALUE_NONE, "only execute a group of tests without generating from source xml" - )->addOption( - "force", - 'f', - InputOption::VALUE_NONE, - 'force generation of tests regardless of Magento Instance Configuration' )->addArgument( 'groups', InputArgument::IS_ARRAY | InputArgument::REQUIRED, @@ -53,17 +56,25 @@ protected function configure() * * @param InputInterface $input * @param OutputInterface $output - * @return integer|null|void + * @return integer * @throws \Exception * - * @SuppressWarnings(PHPMD.UnusedLocalVariable) + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * @SuppressWarnings(PHPMD.CyclomaticComplexity) + * @SuppressWarnings(PHPMD.NPathComplexity) */ - protected function execute(InputInterface $input, OutputInterface $output) + 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'); $remove = $input->getOption('remove'); + $debug = $input->getOption('debug') ?? MftfApplicationConfig::LEVEL_DEVELOPER; // for backward compatibility + $allowSkipped = $input->getOption('allow-skipped'); + $verbose = $output->isVerbose(); if ($skipGeneration and $remove) { // "skip-generate" and "remove" options cannot be used at the same time @@ -75,65 +86,78 @@ protected function execute(InputInterface $input, OutputInterface $output) // Create Mftf Configuration MftfApplicationConfig::create( $force, - MftfApplicationConfig::GENERATION_PHASE, - false, - false + MftfApplicationConfig::EXECUTION_PHASE, + $verbose, + $debug, + $allowSkipped ); + $generationErrorCode = 0; + if (!$skipGeneration) { $testConfiguration = $this->getGroupAndSuiteConfiguration($groups); $command = $this->getApplication()->find('generate:tests'); $args = [ '--tests' => $testConfiguration, '--force' => $force, - '--remove' => $remove + '--remove' => $remove, + '--debug' => $debug, + '--allow-skipped' => $allowSkipped, + '-v' => $verbose ]; $command->run(new ArrayInput($args), $output); - } - $codeceptionCommand = realpath(PROJECT_ROOT . '/vendor/bin/codecept') . ' run functional --verbose --steps'; + if (!empty(GenerationErrorHandler::getInstance()->getAllErrors())) { + $generationErrorCode = 1; + } + } - foreach ($groups as $group) { - $codeceptionCommand .= " -g {$group}"; + 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; } - $process = new Process($codeceptionCommand); - $process->setWorkingDirectory(TESTS_BP); - $process->setIdleTimeout(600); - $process->setTimeout(0); - $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..93ca21d4b 100644 --- a/src/Magento/FunctionalTestingFramework/Console/SetupEnvCommand.php +++ b/src/Magento/FunctionalTestingFramework/Console/SetupEnvCommand.php @@ -1,12 +1,15 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2018 Adobe + * All Rights Reserved. */ + 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\InputOption; use Symfony\Component\Console\Exception\InvalidOptionException; @@ -16,6 +19,8 @@ class SetupEnvCommand extends Command { + private const SUCCESS_EXIT_CODE = 0; + /** * Env processor manages .env files. * @@ -27,12 +32,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 +50,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 +65,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 new file mode 100644 index 000000000..00baa2cfa --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Console/StaticChecksCommand.php @@ -0,0 +1,213 @@ +<?php +// @codingStandardsIgnoreFile +/** + * Copyright 2019 Adobe + * All Rights Reserved. + */ + +declare(strict_types = 1); + +namespace Magento\FunctionalTestingFramework\Console; + +use Magento\FunctionalTestingFramework\StaticCheck\StaticCheckInterface; +use Magento\FunctionalTestingFramework\StaticCheck\StaticChecksList; +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 +{ + /** + * Associative array containing static ruleset properties. + * + * @var array + */ + 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. + * + * @return void + */ + 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($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 + * @return int + * @throws Exception + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + $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 + ); + + $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); + $this->ioStyle->text($staticOutput??""); + + $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..e4c34fb3a 100644 --- a/src/Magento/FunctionalTestingFramework/Console/UpgradeTestsCommand.php +++ b/src/Magento/FunctionalTestingFramework/Console/UpgradeTestsCommand.php @@ -1,9 +1,10 @@ <?php // @codingStandardsIgnoreFile /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2018 Adobe + * All Rights Reserved. */ + declare(strict_types = 1); namespace Magento\FunctionalTestingFramework\Console; @@ -17,6 +18,8 @@ class UpgradeTestsCommand extends Command { + private const SUCCESS_EXIT_CODE = 0; + /** * Pool of upgrade scripts to run * @@ -32,8 +35,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 +49,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/Argument.php b/src/Magento/FunctionalTestingFramework/Data/Argument/Interpreter/Argument.php index 04328daf5..feadc65fe 100644 --- a/src/Magento/FunctionalTestingFramework/Data/Argument/Interpreter/Argument.php +++ b/src/Magento/FunctionalTestingFramework/Data/Argument/Interpreter/Argument.php @@ -1,8 +1,9 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ + namespace Magento\FunctionalTestingFramework\Data\Argument\Interpreter; use Magento\FunctionalTestingFramework\Data\Argument\InterpreterInterface; diff --git a/src/Magento/FunctionalTestingFramework/Data/Argument/Interpreter/ArrayType.php b/src/Magento/FunctionalTestingFramework/Data/Argument/Interpreter/ArrayType.php index 566c09912..59872624e 100644 --- a/src/Magento/FunctionalTestingFramework/Data/Argument/Interpreter/ArrayType.php +++ b/src/Magento/FunctionalTestingFramework/Data/Argument/Interpreter/ArrayType.php @@ -1,8 +1,9 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ + namespace Magento\FunctionalTestingFramework\Data\Argument\Interpreter; use Magento\FunctionalTestingFramework\Data\Argument\InterpreterInterface; @@ -98,7 +99,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/Data/Argument/Interpreter/Boolean.php b/src/Magento/FunctionalTestingFramework/Data/Argument/Interpreter/Boolean.php index 8e4e5a899..19c5ee058 100644 --- a/src/Magento/FunctionalTestingFramework/Data/Argument/Interpreter/Boolean.php +++ b/src/Magento/FunctionalTestingFramework/Data/Argument/Interpreter/Boolean.php @@ -1,8 +1,9 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ + namespace Magento\FunctionalTestingFramework\Data\Argument\Interpreter; use Magento\FunctionalTestingFramework\Data\Argument\InterpreterInterface; diff --git a/src/Magento/FunctionalTestingFramework/Data/Argument/Interpreter/Composite.php b/src/Magento/FunctionalTestingFramework/Data/Argument/Interpreter/Composite.php index 8c5981a78..229593937 100644 --- a/src/Magento/FunctionalTestingFramework/Data/Argument/Interpreter/Composite.php +++ b/src/Magento/FunctionalTestingFramework/Data/Argument/Interpreter/Composite.php @@ -1,8 +1,9 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ + namespace Magento\FunctionalTestingFramework\Data\Argument\Interpreter; use Magento\FunctionalTestingFramework\Data\Argument\InterpreterInterface; diff --git a/src/Magento/FunctionalTestingFramework/Data/Argument/Interpreter/Constant.php b/src/Magento/FunctionalTestingFramework/Data/Argument/Interpreter/Constant.php index 05734375b..4f5f2b0ea 100644 --- a/src/Magento/FunctionalTestingFramework/Data/Argument/Interpreter/Constant.php +++ b/src/Magento/FunctionalTestingFramework/Data/Argument/Interpreter/Constant.php @@ -1,8 +1,9 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ + namespace Magento\FunctionalTestingFramework\Data\Argument\Interpreter; use Magento\FunctionalTestingFramework\Data\Argument\InterpreterInterface; diff --git a/src/Magento/FunctionalTestingFramework/Data/Argument/Interpreter/DataObject.php b/src/Magento/FunctionalTestingFramework/Data/Argument/Interpreter/DataObject.php index 595d7df04..f13fbd9b3 100644 --- a/src/Magento/FunctionalTestingFramework/Data/Argument/Interpreter/DataObject.php +++ b/src/Magento/FunctionalTestingFramework/Data/Argument/Interpreter/DataObject.php @@ -1,9 +1,9 @@ <?php /** - * - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ + namespace Magento\FunctionalTestingFramework\Data\Argument\Interpreter; use Magento\FunctionalTestingFramework\Data\Argument\InterpreterInterface; diff --git a/src/Magento/FunctionalTestingFramework/Data/Argument/Interpreter/NullType.php b/src/Magento/FunctionalTestingFramework/Data/Argument/Interpreter/NullType.php index f1985cf3f..e4e60d7f3 100644 --- a/src/Magento/FunctionalTestingFramework/Data/Argument/Interpreter/NullType.php +++ b/src/Magento/FunctionalTestingFramework/Data/Argument/Interpreter/NullType.php @@ -1,8 +1,9 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ + namespace Magento\FunctionalTestingFramework\Data\Argument\Interpreter; use Magento\FunctionalTestingFramework\Data\Argument\InterpreterInterface; diff --git a/src/Magento/FunctionalTestingFramework/Data/Argument/Interpreter/Number.php b/src/Magento/FunctionalTestingFramework/Data/Argument/Interpreter/Number.php index 3742a67ad..f87621a00 100644 --- a/src/Magento/FunctionalTestingFramework/Data/Argument/Interpreter/Number.php +++ b/src/Magento/FunctionalTestingFramework/Data/Argument/Interpreter/Number.php @@ -1,8 +1,9 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ + namespace Magento\FunctionalTestingFramework\Data\Argument\Interpreter; use Magento\FunctionalTestingFramework\Data\Argument\InterpreterInterface; diff --git a/src/Magento/FunctionalTestingFramework/Data/Argument/Interpreter/StringUtils.php b/src/Magento/FunctionalTestingFramework/Data/Argument/Interpreter/StringUtils.php index ba979e21c..feadc916e 100644 --- a/src/Magento/FunctionalTestingFramework/Data/Argument/Interpreter/StringUtils.php +++ b/src/Magento/FunctionalTestingFramework/Data/Argument/Interpreter/StringUtils.php @@ -1,8 +1,9 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ + namespace Magento\FunctionalTestingFramework\Data\Argument\Interpreter; use Magento\FunctionalTestingFramework\Data\Argument\InterpreterInterface; diff --git a/src/Magento/FunctionalTestingFramework/Data/Argument/InterpreterInterface.php b/src/Magento/FunctionalTestingFramework/Data/Argument/InterpreterInterface.php index 6aa835e3e..81a3b2618 100644 --- a/src/Magento/FunctionalTestingFramework/Data/Argument/InterpreterInterface.php +++ b/src/Magento/FunctionalTestingFramework/Data/Argument/InterpreterInterface.php @@ -1,8 +1,9 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ + namespace Magento\FunctionalTestingFramework\Data\Argument; /** diff --git a/src/Magento/FunctionalTestingFramework/Data/Argument/InterpreterInterface/Proxy.php b/src/Magento/FunctionalTestingFramework/Data/Argument/InterpreterInterface/Proxy.php index 543682b4f..ea1ecafa6 100644 --- a/src/Magento/FunctionalTestingFramework/Data/Argument/InterpreterInterface/Proxy.php +++ b/src/Magento/FunctionalTestingFramework/Data/Argument/InterpreterInterface/Proxy.php @@ -1,8 +1,9 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ + namespace Magento\FunctionalTestingFramework\Data\Argument\InterpreterInterface; /** diff --git a/src/Magento/FunctionalTestingFramework/Data/etc/types.xsd b/src/Magento/FunctionalTestingFramework/Data/etc/types.xsd index b7fbc31ee..d27918af1 100644 --- a/src/Magento/FunctionalTestingFramework/Data/etc/types.xsd +++ b/src/Magento/FunctionalTestingFramework/Data/etc/types.xsd @@ -1,8 +1,8 @@ <?xml version="1.0" encoding="UTF-8"?> <!-- /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ --> <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> diff --git a/src/Magento/FunctionalTestingFramework/DataGenerator/Config/Dom.php b/src/Magento/FunctionalTestingFramework/DataGenerator/Config/Dom.php index d4b8f7c8f..af00b3674 100644 --- a/src/Magento/FunctionalTestingFramework/DataGenerator/Config/Dom.php +++ b/src/Magento/FunctionalTestingFramework/DataGenerator/Config/Dom.php @@ -1,8 +1,9 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2018 Adobe + * All Rights Reserved. */ + namespace Magento\FunctionalTestingFramework\DataGenerator\Config; use Magento\FunctionalTestingFramework\Config\Dom\NodeMergingConfig; @@ -81,6 +82,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/OperationDom.php b/src/Magento/FunctionalTestingFramework/DataGenerator/Config/OperationDom.php index e76289919..efe13992e 100644 --- a/src/Magento/FunctionalTestingFramework/DataGenerator/Config/OperationDom.php +++ b/src/Magento/FunctionalTestingFramework/DataGenerator/Config/OperationDom.php @@ -1,8 +1,9 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2018 Adobe + * All Rights Reserved. */ + namespace Magento\FunctionalTestingFramework\DataGenerator\Config; use Magento\FunctionalTestingFramework\Config\Dom\NodeMergingConfig; 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 a28547d18..5a2061ff9 100644 --- a/src/Magento/FunctionalTestingFramework/DataGenerator/Handlers/CredentialStore.php +++ b/src/Magento/FunctionalTestingFramework/DataGenerator/Handlers/CredentialStore.php @@ -1,47 +1,55 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2018 Adobe + * All Rights Reserved. */ namespace Magento\FunctionalTestingFramework\DataGenerator\Handlers; -use Magento\FunctionalTestingFramework\Config\MftfApplicationConfig; -use Magento\FunctionalTestingFramework\Console\BuildProjectCommand; +use Magento\FunctionalTestingFramework\Exceptions\Collector\ExceptionCollector; use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; -use Magento\FunctionalTestingFramework\Util\Logger\LoggingUtil; +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 ENCRYPTION_ALGO = "AES-256-CBC"; + 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'; /** - * Singleton instance + * Credential storage array * - * @var CredentialStore + * @var BaseStorage[] */ - private static $INSTANCE = null; + private $credStorage = []; /** - * Initial vector for open_ssl encryption. + * Boolean to indicate if credential storage have been initialized * - * @var string + * @var boolean */ - private $iv = null; + private $initialized; /** - * Key for open_ssl encryption/decryption + * Singleton instance * - * @var string + * @var CredentialStore */ - private $encodedKey = null; + private static $INSTANCE = null; /** - * Key/Value paris of credential names and their corresponding values + * Exception contexts * - * @var array + * @var ExceptionCollector */ - private $credentials = []; + private $exceptionContexts; /** * Static singleton getter for CredentialStore Instance @@ -50,7 +58,7 @@ class CredentialStore */ public static function getInstance() { - if (self::$INSTANCE == null) { + if (self::$INSTANCE === null) { self::$INSTANCE = new CredentialStore(); } @@ -58,18 +66,16 @@ public static function getInstance() } /** - * CredentialStore constructor. + * CredentialStore constructor */ private function __construct() { - $this->encodedKey = base64_encode(openssl_random_pseudo_bytes(16)); - $this->iv = substr(hash('sha256', $this->encodedKey), 0, 16); - $creds = $this->readInCredentialsFile(); - $this->credentials = $this->encryptCredFileContents($creds); + $this->initialized = false; + $this->exceptionContexts = new ExceptionCollector(); } /** - * Returns the value of a secret based on corresponding key + * Get encrypted value by key * * @param string $key * @return string|null @@ -77,83 +83,214 @@ private function __construct() */ public function getSecret($key) { - if (!array_key_exists($key, $this->credentials)) { - throw new TestFrameworkException( - "{$key} not defined in .credentials, please provide a value in order to use this secret in a test." - ); + // 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) { + return $value; + } } - // log here for verbose config - if (MftfApplicationConfig::getConfig()->verboseEnabled()) { - LoggingUtil::getInstance()->getLogger(CredentialStore::class)->debug( - "retrieving secret for key name {$key}" - ); + $exceptionContexts = $this->getExceptionContexts(); + $this->resetExceptionContext(); + throw new TestFrameworkException( + "{$key} not found. " . self::CREDENTIAL_STORAGE_INFO + . " and ensure key, value exists to use _CREDS in tests." + . $exceptionContexts + ); + } + + /** + * Return decrypted input value + * + * @param string $value + * @return string|false The decrypted string on success or false on failure + * @throws TestFrameworkException + */ + public function decryptSecretValue($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 string|false The decrypted string on success or false on failure + * @throws TestFrameworkException + */ + public function decryptAllSecretsInString($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"; + } - return $this->credentials[$key] ?? null; + if (is_array($exceptions)) { + $exceptionMessage .= implode("\n", $exceptions) . "\n"; + } else { + $exceptionMessage .= $exceptions . "\n"; + } + } + return $exceptionMessage; } /** - * Private function which reads in secret key/values from .credentials file and stores in memory as key/value pair. + * Reset exception contexts to empty array * - * @return array + * @return void + */ + private function resetExceptionContext() + { + $this->exceptionContexts->reset(); + } + + /** + * Initialize all available credential storage + * + * @return void * @throws TestFrameworkException */ - private function readInCredentialsFile() + private function initializeCredentialStorage() { - $credsFilePath = str_replace( - '.credentials.example', - '.credentials', - BuildProjectCommand::CREDENTIALS_FILE_PATH - ); + if (!$this->initialized) { + // Initialize credential storage by defined order of precedence as the following + $this->initializeFileStorage(); + $this->initializeVaultStorage(); + $this->initializeAwsSecretsManagerStorage(); + $this->initialized = true; + } - if (!file_exists($credsFilePath)) { + if (empty($this->credStorage)) { throw new TestFrameworkException( - "Cannot find .credentials file, please create in " - . TESTS_BP . " in order to reference sensitive information" + 'Invalid Credential Storage. ' . self::CREDENTIAL_STORAGE_INFO + . '.' . $this->getExceptionContexts() ); } - - return file($credsFilePath, FILE_IGNORE_NEW_LINES); + $this->resetExceptionContext(); } /** - * Function which takes the contents of the credentials file and encrypts the entries. + * Initialize file storage * - * @param array $credContents - * @return array + * @return void */ - private function encryptCredFileContents($credContents) + private function initializeFileStorage(): void { - $encryptedCreds = []; - foreach ($credContents as $credValue) { - if (substr($credValue, 0, 1) === '#' || empty($credValue)) { - continue; - } + // 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()); + } + } - list($key, $value) = explode("=", $credValue); - if (!empty($value)) { - $encryptedCreds[$key] = openssl_encrypt( - $value, - self::ENCRYPTION_ALGO, - $this->encodedKey, - 0, - $this->iv + /** + * 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()); } } - - return $encryptedCreds; } /** - * Takes a value encrypted at runtime and descrypts using the object's initial vector. + * Initialize AWS Secrets Manager storage * - * @param string $value - * @return string + * @return void */ - public function decryptSecretValue($value) + private function initializeAwsSecretsManagerStorage() { - return openssl_decrypt($value, self::ENCRYPTION_ALGO, $this->encodedKey, 0, $this->iv); + // 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 7305fdc37..a38e13f7a 100644 --- a/src/Magento/FunctionalTestingFramework/DataGenerator/Handlers/DataObjectHandler.php +++ b/src/Magento/FunctionalTestingFramework/DataGenerator/Handlers/DataObjectHandler.php @@ -1,17 +1,20 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ namespace Magento\FunctionalTestingFramework\DataGenerator\Handlers; use Magento\FunctionalTestingFramework\DataGenerator\Objects\EntityDataObject; use Magento\FunctionalTestingFramework\DataGenerator\Parsers\DataProfileSchemaParser; +use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; use Magento\FunctionalTestingFramework\Exceptions\XmlException; 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 { @@ -32,6 +35,7 @@ class DataObjectHandler implements ObjectHandlerInterface const _ENTITY_KEY = 'entityKey'; const _SEPARATOR = '->'; const _REQUIRED_ENTITY = 'requiredEntity'; + const _FILENAME = 'filename'; const DATA_NAME_ERROR_MSG = "Entity names cannot contain non alphanumeric characters.\tData='%s'"; /** @@ -55,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 */ @@ -65,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(); } @@ -117,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) { @@ -128,8 +149,15 @@ 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 = []; @@ -160,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, @@ -167,12 +203,15 @@ private function processParserOutput($parserOutput) $linkedEntities, $uniquenessData, $vars, - $parentEntity + $parentEntity, + $filename, + $deprecated ); $entityDataObjects[$entityDataObject->getName()] = $entityDataObject; } - + $this->entityNameValidator->summarize(NameValidationUtil::DATA_ENTITY_NAME); + $this->entityKeyValidator->summarize(NameValidationUtil::DATA_ENTITY_KEY); return $entityDataObjects; } @@ -187,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] ?? []); @@ -204,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; } @@ -269,10 +315,14 @@ private function processVarElements($entityData) * * @param EntityDataObject $dataObject * @return EntityDataObject + * @throws TestFrameworkException */ private function extendDataObject($dataObject) { - if ($dataObject->getParentName() != null) { + 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); } return $dataObject; diff --git a/src/Magento/FunctionalTestingFramework/DataGenerator/Handlers/OperationDefinitionObjectHandler.php b/src/Magento/FunctionalTestingFramework/DataGenerator/Handlers/OperationDefinitionObjectHandler.php index 66ca3f594..cc24cd921 100644 --- a/src/Magento/FunctionalTestingFramework/DataGenerator/Handlers/OperationDefinitionObjectHandler.php +++ b/src/Magento/FunctionalTestingFramework/DataGenerator/Handlers/OperationDefinitionObjectHandler.php @@ -1,17 +1,19 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ + namespace Magento\FunctionalTestingFramework\DataGenerator\Handlers; use Magento\FunctionalTestingFramework\DataGenerator\Objects\OperationDefinitionObject; use Magento\FunctionalTestingFramework\DataGenerator\Objects\OperationElement; use Magento\FunctionalTestingFramework\DataGenerator\Parsers\OperationDefinitionParser; use Magento\FunctionalTestingFramework\DataGenerator\Util\OperationElementExtractor; -use Magento\FunctionalTestingFramework\Exceptions\XmlException; 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 { @@ -25,6 +27,7 @@ class OperationDefinitionObjectHandler implements ObjectHandlerInterface const ENTITY_OPERATION_STORE_CODE = 'storeCode'; const ENTITY_OPERATION_SUCCESS_REGEX = 'successRegex'; const ENTITY_OPERATION_RETURN_REGEX = 'returnRegex'; + const ENTITY_OPERATION_RETURN_INDEX = 'returnIndex'; const ENTITY_OPERATION_HEADER = 'header'; const ENTITY_OPERATION_CONTENT_TYPE = 'contentType'; const ENTITY_OPERATION_HEADER_PARAM = 'param'; @@ -128,18 +131,21 @@ public function getOperationDefinition($operation, $dataType) * @return void * @throws \Exception * - * @SuppressWarnings(PHPMD.CyclomaticComplexity) - * @SuppressWarnings(PHPMD.NPathComplexity) - * @SuppressWarnings(PHPMD.UnusedPrivateMethod) - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + * @SuppressWarnings(PHPMD) */ private function initialize() { - //TODO: Reduce CyclomaticComplexity/NPathComplexity/Length of method, remove warning suppression. $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; @@ -147,31 +153,15 @@ 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; - $headers = []; - $params = []; + $headers = $this->initializeHeaders($opDefArray); + $params = $this->initializeParams($opDefArray); $operationElements = []; $removeBackend = $opDefArray[OperationDefinitionObjectHandler::ENTITY_OPERATION_BACKEND_REMOVE] ?? false; - if (array_key_exists(OperationDefinitionObjectHandler::ENTITY_OPERATION_HEADER, $opDefArray)) { - foreach ($opDefArray[OperationDefinitionObjectHandler::ENTITY_OPERATION_HEADER] as $headerEntry) { - if (isset($headerEntry[OperationDefinitionObjectHandler::ENTITY_OPERATION_HEADER_VALUE]) - && $headerEntry[OperationDefinitionObjectHandler::ENTITY_OPERATION_HEADER_VALUE] !== 'none') { - $headers[] = $headerEntry[OperationDefinitionObjectHandler::ENTITY_OPERATION_HEADER_PARAM] - . ': ' - . $headerEntry[OperationDefinitionObjectHandler::ENTITY_OPERATION_HEADER_VALUE]; - } - } - } - - if (array_key_exists(OperationDefinitionObjectHandler::ENTITY_OPERATION_URL_PARAM, $opDefArray)) { - foreach ($opDefArray[OperationDefinitionObjectHandler::ENTITY_OPERATION_URL_PARAM] as $paramEntry) { - $params[$paramEntry[OperationDefinitionObjectHandler::ENTITY_OPERATION_URL_PARAM_KEY]] = - $paramEntry[OperationDefinitionObjectHandler::ENTITY_OPERATION_URL_PARAM_VALUE]; - } - } - // extract relevant OperationObjects as OperationElements if (array_key_exists(OperationDefinitionObjectHandler::ENTITY_OPERATION_OBJECT, $opDefArray)) { foreach ($opDefArray[OperationDefinitionObjectHandler::ENTITY_OPERATION_OBJECT] as $opElementArray) { @@ -224,6 +214,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, @@ -237,8 +234,51 @@ private function initialize() $contentType, $removeBackend, $successRegex, - $returnRegex + $returnRegex, + $returnIndex, + $deprecated ); } + $operationNameValidator->summarize(NameValidationUtil::METADATA_OPERATION_NAME); + } + + /** + * Convert headers metadata into an array of objects for further use in. + * + * @param array $opDefArray + * @return array + */ + private function initializeHeaders(array $opDefArray): array + { + $headers = []; + if (array_key_exists(OperationDefinitionObjectHandler::ENTITY_OPERATION_HEADER, $opDefArray)) { + foreach ($opDefArray[OperationDefinitionObjectHandler::ENTITY_OPERATION_HEADER] as $headerEntry) { + if (isset($headerEntry[OperationDefinitionObjectHandler::ENTITY_OPERATION_HEADER_VALUE]) + && $headerEntry[OperationDefinitionObjectHandler::ENTITY_OPERATION_HEADER_VALUE] !== 'none') { + $headers[] = $headerEntry[OperationDefinitionObjectHandler::ENTITY_OPERATION_HEADER_PARAM] + . ': ' + . $headerEntry[OperationDefinitionObjectHandler::ENTITY_OPERATION_HEADER_VALUE]; + } + } + } + return $headers; + } + + /** + * Convert params metadata into an array of objects. + * + * @param array $opDefArray + * @return array + */ + private function initializeParams(array $opDefArray): array + { + $params = []; + if (array_key_exists(OperationDefinitionObjectHandler::ENTITY_OPERATION_URL_PARAM, $opDefArray)) { + foreach ($opDefArray[OperationDefinitionObjectHandler::ENTITY_OPERATION_URL_PARAM] as $paramEntry) { + $params[$paramEntry[OperationDefinitionObjectHandler::ENTITY_OPERATION_URL_PARAM_KEY]] = + $paramEntry[OperationDefinitionObjectHandler::ENTITY_OPERATION_URL_PARAM_VALUE]; + } + } + return $params; } } diff --git a/src/Magento/FunctionalTestingFramework/DataGenerator/Handlers/PersistedObjectHandler.php b/src/Magento/FunctionalTestingFramework/DataGenerator/Handlers/PersistedObjectHandler.php index 64bb5f0a0..fec6e652d 100644 --- a/src/Magento/FunctionalTestingFramework/DataGenerator/Handlers/PersistedObjectHandler.php +++ b/src/Magento/FunctionalTestingFramework/DataGenerator/Handlers/PersistedObjectHandler.php @@ -1,13 +1,16 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2018 Adobe + * All Rights Reserved. */ namespace Magento\FunctionalTestingFramework\DataGenerator\Handlers; use Magento\FunctionalTestingFramework\DataGenerator\Persist\DataPersistenceHandler; +use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; use Magento\FunctionalTestingFramework\Exceptions\TestReferenceException; +use Magento\FunctionalTestingFramework\Util\Logger\LoggingUtil; +use Magento\FunctionalTestingFramework\Config\MftfApplicationConfig; class PersistedObjectHandler { @@ -52,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() { @@ -86,6 +88,16 @@ public function createEntity( } $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, @@ -94,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; @@ -158,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; @@ -173,12 +185,21 @@ public function getEntity($key, $scope, $entity, $dependentObjectKeys = [], $sto * @param string $field * @param string $scope * @return string - * @throws TestReferenceException - * @throws \Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException */ public function retrieveEntityField($stepKey, $field, $scope) { - return $this->retrieveEntity($stepKey, $scope)->getCreatedDataByName($field); + $fieldValue = $this->retrieveEntity($stepKey, $scope)->getCreatedDataByName($field); + if ($fieldValue === null) { + $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)->warning($warnMsg); + if (MftfApplicationConfig::getConfig()->verboseEnabled() + && MftfApplicationConfig::getConfig()->getPhase() !== MftfApplicationConfig::UNIT_TEST_PHASE) { + print("\n$warnMsg\n"); + } + } + return $fieldValue; } /** @@ -193,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; } @@ -233,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..c0f9117b7 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/DataGenerator/Handlers/SecretStorage/AwsSecretsManagerStorage.php @@ -0,0 +1,212 @@ +<?php +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ + +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 new file mode 100644 index 000000000..842adb07d --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/DataGenerator/Handlers/SecretStorage/BaseStorage.php @@ -0,0 +1,95 @@ +<?php +/** + * Copyright 2019 Adobe + * All Rights Reserved. + */ + +namespace Magento\FunctionalTestingFramework\DataGenerator\Handlers\SecretStorage; + +abstract class BaseStorage +{ + const ENCRYPTION_ALGO = "AES-256-CBC"; + + /** + * Initial vector for open_ssl encryption + * + * @var string + */ + protected static $iv = null; + + /** + * Key for open_ssl encryption/decryption + * + * @var string + */ + protected static $encodedKey = null; + + /** + * Accessed key/value secret data pairs + * + * @var array + */ + protected static $cachedSecretData = []; + + /** + * BaseStorage constructor + */ + public function __construct() + { + if (null === self::$encodedKey) { + self::$encodedKey = base64_encode(openssl_random_pseudo_bytes(16)); + self::$iv = substr(hash('sha256', self::$encodedKey), 0, 16); + } + } + + /** + * Returns the encrypted value based on corresponding key + * + * @param string $key + * @return string|null + */ + public function getEncryptedValue($key) + { + if (!array_key_exists($key, self::$cachedSecretData)) { + return null; + } + return self::$cachedSecretData[$key] ?? null; + } + + /** + * 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|false The decrypted string on success or false on failure + */ + 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 string|false The decrypted string on success or false on failure + */ + public static function getAllDecryptedValuesInString($string) + { + $decrypted = false; + foreach (self::$cachedSecretData as $key => $secretValue) { + if (strpos($string, $secretValue) !== false) { + $decryptedValue = self::getDecryptedValue($secretValue); + if ($decryptedValue === false) { + return false; + } + if (!$decrypted) { + $decrypted = true; + } + $string = str_replace($secretValue, $decryptedValue, $string); + } + } + return $decrypted ? $string : false; + } +} diff --git a/src/Magento/FunctionalTestingFramework/DataGenerator/Handlers/SecretStorage/FileStorage.php b/src/Magento/FunctionalTestingFramework/DataGenerator/Handlers/SecretStorage/FileStorage.php new file mode 100644 index 000000000..8932f51b9 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/DataGenerator/Handlers/SecretStorage/FileStorage.php @@ -0,0 +1,123 @@ +<?php +/** + * Copyright 2019 Adobe + * All Rights Reserved. + */ + +namespace Magento\FunctionalTestingFramework\DataGenerator\Handlers\SecretStorage; + +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 +{ + /** + * Key/value secret data pairs parsed from file + * + * @var array + */ + private $secretData = []; + + /** + * Initialize secret data value which represents encrypted credentials + * + * @return void + * @throws TestFrameworkException + */ + public function initialize(): void + { + if (!$this->secretData) { + $creds = $this->readInCredentialsFile(); + $this->secretData = $this->encryptCredFileContents($creds); + } + } + + /** + * Returns the value of a secret based on corresponding key + * + * @param string $key + * @return string|null + * @throws TestFrameworkException + */ + public function getEncryptedValue($key): ?string + { + $this->initialize(); + + // Check if secret is in cached array + if (null !== ($value = parent::getEncryptedValue($key))) { + return $value; + } + + // log here for verbose config + if (MftfApplicationConfig::getConfig()->verboseEnabled()) { + LoggingUtil::getInstance()->getLogger(FileStorage::class)->debug( + "retrieving secret for key name {$key} from file" + ); + } + + // Retrieve from file storage + if (array_key_exists($key, $this->secretData) && (null !== ($value = $this->secretData[$key]))) { + parent::$cachedSecretData[$key] = $value; + } + + return $value; + } + + /** + * Private function which reads in secret key/values from .credentials file and stores in memory as key/value pair + * + * @return array + * @throws TestFrameworkException + */ + private function readInCredentialsFile() + { + $credsFilePath = str_replace( + '.credentials.example', + '.credentials', + FilePathFormatter::format(TESTS_BP) . '.credentials.example' + ); + + if (!file_exists($credsFilePath)) { + throw new TestFrameworkException( + "Credential file is not used: .credentials file not found in " . TESTS_BP + ); + } + + return file($credsFilePath, FILE_IGNORE_NEW_LINES); + } + + /** + * Function which takes the contents of the credentials file and encrypts the entries + * + * @param array $credContents + * @return array + * @throws TestFrameworkException + */ + private function encryptCredFileContents($credContents) + { + $encryptedCreds = []; + 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); + if (!empty($value)) { + $encryptedCreds[$key] = openssl_encrypt( + $value, + parent::ENCRYPTION_ALGO, + parent::$encodedKey, + 0, + parent::$iv + ); + } + } + return $encryptedCreds; + } +} diff --git a/src/Magento/FunctionalTestingFramework/DataGenerator/Handlers/SecretStorage/VaultStorage.php b/src/Magento/FunctionalTestingFramework/DataGenerator/Handlers/SecretStorage/VaultStorage.php new file mode 100644 index 000000000..d53af9817 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/DataGenerator/Handlers/SecretStorage/VaultStorage.php @@ -0,0 +1,262 @@ +<?php +/** + * Copyright 2019 Adobe + * All Rights Reserved. + */ + +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; + +class VaultStorage extends BaseStorage +{ + /** + * Mftf project path + */ + const MFTF_PATH = '/mftf'; + + /** + * Vault kv version 2 data + */ + const KV2_DATA = 'data'; + + /** + * Default vault token file + */ + const TOKEN_FILE = '.vault-token'; + + /** + * Default vault config file + */ + const CONFIG_FILE = '.vault'; + + /** + * Environment variable name for vault config path + */ + const CONFIG_PATH_ENV_VAR = 'VAULT_CONFIG_PATH'; + + /** + * Regex to grab token helper script + */ + const TOKEN_HELPER_REGEX_GROUP_NAME = 'GROUP_NAME'; + const TOKEN_HELPER_REGEX = "~\s*token_helper\s*=(?<" . self::TOKEN_HELPER_REGEX_GROUP_NAME . ">.+)$~"; + + /** + * Vault client + * + * @var Client + */ + private $client = null; + + /** + * Vault token + * + * @var string + */ + private $token = null; + + /** + * Vault secret base path + * + * @var string + */ + private $secretBasePath; + + /** + * VaultStorage constructor + * + * @param string $baseUrl + * @param string $secretBasePath + * @throws TestFrameworkException + */ + public function __construct($baseUrl, $secretBasePath) + { + parent::__construct(); + if (null === $this->client) { + // 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(); + if (!$this->authenticated()) { + throw new TestFrameworkException("Credential vault is not used: cannot authenticate"); + } + } + + /** + * Returns the value of a secret based on corresponding key + * + * @param string $key + * @return string|null + */ + 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(VaultStorage::class)->debug( + "Retrieving secret for key name {$key} from vault" + ); + } + + $reValue = null; + try { + // Split vendor/key to construct secret path + list($vendor, $key) = explode('/', trim($key, '/'), 2); + $url = $this->secretBasePath + . (empty(self::KV2_DATA) ? '' : '/' . self::KV2_DATA) + . self::MFTF_PATH + . '/' + . $vendor + . '/' + . $key; + // Read value by key from vault + $value = $this->client->read($url)->getData()[self::KV2_DATA][$key]; + // Encrypt value for return + $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($errMessage); + } + } + return $reValue; + } + + /** + * Check if vault token is valid + * + * @return boolean + */ + private function authenticated() + { + try { + // Authenticating using token auth backend + $authenticated = $this->client + ->setAuthenticationStrategy(new VaultTokenAuthStrategy($this->token)) + ->authenticate(); + + if ($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; + } + + /** + * Read vault token from file system + * + * @return void + * @throws TestFrameworkException + */ + private function readVaultTokenFromFileSystem() + { + // Find user home directory + $homeDir = getenv('HOME'); + if ($homeDir === false) { + throw new TestFrameworkException( + "HOME environment variable is not set. It's required when using vault." + ); + } + $homeDir = realpath($homeDir) . DIRECTORY_SEPARATOR; + + // Read .vault-token file if it is found in default location + $vaultTokenFile = $homeDir . self::TOKEN_FILE; + if (file_exists($vaultTokenFile)) { + $token = file_get_contents($vaultTokenFile); + if ($token !== false) { + $this->token = $token; + return; + } + } + + // Otherwise search vault config file for custom token helper script + $vaultConfigPath = getenv(self::CONFIG_PATH_ENV_VAR); + if ($vaultConfigPath === false) { + $vaultConfigFile = $homeDir . self::CONFIG_FILE; + } else { + $vaultConfigFile = realpath($vaultConfigPath) . DIRECTORY_SEPARATOR . self::CONFIG_FILE; + } + // Get custom token helper script file from .vault config file + if (file_exists($vaultConfigFile)) { + $cmd = $this->getTokenHelperScript(file($vaultConfigFile, FILE_IGNORE_NEW_LINES)); + if (!empty($cmd)) { + $this->token = $this->execVaultTokenHelper($cmd . ' get'); + return; + } + } + throw new TestFrameworkException( + 'Unable to read .vault-token file. Please authenticate to vault through vault CLI first.' + ); + } + + /** + * Get vault token helper script by parsing lines in vault config file + * + * @param array $lines + * @return string + */ + private function getTokenHelperScript($lines) + { + $tokenHelper = ''; + foreach ($lines as $line) { + preg_match(self::TOKEN_HELPER_REGEX, $line, $matches); + if (isset($matches[self::TOKEN_HELPER_REGEX_GROUP_NAME])) { + $tokenHelper = trim(trim(trim($matches[self::TOKEN_HELPER_REGEX_GROUP_NAME]), '"')); + } + } + return $tokenHelper; + } + + /** + * Execute vault token helper script and return the token it contains + * + * @param string $cmd + * @return string + * @throws TestFrameworkException + */ + private function execVaultTokenHelper($cmd) + { + exec($cmd, $out, $status); + if ($status === 0 && isset($out[0]) && !empty($out[0])) { + return $out[0]; + } + throw new TestFrameworkException( + 'Error running custom vault token helper script. Please make sure vault CLI works in your environment.' + ); + } +} diff --git a/src/Magento/FunctionalTestingFramework/DataGenerator/Handlers/SecretStorage/VaultTokenAuthStrategy.php b/src/Magento/FunctionalTestingFramework/DataGenerator/Handlers/SecretStorage/VaultTokenAuthStrategy.php new file mode 100644 index 000000000..fa4b18409 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/DataGenerator/Handlers/SecretStorage/VaultTokenAuthStrategy.php @@ -0,0 +1,47 @@ +<?php +/** + * Copyright 2019 Adobe + * All Rights Reserved. + */ + +namespace Magento\FunctionalTestingFramework\DataGenerator\Handlers\SecretStorage; + +use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; +use Vault\AuthenticationStrategies\AbstractAuthenticationStrategy; +use Vault\ResponseModels\Auth; + +/** + * Class VaultTokenAuthStrategy + */ +class VaultTokenAuthStrategy extends AbstractAuthenticationStrategy +{ + /** + * @var string + */ + protected $token; + + /** + * VaultTokenAuthStrategy constructor + * + * @param string $token + */ + public function __construct($token) + { + $this->token = $token; + } + + /** + * Returns auth for further interactions with Vault + * + * @return Auth + * @throws TestFrameworkException + */ + public function authenticate(): ?Auth + { + try { + return new Auth(['clientToken' => $this->token]); + } catch (\Exception $e) { + throw new TestFrameworkException("Cannot authenticate Vault token."); + } + } +} diff --git a/src/Magento/FunctionalTestingFramework/DataGenerator/Objects/EntityDataObject.php b/src/Magento/FunctionalTestingFramework/DataGenerator/Objects/EntityDataObject.php index 1ba126e5f..eae7715b1 100644 --- a/src/Magento/FunctionalTestingFramework/DataGenerator/Objects/EntityDataObject.php +++ b/src/Magento/FunctionalTestingFramework/DataGenerator/Objects/EntityDataObject.php @@ -1,13 +1,15 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ namespace Magento\FunctionalTestingFramework\DataGenerator\Objects; use Magento\FunctionalTestingFramework\Config\MftfApplicationConfig; +use Magento\FunctionalTestingFramework\DataGenerator\Util\GenerationDataReferenceResolver; use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; +use Magento\FunctionalTestingFramework\Exceptions\TestReferenceException; use Magento\FunctionalTestingFramework\Util\Logger\LoggingUtil; /** @@ -72,19 +74,43 @@ class EntityDataObject */ private $parentEntity; + /** + * String of filename + * @var string + */ + 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 $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, $type, $data, $linkedEntities, $uniquenessData, $vars = [], $parentEntity = null) - { + public function __construct( + $name, + $type, + $data, + $linkedEntities, + $uniquenessData, + $vars = [], + $parentEntity = null, + $filename = null, + $deprecated = null + ) { $this->name = $name; $this->type = $type; $this->data = $data; @@ -95,6 +121,18 @@ public function __construct($name, $type, $data, $linkedEntities, $uniquenessDat $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; } /** @@ -107,6 +145,16 @@ public function getName() return $this->name; } + /** + * Getter for the Entity Filename + * + * @return string + */ + public function getFilename() + { + return $this->filename; + } + /** * Get the type of this entity data object * @@ -149,17 +197,59 @@ public function getDataByName($name, $uniquenessFormat) throw new TestFrameworkException($exceptionMessage); } - $name_lower = strtolower($name); + if ($this->data === null) { + return null; + } + return $this->resolveDataReferences($name, $uniquenessFormat); + } - if ($this->data !== null && array_key_exists($name_lower, $this->data)) { - $uniquenessData = $this->getUniquenessDataByName($name_lower); - if (null === $uniquenessData || $uniquenessFormat == self::NO_UNIQUE_PROCESS) { + /** + * Resolves data references in entities while generating static test files. + * + * @param string $name + * @param integer $uniquenessFormat + * @return string|null + * @throws TestFrameworkException + * @throws TestReferenceException + */ + private function resolveDataReferences($name, $uniquenessFormat) + { + $name_lower = strtolower($name); + $dataReferenceResolver = new GenerationDataReferenceResolver(); + if (array_key_exists($name_lower, $this->data)) { + if (is_array($this->data[$name_lower])) { + return $this->data[$name_lower]; + } + $uniquenessData = $this->getUniquenessDataByName($name_lower) === null + ? $dataReferenceResolver->getDataUniqueness( + $this->data[$name_lower], + $this->name . '.' . $name + ) + : $this->getUniquenessDataByName($name_lower); + if ($uniquenessData !== null) { + $this->uniquenessData[$name] = $uniquenessData; + } + $this->data[$name_lower] = $dataReferenceResolver->getDataReference( + $this->data[$name_lower], + $this->name . '.' . $name + ); + if (null === $uniquenessData || $uniquenessFormat === self::NO_UNIQUE_PROCESS) { return $this->data[$name_lower]; } return $this->formatUniqueData($name_lower, $uniquenessData, $uniquenessFormat); + } elseif (array_key_exists($name, $this->data)) { + if (is_array($this->data[$name])) { + return $this->data[$name]; + } + $this->data[$name] = $dataReferenceResolver->getDataReference( + $this->data[$name], + $this->name . '.' . $name + ); + // Data returned by the API may be camelCase so we need to check for the original $name also. + return $this->data[$name]; + } else { + return null; } - - return null; } /** @@ -186,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()); @@ -194,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() . '")'; @@ -268,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 d6237a437..0c5fcafff 100644 --- a/src/Magento/FunctionalTestingFramework/DataGenerator/Objects/OperationDefinitionObject.php +++ b/src/Magento/FunctionalTestingFramework/DataGenerator/Objects/OperationDefinitionObject.php @@ -1,13 +1,16 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ namespace Magento\FunctionalTestingFramework\DataGenerator\Objects; +use Magento\FunctionalTestingFramework\Util\Logger\LoggingUtil; + /** * Class OperationDefinitionObject + * @SuppressWarnings(PHPMD) */ class OperationDefinitionObject { @@ -104,27 +107,43 @@ class OperationDefinitionObject */ private $returnRegex; + /** + * Index of element to be returned from "returnRegex" matches. + * + * @var string + */ + private $returnIndex; + /** * Determines if operation should remove backend_name from URL. * @var boolean */ 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 $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( @@ -140,20 +159,24 @@ public function __construct( $contentType, $removeBackend, $successRegex = null, - $returnRegex = null + $returnRegex = null, + $returnIndex = null, + $deprecated = null ) { $this->name = $name; $this->operation = $operation; $this->dataType = $dataType; $this->apiMethod = $apiMethod; - $this->apiUri = trim($apiUri, '/'); + $this->apiUri = trim($apiUri ?? '', '/'); $this->auth = $auth; $this->headers = $headers; $this->params = $params; $this->operationMetadata = $metaData; $this->successRegex = $successRegex; $this->returnRegex = $returnRegex; + $this->returnIndex = $returnIndex; $this->removeBackend = $removeBackend; + $this->deprecated = $deprecated; $this->apiUrl = null; if (!empty($contentType)) { @@ -166,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 * @@ -284,6 +317,16 @@ public function getReturnRegex() return $this->returnRegex; } + /** + * Getter for return regex matches index. + * + * @return string|null + */ + public function getReturnIndex() + { + return $this->returnIndex; + } + /** * Function to append or add query parameters * @@ -300,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/Objects/OperationElement.php b/src/Magento/FunctionalTestingFramework/DataGenerator/Objects/OperationElement.php index 7338e52f3..185c0703d 100644 --- a/src/Magento/FunctionalTestingFramework/DataGenerator/Objects/OperationElement.php +++ b/src/Magento/FunctionalTestingFramework/DataGenerator/Objects/OperationElement.php @@ -1,7 +1,7 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ namespace Magento\FunctionalTestingFramework\DataGenerator\Objects; @@ -64,11 +64,7 @@ public function __construct($key, $value, $type, $required, $nestedElements = [] $this->value = $value; $this->type = $type; $this->nestedElements = $nestedElements; - if ($required) { - $this->required = true; - } else { - $this->required = false; - } + $this->required = filter_var($required, FILTER_VALIDATE_BOOLEAN); $this->nestedMetadata = $nestedMetadata; } diff --git a/src/Magento/FunctionalTestingFramework/DataGenerator/Parsers/DataProfileSchemaParser.php b/src/Magento/FunctionalTestingFramework/DataGenerator/Parsers/DataProfileSchemaParser.php index 0a40afe09..51fc4e8bc 100644 --- a/src/Magento/FunctionalTestingFramework/DataGenerator/Parsers/DataProfileSchemaParser.php +++ b/src/Magento/FunctionalTestingFramework/DataGenerator/Parsers/DataProfileSchemaParser.php @@ -1,7 +1,7 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ namespace Magento\FunctionalTestingFramework\DataGenerator\Parsers; diff --git a/src/Magento/FunctionalTestingFramework/DataGenerator/Parsers/OperationDefinitionParser.php b/src/Magento/FunctionalTestingFramework/DataGenerator/Parsers/OperationDefinitionParser.php index e7a68435c..b59eee44d 100644 --- a/src/Magento/FunctionalTestingFramework/DataGenerator/Parsers/OperationDefinitionParser.php +++ b/src/Magento/FunctionalTestingFramework/DataGenerator/Parsers/OperationDefinitionParser.php @@ -1,7 +1,7 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ namespace Magento\FunctionalTestingFramework\DataGenerator\Parsers; 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 6adf87892..000000000 --- a/src/Magento/FunctionalTestingFramework/DataGenerator/Persist/Curl/AbstractExecutor.php +++ /dev/null @@ -1,45 +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 -{ - /** - * Base url. - * - * @var string - */ - protected static $baseUrl = null; - - /** - * Resolve base url. - * - * @return void - */ - protected static function resolveBaseUrl() - { - - if ((getenv('MAGENTO_RESTAPI_SERVER_HOST') !== false) - && (getenv('MAGENTO_RESTAPI_SERVER_HOST') !== '') ) { - self::$baseUrl = getenv('MAGENTO_RESTAPI_SERVER_HOST'); - } else { - self::$baseUrl = getenv('MAGENTO_BASE_URL'); - } - - if ((getenv('MAGENTO_RESTAPI_SERVER_PORT') !== false) - && (getenv('MAGENTO_RESTAPI_SERVER_PORT') !== '')) { - self::$baseUrl .= ':' . getenv('MAGENTO_RESTAPI_SERVER_PORT'); - } - - self::$baseUrl = rtrim(self::$baseUrl, '/') . '/'; - } -} diff --git a/src/Magento/FunctionalTestingFramework/DataGenerator/Persist/CurlHandler.php b/src/Magento/FunctionalTestingFramework/DataGenerator/Persist/CurlHandler.php index ee61d8c12..d0eeb3f51 100644 --- a/src/Magento/FunctionalTestingFramework/DataGenerator/Persist/CurlHandler.php +++ b/src/Magento/FunctionalTestingFramework/DataGenerator/Persist/CurlHandler.php @@ -1,19 +1,21 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ + namespace Magento\FunctionalTestingFramework\DataGenerator\Persist; -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\Allure\AllureHelper; +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 @@ -107,6 +109,7 @@ public function executeRequest($dependentEntities) $executor = null; $successRegex = null; $returnRegex = null; + $returnIndex = null; if ((null !== $dependentEntities) && is_array($dependentEntities)) { $entities = array_merge([$this->entityObject], $dependentEntities); @@ -119,6 +122,11 @@ public function executeRequest($dependentEntities) $contentType = $this->operationDefinition->getContentType(); $successRegex = $this->operationDefinition->getSuccessRegex(); $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( @@ -128,19 +136,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) { @@ -156,13 +166,18 @@ public function executeRequest($dependentEntities) $executor->write( $apiUrl, $this->requestData, - self::$curlMethodMapping[$this->operation], + $method ?? self::$curlMethodMapping[$this->operation], $headers ); - $response = $executor->read($successRegex, $returnRegex); + $response = $executor->read($successRegex, $returnRegex, $returnIndex); $executor->close(); + AllureHelper::addAttachmentToCurrentStep( + json_encode(json_decode($response, true), JSON_PRETTY_PRINT+JSON_UNESCAPED_UNICODE+JSON_UNESCAPED_SLASHES), + 'Response Data' + ); + return $response; } @@ -197,15 +212,33 @@ private function resolveUrlReference($urlIn, $entityObjects) { $urlOut = $urlIn; $matchedParams = []; + // Find all the params ({}) references preg_match_all("/[{](.+?)[}]/", $urlIn, $matchedParams); if (!empty($matchedParams)) { foreach ($matchedParams[0] as $paramKey => $paramValue) { + $paramEntityParent = ""; + $matchedParent = []; + $dataItem = $matchedParams[1][$paramKey]; + // Find all the parent property (Type.key) references, assuming there will be only one + // parent property reference within one param + preg_match_all("/(.+?)\./", $dataItem, $matchedParent); + + if (!empty($matchedParent) && !empty($matchedParent[0])) { + $paramEntityParent = $matchedParent[1][0]; + $dataItem = preg_replace('/^'.$matchedParent[0][0].'/', '', $dataItem); + } + foreach ($entityObjects as $entityObject) { - $param = $entityObject->getDataByName( - $matchedParams[1][$paramKey], - EntityDataObject::CEST_UNIQUE_VALUE - ); + $param = null; + + if ($paramEntityParent === "" || $entityObject->getType() === $paramEntityParent) { + $param = $entityObject->getDataByName( + $dataItem, + EntityDataObject::CEST_UNIQUE_VALUE + ); + } + if (null !== $param) { $urlOut = str_replace($paramValue, $param, $urlOut); continue; diff --git a/src/Magento/FunctionalTestingFramework/DataGenerator/Persist/DataPersistenceHandler.php b/src/Magento/FunctionalTestingFramework/DataGenerator/Persist/DataPersistenceHandler.php index dcb9d158a..3c757c678 100644 --- a/src/Magento/FunctionalTestingFramework/DataGenerator/Persist/DataPersistenceHandler.php +++ b/src/Magento/FunctionalTestingFramework/DataGenerator/Persist/DataPersistenceHandler.php @@ -1,7 +1,7 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ namespace Magento\FunctionalTestingFramework\DataGenerator\Persist; @@ -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; @@ -78,12 +82,15 @@ public function __construct($entityObject, $dependentObjects = [], $customFields * @return void * @throws TestFrameworkException */ - public function createEntity($storeCode = null) + public function createEntity(?string $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, @@ -126,12 +136,15 @@ public function updateEntity($updateDataName, $updateDependentObjects = []) * @return void * @throws TestFrameworkException */ - public function getEntity($index = null, $storeCode = null) + public function getEntity(?string $index = null, ?string $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 a14d384a8..a492fef37 100644 --- a/src/Magento/FunctionalTestingFramework/DataGenerator/Persist/OperationDataArrayResolver.php +++ b/src/Magento/FunctionalTestingFramework/DataGenerator/Persist/OperationDataArrayResolver.php @@ -1,7 +1,7 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ namespace Magento\FunctionalTestingFramework\DataGenerator\Persist; @@ -11,7 +11,9 @@ use Magento\FunctionalTestingFramework\DataGenerator\Objects\EntityDataObject; use Magento\FunctionalTestingFramework\DataGenerator\Objects\OperationElement; use Magento\FunctionalTestingFramework\DataGenerator\Util\OperationElementExtractor; +use Magento\FunctionalTestingFramework\DataGenerator\Util\RuntimeDataReferenceResolver; use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; +use Magento\FunctionalTestingFramework\Exceptions\TestReferenceException; class OperationDataArrayResolver { @@ -44,7 +46,7 @@ class OperationDataArrayResolver * * @param array $dependentEntities */ - public function __construct($dependentEntities = null) + public function __construct(?array $dependentEntities = null) { if ($dependentEntities !== null) { foreach ($dependentEntities as $entity) { @@ -65,16 +67,18 @@ public function __construct($dependentEntities = null) * @param boolean $fromArray * @return array * @throws \Exception + * * @SuppressWarnings(PHPMD.CyclomaticComplexity) + * I suppressed this warning because I was in a hurry to deliver a community PR. That PR modified this function and + * introduced a new conditional, bumping the complexity to 11. */ public function resolveOperationDataArray($entityObject, $operationMetadata, $operation, $fromArray = false) { - //TODO: Refactor to reduce Cyclomatic Complexity, remove SupressWarning accordingly. $operationDataArray = []; 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( @@ -103,63 +107,65 @@ public function resolveOperationDataArray($entityObject, $operationMetadata, $op $operationElementType = $operationElement->getValue(); if (in_array($operationElementType, self::PRIMITIVE_TYPES)) { - $elementData = $this->resolvePrimitiveReference( + $this->resolvePrimitiveReferenceElement( $entityObject, - $operationElement->getKey(), - $operationElement->getType() + $operationElement, + $operationElementType, + $operationDataArray ); - - // 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 (array_key_exists($operationElement->getKey(), $entityObject->getUniquenessData())) { - $uniqueData = $entityObject->getUniquenessDataByName($operationElement->getKey()); - if ($uniqueData === 'suffix') { - $elementData .= (string)self::getSequence($entityObject->getName()); - } else { - $elementData = (string)self::getSequence($entityObject->getName()) . $elementData; - } + } elseif (is_array($operationElementType)) { + foreach ($operationElementType as $currentElementType) { + if (in_array($currentElementType, self::PRIMITIVE_TYPES)) { + $this->resolvePrimitiveReferenceElement( + $entityObject, + $operationElement, + $currentElementType, + $operationDataArray + ); + } else { + $this->resolveNonPrimitiveReferenceElement( + $entityObject, + $operation, + $fromArray, + $currentElementType, + $operationElement, + $operationDataArray + ); } - $operationDataArray[$operationElement->getKey()] = $this->castValue( - $operationElementType, - $elementData - ); - } elseif ($operationElement->isRequired()) { - throw new \Exception(sprintf( - self::EXCEPTION_REQUIRED_DATA, - $operationElement->getType(), - $operationElement->getKey(), - $entityObject->getName() - )); } } else { - $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) { - throw new \Exception(sprintf( - self::EXCEPTION_REQUIRED_DATA, - $operationElement->getType(), - $operationElement->getKey(), - $entityObject->getName() - )); - } - foreach ($entityNamesOfType as $entityName) { - $operationDataSubArray = $this->resolveNonPrimitiveElement( - $entityName, - $operationElement, - $operation, - $fromArray - ); + $this->resolveNonPrimitiveReferenceElement( + $entityObject, + $operation, + $fromArray, + $operationElementType, + $operationElement, + $operationDataArray + ); + } + } + return $this->resolveRunTimeDataReferences($operationDataArray, $entityObject); + } - if ($operationElement->getType() == OperationDefinitionObjectHandler::ENTITY_OPERATION_ARRAY) { - $operationDataArray[$operationElement->getKey()][] = $operationDataSubArray; - } else { - $operationDataArray[$operationElement->getKey()] = $operationDataSubArray; - } - } + /** + * Resolve data references at run time. + * @param array $operationDataArray + * @param EntityDataObject $entityObject + * @return array + * @throws TestFrameworkException + * @throws TestReferenceException + */ + private function resolveRunTimeDataReferences($operationDataArray, $entityObject) + { + $dataReferenceResolver = new RuntimeDataReferenceResolver(); + foreach ($operationDataArray as $key => $operationDataValue) { + if (is_array($operationDataValue)) { + continue; } + $operationDataArray[$key] = $dataReferenceResolver->getDataReference( + $operationDataValue, + $entityObject->getName() + ); } return $operationDataArray; @@ -183,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) { @@ -218,7 +224,7 @@ private function getDependentEntitiesOfType($type) $entitiesOfType = []; foreach ($this->dependentEntities as $dependentEntity) { - if ($dependentEntity->getType() == $type) { + if ($dependentEntity->getType() === $type) { $entitiesOfType[] = $dependentEntity; } } @@ -238,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; @@ -266,8 +273,9 @@ private function resolveNonPrimitiveElement($entityName, $operationElement, $ope $linkedEntityObj = $this->resolveLinkedEntityObject($entityName); // in array case - if (!empty($operationElement->getNestedOperationElement($operationElement->getValue())) - && $operationElement->getType() == OperationDefinitionObjectHandler::ENTITY_OPERATION_ARRAY + if (!is_array($operationElement->getValue()) + && !empty($operationElement->getNestedOperationElement($operationElement->getValue())) + && $operationElement->getType() === OperationDefinitionObjectHandler::ENTITY_OPERATION_ARRAY ) { $operationSubArray = $this->resolveOperationDataArray( $linkedEntityObj, @@ -374,5 +382,106 @@ private function castValue($type, $value) return $newVal; } + + /** + * Resolve a reference for a primitive piece of data + * + * @param EntityDataObject $entityObject + * @param $operationElement + * @param $operationElementType + * @param array $operationDataArray + * @throws TestFrameworkException + */ + private function resolvePrimitiveReferenceElement($entityObject, $operationElement, $operationElementType, array &$operationDataArray) + { + $elementData = $this->resolvePrimitiveReference( + $entityObject, + $operationElement->getKey(), + $operationElement->getType() + ); + + // 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 && $elementData !== '') { + if (array_key_exists($operationElement->getKey(), $entityObject->getUniquenessData())) { + $uniqueData = $entityObject->getUniquenessDataByName($operationElement->getKey()); + if ($uniqueData === 'suffix') { + $elementData .= (string)self::getSequence($entityObject->getName()); + } else { + $elementData = (string)self::getSequence($entityObject->getName()) . $elementData; + } + } + $operationDataArray[$operationElement->getKey()] = $this->castValue( + $operationElementType, + $elementData + ); + } elseif ($operationElement->isRequired()) { + throw new \Exception(sprintf( + self::EXCEPTION_REQUIRED_DATA, + $operationElement->getType(), + $operationElement->getKey(), + $entityObject->getName() + )); + } + } + + /** + * Resolves DataObjects referenced by the operation + * + * @param $entityObject + * @param $operation + * @param $fromArray + * @param $operationElementType + * @param $operationElement + * @param array $operationDataArray + * @throws TestFrameworkException + */ + private function resolveNonPrimitiveReferenceElement($entityObject, $operation, $fromArray, &$operationElementType, $operationElement, array &$operationDataArray) + { + $operationElementProperty = null; + if (strpos($operationElementType, '.') !== false) { + $operationElementComponents = explode('.', $operationElementType); + $operationElementType = $operationElementComponents[0]; + $operationElementProperty = $operationElementComponents[1]; + } + + $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) { + throw new \Exception(sprintf( + self::EXCEPTION_REQUIRED_DATA, + $operationElement->getType(), + $operationElement->getKey(), + $entityObject->getName() + )); + } + foreach ($entityNamesOfType as $entityName) { + if ($operationElementProperty === null) { + $operationDataSubArray = $this->resolveNonPrimitiveElement( + $entityName, + $operationElement, + $operation, + $fromArray + ); + } else { + $linkedEntityObj = $this->resolveLinkedEntityObject($entityName); + $operationDataSubArray = $linkedEntityObj->getDataByName($operationElementProperty, 0); + + if ($operationDataSubArray === null) { + throw new \Exception( + sprintf('Property %s not found in entity %s \n', $operationElementProperty, $entityName) + ); + } + } + + if ($operationElement->getType() === OperationDefinitionObjectHandler::ENTITY_OPERATION_ARRAY) { + $operationDataArray[$operationElement->getKey()][] = $operationDataSubArray; + } else { + $operationDataArray[$operationElement->getKey()] = $operationDataSubArray; + } + } + } // @codingStandardsIgnoreEnd } diff --git a/src/Magento/FunctionalTestingFramework/DataGenerator/Util/DataExtensionUtil.php b/src/Magento/FunctionalTestingFramework/DataGenerator/Util/DataExtensionUtil.php index 1123531ed..31af658e8 100644 --- a/src/Magento/FunctionalTestingFramework/DataGenerator/Util/DataExtensionUtil.php +++ b/src/Magento/FunctionalTestingFramework/DataGenerator/Util/DataExtensionUtil.php @@ -1,7 +1,7 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2018 Adobe + * All Rights Reserved. */ namespace Magento\FunctionalTestingFramework\DataGenerator\Util; @@ -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(); @@ -92,7 +92,9 @@ public function extendEntity($entityObject) $newLinkedReferences, $newUniqueReferences, $newVarReferences, - $entityObject->getParentName() + $entityObject->getParentName(), + $entityObject->getFilename(), + $entityObject->getDeprecated() ); return $extendedEntity; } diff --git a/src/Magento/FunctionalTestingFramework/DataGenerator/Util/DataReferenceResolverInterface.php b/src/Magento/FunctionalTestingFramework/DataGenerator/Util/DataReferenceResolverInterface.php new file mode 100644 index 000000000..44e9a9643 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/DataGenerator/Util/DataReferenceResolverInterface.php @@ -0,0 +1,26 @@ +<?php +/** + * Copyright 2019 Adobe + * All Rights Reserved. + */ + +namespace Magento\FunctionalTestingFramework\DataGenerator\Util; + +interface DataReferenceResolverInterface +{ + const REFERENCE_REGEX_PATTERN = "/(?<reference>{{[\w]+\..+}})/"; + + /** + * @param string $data + * @param string $originalDataEntity + * @return mixed + */ + public function getDataReference(string $data, string $originalDataEntity); + + /** + * @param string $data + * @param string $originalDataEntity + * @return mixed + */ + public function getDataUniqueness(string $data, string $originalDataEntity); +} diff --git a/src/Magento/FunctionalTestingFramework/DataGenerator/Util/GenerationDataReferenceResolver.php b/src/Magento/FunctionalTestingFramework/DataGenerator/Util/GenerationDataReferenceResolver.php new file mode 100644 index 000000000..5e1790856 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/DataGenerator/Util/GenerationDataReferenceResolver.php @@ -0,0 +1,96 @@ +<?php +/** + * Copyright 2019 Adobe + * All Rights Reserved. + */ + +namespace Magento\FunctionalTestingFramework\DataGenerator\Util; + +use Magento\FunctionalTestingFramework\DataGenerator\Handlers\DataObjectHandler; +use Magento\FunctionalTestingFramework\Exceptions\TestReferenceException; +use Magento\FunctionalTestingFramework\Test\Objects\ActionObject; + +/** + * Class resolves data references in data entities while generating static test files. + */ +class GenerationDataReferenceResolver implements DataReferenceResolverInterface +{ + /** + * Returns data uniqueness for data entity field. + * + * + * @param string $data + * @param string $originalDataEntity + * @return string|null + * @throws TestReferenceException + */ + public function getDataUniqueness(string $data, string $originalDataEntity) + { + preg_match( + ActionObject::ACTION_ATTRIBUTE_VARIABLE_REGEX_PATTERN, + $data, + $matches + ); + + if (empty($matches['reference'])) { + return null; + } + + $strippedReference = str_replace(['{{', '}}'], '', $matches['reference']); + list($entity, $var) = explode('.', $strippedReference); + $entityObject = DataObjectHandler::getInstance()->getObject($entity); + if ($entityObject === null) { + throw new TestReferenceException( + "Could not resolve entity reference \"{$matches['reference']}\" " + . "in Data entity \"{$originalDataEntity}\"" + ); + } + + return $entityObject->getUniquenessDataByName($var); + } + + /** + * Returns data by reference if reference exist. + * + * @param string $data + * @param string $originalDataEntity + * @return string|null + * @throws TestReferenceException + */ + public function getDataReference(string $data, string $originalDataEntity) + { + $result = null; + preg_match(self::REFERENCE_REGEX_PATTERN, $data, $matches); + + if (empty($matches['reference'])) { + return $data; + } + + $strippedReference = str_replace(['{{', '}}'], '', $matches['reference']); + list($entity, $var) = explode('.', $strippedReference); + switch ($entity) { + case ActionObject::__ENV: + case ActionObject::__CREDS: + $result = $data; + break; + default: + $entityObject = DataObjectHandler::getInstance()->getObject($entity); + if ($entityObject === null) { + throw new TestReferenceException( + "Could not find data entity by name \"{$entityObject}\" " + . "referenced in Data entity \"{$originalDataEntity}\"" . PHP_EOL + ); + } + $entityData = $entityObject->getAllData(); + if (!isset($entityData[$var])) { + throw new TestReferenceException( + "Could not resolve entity reference \"{$matches['reference']}\" " + . "in Data entity \"{$originalDataEntity}\"" . PHP_EOL + ); + } + $result = $entityData[$var]; + } + + return $result; + } +} diff --git a/src/Magento/FunctionalTestingFramework/DataGenerator/Util/OperationElementExtractor.php b/src/Magento/FunctionalTestingFramework/DataGenerator/Util/OperationElementExtractor.php index 35d188f5b..25c061e4c 100644 --- a/src/Magento/FunctionalTestingFramework/DataGenerator/Util/OperationElementExtractor.php +++ b/src/Magento/FunctionalTestingFramework/DataGenerator/Util/OperationElementExtractor.php @@ -1,7 +1,7 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ namespace Magento\FunctionalTestingFramework\DataGenerator\Util; @@ -112,9 +112,18 @@ private function extractOperationField(&$operationElements, $operationFieldArray private function extractOperationArray(&$operationArrayData, $operationArrayArray) { foreach ($operationArrayArray as $operationFieldType) { - $operationElementValue = - $operationFieldType[OperationDefinitionObjectHandler::ENTITY_OPERATION_ARRAY_VALUE][0] - [OperationElementExtractor::OPERATION_OBJECT_ARRAY_VALUE] ?? null; + $operationElementValue = []; + $entityValueKey = OperationDefinitionObjectHandler::ENTITY_OPERATION_ARRAY_VALUE; + if (isset($operationFieldType[$entityValueKey])) { + foreach ($operationFieldType[$entityValueKey] as $operationFieldValue) { + $operationElementValue[] = + $operationFieldValue[OperationElementExtractor::OPERATION_OBJECT_ARRAY_VALUE] ?? null; + } + } + + if (count($operationElementValue) === 1) { + $operationElementValue = array_pop($operationElementValue); + } $nestedOperationElements = []; if (array_key_exists(OperationElementExtractor::OPERATION_OBJECT_OBJ_NAME, $operationFieldType)) { diff --git a/src/Magento/FunctionalTestingFramework/DataGenerator/Util/RuntimeDataReferenceResolver.php b/src/Magento/FunctionalTestingFramework/DataGenerator/Util/RuntimeDataReferenceResolver.php new file mode 100644 index 000000000..ad7bb7567 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/DataGenerator/Util/RuntimeDataReferenceResolver.php @@ -0,0 +1,105 @@ +<?php +/** + * Copyright 2019 Adobe + * All Rights Reserved. + */ + +namespace Magento\FunctionalTestingFramework\DataGenerator\Util; + +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; + +/** + * Class resolves data references in data entities at the runtime of tests. + */ +class RuntimeDataReferenceResolver implements DataReferenceResolverInterface +{ + /** + * Returns data by reference if reference exist. + * + * @param string $data + * @param string $originalDataEntity + * @return array|false|string|null + * @throws TestReferenceException + * @throws TestFrameworkException + */ + public function getDataReference(string $data, string $originalDataEntity) + { + $result = null; + preg_match(self::REFERENCE_REGEX_PATTERN, $data, $matches); + + if (empty($matches['reference'])) { + return $data; + } + + $strippedReference = str_replace(['{{', '}}'], '', $matches['reference']); + list($entity, $var) = explode('.', $strippedReference); + switch ($entity) { + case ActionObject::__ENV: + $result = str_replace($matches['reference'], getenv($var), $data); + break; + 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: + $entityObject = DataObjectHandler::getInstance()->getObject($entity); + if ($entityObject === null) { + throw new TestReferenceException( + "Could not find data entity by name \"{$entityObject}\" " + . "referenced in Data entity \"{$originalDataEntity}\"" . PHP_EOL + ); + } + $entityData = $entityObject->getAllData(); + if (!isset($entityData[$var])) { + throw new TestReferenceException( + "Could not resolve entity reference \"{$matches['reference']}\" " + . "in Data entity \"{$originalDataEntity}\"" . PHP_EOL + ); + } + $result = $entityData[$var]; + } + + return $result; + } + + /** + * Returns data uniqueness for data entity field. + * + * @param string $data + * @param string $originalDataEntity + * @return string|null + * @throws TestReferenceException + */ + public function getDataUniqueness(string $data, string $originalDataEntity) + { + preg_match( + ActionObject::ACTION_ATTRIBUTE_VARIABLE_REGEX_PATTERN, + $data, + $matches + ); + + if (empty($matches['reference'])) { + return null; + } + + $strippedReference = str_replace(['{{', '}}'], '', $matches['reference']); + list($entity, $var) = explode('.', $strippedReference); + $entityObject = DataObjectHandler::getInstance()->getObject($entity); + if ($entityObject === null) { + throw new TestReferenceException( + "Could not resolve entity reference \"{$matches['reference']}\" " + . "in Data entity \"{$originalDataEntity}\"" + ); + } + + return $entityObject->getUniquenessDataByName($var); + } +} diff --git a/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataOperation.xsd b/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataOperation.xsd index 7a455dc2b..4c6206075 100644 --- a/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataOperation.xsd +++ b/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataOperation.xsd @@ -1,9 +1,9 @@ <?xml version="1.0" encoding="UTF-8"?> <!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ +/** + * Copyright 2017 Adobe + * All Rights Reserved. + */ --> <xs:schema attributeFormDefault="unqualified" elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema"> @@ -22,13 +22,21 @@ </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"/> <xs:attribute type="xs:boolean" name="removeBackend" use="optional" default="true"/> - <xs:attribute type="xs:string" name="method"/> + <xs:attribute type="operationMethodEnum" name="method"/> <xs:attribute type="xs:string" name="successRegex"/> <xs:attribute type="xs:string" name="returnRegex"/> + <xs:attribute type="xs:string" name="returnIndex" use="optional"/> <xs:attribute type="xs:string" name="filename"/> </xs:complexType> </xs:element> @@ -93,4 +101,12 @@ <xs:enumeration value="anonymous" /> </xs:restriction> </xs:simpleType> -</xs:schema> \ No newline at end of file + <xs:simpleType name="operationMethodEnum" final="restriction"> + <xs:restriction base="xs:string"> + <xs:enumeration value="GET" /> + <xs:enumeration value="PUT" /> + <xs:enumeration value="POST" /> + <xs:enumeration value="DELETE" /> + </xs:restriction> + </xs:simpleType> +</xs:schema> diff --git a/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd b/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd index aec157309..06c64a8eb 100644 --- a/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd +++ b/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd @@ -1,9 +1,9 @@ <?xml version="1.0" encoding="UTF-8"?> <!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ +/** + * Copyright 2017 Adobe + * All Rights Reserved. + */ --> <xs:schema attributeFormDefault="unqualified" elementFormDefault="qualified" @@ -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 64% rename from src/Magento/FunctionalTestingFramework/DataGenerator/Persist/Curl/AdminExecutor.php rename to src/Magento/FunctionalTestingFramework/DataTransport/AdminFormExecutor.php index cff7c04fb..ab7213c35 100644 --- a/src/Magento/FunctionalTestingFramework/DataGenerator/Persist/Curl/AdminExecutor.php +++ b/src/Magento/FunctionalTestingFramework/DataTransport/AdminFormExecutor.php @@ -1,19 +1,23 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2020 Adobe + * All Rights Reserved. */ -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\DataGenerator\Handlers\CredentialStore; +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. @@ -37,18 +41,11 @@ class AdminExecutor extends AbstractExecutor implements CurlInterface private $response; /** - * Should executor remove backend_name from api url + * Flag describes whether the request is to Magento Base URL, removes backend_name from api url * @var boolean */ private $removeBackend; - /** - * Backend url. - * - * @var string - */ - private static $adminUrl; - /** * Constructor. * @param boolean $removeBackend @@ -58,10 +55,6 @@ class AdminExecutor extends AbstractExecutor implements CurlInterface */ public function __construct($removeBackend) { - if (!isset(parent::$baseUrl)) { - parent::resolveBaseUrl(); - } - self::$adminUrl = parent::$baseUrl . getenv('MAGENTO_BACKEND_NAME') . '/'; $this->removeBackend = $removeBackend; $this->transport = new CurlTransport(); $this->authorize(); @@ -76,21 +69,39 @@ public function __construct($removeBackend) private function authorize() { // Perform GET to backend url so form_key is set - $this->transport->write(self::$adminUrl, [], CurlInterface::GET); + $this->transport->write(MftfGlobals::getBackendBaseUrl(), [], CurlInterface::GET); $this->read(); // Authenticate admin user - $authUrl = self::$adminUrl . 'admin/auth/login/'; + $authUrl = MftfGlobals::getBackendBaseUrl() . 'admin/auth/login/'; + $encryptedSecret = CredentialStore::getInstance()->getSecret('magento/MAGENTO_ADMIN_PASSWORD'); + $secret = CredentialStore::getInstance()->decryptSecretValue($encryptedSecret); $data = [ 'login[username]' => getenv('MAGENTO_ADMIN_USERNAME'), - 'login[password]' => getenv('MAGENTO_ADMIN_PASSWORD'), + 'login[password]' => $secret, 'form_key' => $this->formKey, ]; $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!'); + } + } } /** @@ -119,10 +130,11 @@ private function setFormKey() public function write($url, $data = [], $method = CurlInterface::POST, $headers = []) { $url = ltrim($url, "/"); - $apiUrl = self::$adminUrl . $url; + $apiUrl = MftfGlobals::getBackendBaseUrl() . $url; if ($this->removeBackend) { - $apiUrl = parent::$baseUrl . $url; + //TODO + //Cannot find usage. Do we need this? } if ($this->formKey) { @@ -139,12 +151,13 @@ public function write($url, $data = [], $method = CurlInterface::POST, $headers /** * Read response from server. * - * @param string $successRegex - * @param string $returnRegex + * @param string $successRegex + * @param string $returnRegex + * @param string|null $returnIndex * @return string|array * @throws TestFrameworkException */ - public function read($successRegex = null, $returnRegex = null) + public function read(?string $successRegex = null, ?string $returnRegex = null, ?string $returnIndex = null) { $this->response = $this->transport->read(); $this->setFormKey(); @@ -159,7 +172,7 @@ public function read($successRegex = null, $returnRegex = null) if (!empty($returnRegex)) { preg_match($returnRegex, $this->response, $returnMatches); if (!empty($returnMatches)) { - return $returnMatches; + return $returnMatches[$returnIndex] ?? $returnMatches[0]; } } return $this->response; diff --git a/src/Magento/FunctionalTestingFramework/DataTransport/Auth/Tfa.php b/src/Magento/FunctionalTestingFramework/DataTransport/Auth/Tfa.php new file mode 100644 index 000000000..28f5ef2cb --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/DataTransport/Auth/Tfa.php @@ -0,0 +1,108 @@ +<?php +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ + +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/Clock.php b/src/Magento/FunctionalTestingFramework/DataTransport/Auth/Tfa/Clock.php new file mode 100644 index 000000000..bb0d17cd1 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/DataTransport/Auth/Tfa/Clock.php @@ -0,0 +1,24 @@ +<?php +/** + * Copyright 2025 Adobe + * All Rights Reserved. + */ +declare(strict_types=1); + +namespace Magento\FunctionalTestingFramework\DataTransport\Auth\Tfa; + +use DateTimeImmutable; +use Psr\Clock\ClockInterface; + +class Clock implements ClockInterface +{ + /** + * Return DateTimeImmutable class object + * + * @return DateTimeImmutable + */ + public function now(): DateTimeImmutable + { + return new DateTimeImmutable(); + } +} 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..94e8f2a4b --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/DataTransport/Auth/Tfa/OTP.php @@ -0,0 +1,73 @@ +<?php +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ + +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(?string $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, + TOTP::DEFAULT_PERIOD, + TOTP::DEFAULT_DIGEST, + TOTP::DEFAULT_DIGITS, + TOTP::DEFAULT_EPOCH, + new Clock() + ); + 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..2dd296cad --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/DataTransport/Auth/WebApiAuth.php @@ -0,0 +1,154 @@ +<?php +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ + +namespace Magento\FunctionalTestingFramework\DataTransport\Auth; + +use Magento\FunctionalTestingFramework\DataGenerator\Handlers\CredentialStore; +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) + * @SuppressWarnings(PHPMD.NPathComplexity) + */ + public static function getAdminToken(?string $username = null, ?string $password = null) + { + $login = $username ?? getenv('MAGENTO_ADMIN_USERNAME'); + try { + $encryptedSecret = CredentialStore::getInstance()->getSecret('magento/MAGENTO_ADMIN_PASSWORD'); + $secret = CredentialStore::getInstance()->decryptSecretValue($encryptedSecret); + $password = $password ?? $secret; + } catch (TestFrameworkException $e) { + $message = "Password not found in credentials file"; + throw new FastFailException($message . $e->getMessage(), $e->getContext()); + } + 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' => $secret, + ]; + 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 82% rename from src/Magento/FunctionalTestingFramework/DataGenerator/Persist/Curl/FrontendExecutor.php rename to src/Magento/FunctionalTestingFramework/DataTransport/FrontendFormExecutor.php index aa1706ef3..905562d71 100644 --- a/src/Magento/FunctionalTestingFramework/DataGenerator/Persist/Curl/FrontendExecutor.php +++ b/src/Magento/FunctionalTestingFramework/DataTransport/FrontendFormExecutor.php @@ -1,19 +1,20 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2020 Adobe + * All Rights Reserved. */ -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 @@ -67,9 +68,6 @@ class FrontendExecutor extends AbstractExecutor implements CurlInterface */ public function __construct($customerEmail, $customerPassWord) { - if (!isset(parent::$baseUrl)) { - parent::resolveBaseUrl(); - } $this->transport = new CurlTransport(); $this->customerEmail = $customerEmail; $this->customerPassword = $customerPassWord; @@ -84,11 +82,11 @@ public function __construct($customerEmail, $customerPassWord) */ private function authorize() { - $url = parent::$baseUrl . 'customer/account/login/'; - $this->transport->write($url); + $url = MftfGlobals::getBaseUrl() . 'customer/account/login/'; + $this->transport->write($url, [], CurlInterface::GET); $this->read(); - $url = parent::$baseUrl . 'customer/account/loginPost/'; + $url = MftfGlobals::getBaseUrl() . 'customer/account/loginPost/'; $data = [ 'login[username]' => $this->customerEmail, 'login[password]' => $this->customerPassword, @@ -146,7 +144,7 @@ public function write($url, $data = [], $method = CurlInterface::POST, $headers if (isset($data['customer_password'])) { unset($data['customer_password']); } - $apiUrl = parent::$baseUrl . $url; + $apiUrl = MftfGlobals::getBaseUrl() . $url; if ($this->formKey) { $data['form_key'] = $this->formKey; } else { @@ -161,12 +159,13 @@ public function write($url, $data = [], $method = CurlInterface::POST, $headers /** * Read response from server. * - * @param string $successRegex - * @param string $returnRegex + * @param string $successRegex + * @param string $returnRegex + * @param string|null $returnIndex * @return string|array * @throws TestFrameworkException */ - public function read($successRegex = null, $returnRegex = null) + public function read(?string $successRegex = null, ?string $returnRegex = null, ?string $returnIndex = null) { $this->response = $this->transport->read(); $this->setCookies(); @@ -182,7 +181,7 @@ public function read($successRegex = null, $returnRegex = null) if (!empty($returnRegex)) { preg_match($returnRegex, $this->response, $returnMatches); if (!empty($returnMatches)) { - return $returnMatches; + return $returnMatches[$returnIndex] ?? $returnMatches[0]; } } return $this->response; diff --git a/src/Magento/FunctionalTestingFramework/Util/Protocol/CurlInterface.php b/src/Magento/FunctionalTestingFramework/DataTransport/Protocol/CurlInterface.php similarity index 73% rename from src/Magento/FunctionalTestingFramework/Util/Protocol/CurlInterface.php rename to src/Magento/FunctionalTestingFramework/DataTransport/Protocol/CurlInterface.php index 171fd01e6..f69d3aa28 100644 --- a/src/Magento/FunctionalTestingFramework/Util/Protocol/CurlInterface.php +++ b/src/Magento/FunctionalTestingFramework/DataTransport/Protocol/CurlInterface.php @@ -1,10 +1,10 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2020 Adobe + * All Rights Reserved. */ -namespace Magento\FunctionalTestingFramework\Util\Protocol; +namespace Magento\FunctionalTestingFramework\DataTransport\Protocol; /** * Curl protocol interface. @@ -42,11 +42,12 @@ public function write($url, $body = [], $method = CurlInterface::POST, $headers /** * Read response from server. * - * @param string $successRegex - * @param string $returnRegex + * @param string $successRegex + * @param string $returnRegex + * @param string|null $returnIndex * @return string|array */ - public function read($successRegex = null, $returnRegex = null); + public function read(?string $successRegex = null, ?string $returnRegex = null, ?string $returnIndex = null); /** * Close the connection to the server. diff --git a/src/Magento/FunctionalTestingFramework/Util/Protocol/CurlTransport.php b/src/Magento/FunctionalTestingFramework/DataTransport/Protocol/CurlTransport.php similarity index 86% rename from src/Magento/FunctionalTestingFramework/Util/Protocol/CurlTransport.php rename to src/Magento/FunctionalTestingFramework/DataTransport/Protocol/CurlTransport.php index 5a9133a05..5bc097fdf 100644 --- a/src/Magento/FunctionalTestingFramework/Util/Protocol/CurlTransport.php +++ b/src/Magento/FunctionalTestingFramework/DataTransport/Protocol/CurlTransport.php @@ -1,10 +1,10 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2020 Adobe + * All Rights Reserved. */ -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; @@ -161,12 +161,13 @@ public function write($url, $body = [], $method = CurlInterface::POST, $headers /** * Read response from server. * - * @param string $successRegex - * @param string $returnRegex + * @param string $successRegex + * @param string $returnRegex + * @param string|null $returnIndex * @return string * @throws TestFrameworkException */ - public function read($successRegex = null, $returnRegex = null) + public function read(?string $successRegex = null, ?string $returnRegex = null, ?string $returnIndex = null) { $response = curl_exec($this->getResource()); @@ -188,7 +189,10 @@ public function read($successRegex = null, $returnRegex = null) */ 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; } @@ -270,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 54% rename from src/Magento/FunctionalTestingFramework/DataGenerator/Persist/Curl/WebapiExecutor.php rename to src/Magento/FunctionalTestingFramework/DataTransport/WebApiExecutor.php index 16fef75f2..be0c29487 100644 --- a/src/Magento/FunctionalTestingFramework/DataGenerator/Persist/Curl/WebapiExecutor.php +++ b/src/Magento/FunctionalTestingFramework/DataTransport/WebApiExecutor.php @@ -1,31 +1,34 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2020 Adobe + * All Rights Reserved. */ -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,58 +36,35 @@ 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; /** - * WebapiExecutor Constructor. + * WebApiExecutor Constructor * * @param string $storeCode - * @throws TestFrameworkException + * @throws FastFailException */ - public function __construct($storeCode = null) + public function __construct(?string $storeCode = null) { - if (!isset(parent::$baseUrl)) { - parent::resolveBaseUrl(); - } - $this->storeCode = $storeCode; $this->transport = new CurlTransport(); $this->authorize(); } /** - * Returns the authorization token needed for some requests via REST call. + * 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->headers = array_merge( - ['Authorization: Bearer ' . str_replace('"', "", $this->read())], + ['Authorization: Bearer ' . WebApiAuth::getAdminToken()], $this->headers ); } @@ -112,15 +92,15 @@ public function write($url, $data = [], $method = CurlInterface::POST, $headers /** * Read response from server. * - * @param string $successRegex - * @param string $returnRegex + * @param string $successRegex + * @param string $returnRegex + * @param string|null $returnIndex * @return string * @throws TestFrameworkException */ - public function read($successRegex = null, $returnRegex = null) + public function read(?string $successRegex = null, ?string $returnRegex = null, ?string $returnIndex = null) { - $this->response = $this->transport->read(); - return $this->response; + return $this->transport->read(); } /** @@ -146,17 +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 = parent::$baseUrl . '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; } } diff --git a/src/Magento/FunctionalTestingFramework/DataGenerator/Persist/Curl/WebapiNoAuthExecutor.php b/src/Magento/FunctionalTestingFramework/DataTransport/WebApiNoAuthExecutor.php similarity index 55% rename from src/Magento/FunctionalTestingFramework/DataGenerator/Persist/Curl/WebapiNoAuthExecutor.php rename to src/Magento/FunctionalTestingFramework/DataTransport/WebApiNoAuthExecutor.php index c54bc60b9..0d31b3c1f 100644 --- a/src/Magento/FunctionalTestingFramework/DataGenerator/Persist/Curl/WebapiNoAuthExecutor.php +++ b/src/Magento/FunctionalTestingFramework/DataTransport/WebApiNoAuthExecutor.php @@ -1,15 +1,15 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2020 Adobe + * All Rights Reserved. */ -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 4b67add0b..6b3e2baf1 100644 --- a/src/Magento/FunctionalTestingFramework/Exceptions/Collector/ExceptionCollector.php +++ b/src/Magento/FunctionalTestingFramework/Exceptions/Collector/ExceptionCollector.php @@ -1,8 +1,9 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2018 Adobe + * All Rights Reserved. */ + namespace Magento\FunctionalTestingFramework\Exceptions\Collector; class ExceptionCollector @@ -43,6 +44,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. @@ -53,7 +74,7 @@ public function throwException() private function formatErrors($errors) { $flattenedErrors = []; - foreach ($errors as $key => $errorMsg) { + foreach ($errors as $errorMsg) { if (is_array($errorMsg)) { $flattenedErrors = array_merge($flattenedErrors, $this->formatErrors($errorMsg)); continue; diff --git a/src/Magento/FunctionalTestingFramework/Exceptions/FastFailException.php b/src/Magento/FunctionalTestingFramework/Exceptions/FastFailException.php new file mode 100644 index 000000000..e6b6e0b45 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Exceptions/FastFailException.php @@ -0,0 +1,54 @@ +<?php +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ + +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..8d4e3f07f 100644 --- a/src/Magento/FunctionalTestingFramework/Exceptions/TestFrameworkException.php +++ b/src/Magento/FunctionalTestingFramework/Exceptions/TestFrameworkException.php @@ -1,7 +1,7 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2018 Adobe + * All Rights Reserved. */ namespace Magento\FunctionalTestingFramework\Exceptions; @@ -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/Exceptions/TestReferenceException.php b/src/Magento/FunctionalTestingFramework/Exceptions/TestReferenceException.php index 0546c4a22..dd6b07847 100644 --- a/src/Magento/FunctionalTestingFramework/Exceptions/TestReferenceException.php +++ b/src/Magento/FunctionalTestingFramework/Exceptions/TestReferenceException.php @@ -1,7 +1,7 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ namespace Magento\FunctionalTestingFramework\Exceptions; diff --git a/src/Magento/FunctionalTestingFramework/Exceptions/XmlException.php b/src/Magento/FunctionalTestingFramework/Exceptions/XmlException.php index 929ffd937..5ea572a9d 100644 --- a/src/Magento/FunctionalTestingFramework/Exceptions/XmlException.php +++ b/src/Magento/FunctionalTestingFramework/Exceptions/XmlException.php @@ -1,7 +1,7 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ namespace Magento\FunctionalTestingFramework\Exceptions; diff --git a/src/Magento/FunctionalTestingFramework/Extension/BaseExtension.php b/src/Magento/FunctionalTestingFramework/Extension/BaseExtension.php new file mode 100644 index 000000000..4d2e8e301 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Extension/BaseExtension.php @@ -0,0 +1,107 @@ +<?php +/** + * Copyright 2018 Adobe + * All Rights Reserved. + */ + +namespace Magento\FunctionalTestingFramework\Extension; + +use Codeception\Events; +use Codeception\Exception\ModuleRequireException; +use Codeception\Extension; +use Codeception\Module\WebDriver; + +/** + * Class BaseExtension + */ +class BaseExtension extends Extension +{ + /** + * Codeception Events Mapping to methods + * + * @var array + */ + public static $events = [ + Events::TEST_BEFORE => 'beforeTest', + Events::STEP_BEFORE => 'beforeStep' + ]; + + /** + * The current URI of the active page + * + * @var string + */ + private $uri; + + /** + * Codeception event listener function - initialize uri before test + * + * @param \Codeception\Event\TestEvent $e + * @return void + * @throws \Exception + * + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function beforeTest(\Codeception\Event\TestEvent $e) + { + $this->uri = null; + } + + /** + * Codeception event listener function - check for page uri change before step + * + * @param \Codeception\Event\StepEvent $e + * @return void + * @throws \Exception + * + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function beforeStep(\Codeception\Event\StepEvent $e) + { + $this->pageChanged(); + } + + /** + * WebDriver instance for execution + * + * @return WebDriver + * @throws ModuleRequireException + */ + public function getDriver() + { + return $this->getModule($this->config['driver']); + } + + /** + * Gets the active page URI from the start of the most recent step + * + * @return string + */ + public function getUri() + { + return $this->uri; + } + + /** + * Check if page uri has changed + * + * @return boolean + */ + protected function pageChanged() + { + try { + if ($this->getDriver() === null) { + return false; + } + $currentUri = $this->getDriver()->_getCurrentUri(); + + if ($this->uri !== $currentUri) { + $this->uri = $currentUri; + return true; + } + } catch (\Exception $e) { + // just fall through and return false + } + return false; + } +} diff --git a/src/Magento/FunctionalTestingFramework/Extension/BrowserLogUtil.php b/src/Magento/FunctionalTestingFramework/Extension/BrowserLogUtil.php new file mode 100644 index 000000000..c890bbea1 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Extension/BrowserLogUtil.php @@ -0,0 +1,84 @@ +<?php +/** + * Copyright 2019 Adobe + * All Rights Reserved. + */ + +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 b88482130..000000000 --- a/src/Magento/FunctionalTestingFramework/Extension/ErrorLogger.php +++ /dev/null @@ -1,73 +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 \Facebook\WebDriver\Remote\RemoteWebDriver $webDriver - * @param \Codeception\Event\StepEvent $stepEvent - * @return void - */ - public function logErrors($webDriver, $stepEvent) - { - //Types available should be "server", "browser", "driver". Only care about browser at the moment. - if (in_array("browser", $webDriver->manage()->getAvailableLogTypes())) { - $browserLogEntries = $webDriver->manage()->getLog("browser"); - foreach ($browserLogEntries as $entry) { - if (array_key_exists("source", $entry) && $entry["source"] === "javascript") { - $this->logError("javascript", $stepEvent, $entry); - } - } - } - } - - /** - * 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 709647096..000000000 --- a/src/Magento/FunctionalTestingFramework/Extension/PageReadinessExtension.php +++ /dev/null @@ -1,277 +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\Events; -use Codeception\Exception\ModuleRequireException; -use Codeception\Extension; -use Codeception\Module\WebDriver; -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 Extension -{ - /** - * Codeception Events Mapping to methods - * - * @var array - */ - public static $events = [ - Events::TEST_BEFORE => 'beforeTest', - Events::STEP_BEFORE => 'beforeStep' - ]; - - /** - * 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; - - /** - * The current URI of the active page - * - * @var string - */ - private $uri; - - /** - * Initialize local vars - * - * @return void - * @throws \Exception - */ - public function _initialize() - { - $this->logger = LoggingUtil::getInstance()->getLogger(get_class($this)); - $this->verbose = MftfApplicationConfig::getConfig()->verboseEnabled(); - } - - /** - * WebDriver instance to use to execute readiness metric checks - * - * @return WebDriver - * @throws ModuleRequireException - */ - public function getDriver() - { - return $this->getModule($this->config['driver']); - } - - /** - * Initialize the readiness metrics for the test - * - * @param \Codeception\Event\TestEvent $e - * @return void - */ - public function beforeTest(TestEvent $e) - { - if (isset($this->config['resetFailureThreshold'])) { - $failThreshold = intval($this->config['resetFailureThreshold']); - } else { - $failThreshold = 3; - } - - $this->testName = $e->getTest()->getMetadata()->getName(); - $this->uri = null; - - $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->checkForNewPage($step); - - // 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']; - } - - $metrics = $this->readinessMetrics; - - 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) { - } - - /** @var AbstractMetricCheck $metric */ - foreach ($metrics as $metric) { - $metric->finalizeForStep($step); - } - } - - /** - * Check if the URI has changed and reset metric tracking if so - * - * @param Step $step - * @return void - */ - private function checkForNewPage($step) - { - try { - $currentUri = $this->getDriver()->_getCurrentUri(); - - if ($this->uri !== $currentUri) { - $this->logDebug( - 'Page URI changed; resetting readiness metric failure tracking', - [ - 'step' => $step->__toString(), - 'newUri' => $currentUri - ] - ); - - /** @var AbstractMetricCheck $metric */ - foreach ($this->readinessMetrics as $metric) { - $metric->resetTracker(); - } - - $this->uri = $currentUri; - } - } catch (\Exception $e) { - $this->logDebug('Could not retrieve current URI', ['step' => $step->__toString()]); - } - } - - /** - * Gets the active page URI from the start of the most recent step - * - * @return string - */ - public function getUri() - { - return $this->uri; - } - - /** - * 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->uri - ]; - 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 417ae336f..000000000 --- a/src/Magento/FunctionalTestingFramework/Extension/ReadinessMetrics/AbstractMetricCheck.php +++ /dev/null @@ -1,365 +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 UnexpectedAlertOpenException - * @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 07f177dc3..90b2402bb 100644 --- a/src/Magento/FunctionalTestingFramework/Extension/TestContextExtension.php +++ b/src/Magento/FunctionalTestingFramework/Extension/TestContextExtension.php @@ -1,121 +1,294 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2018 Adobe + * All Rights Reserved. */ namespace Magento\FunctionalTestingFramework\Extension; -use \Codeception\Events; +use Codeception\Events; +use Codeception\Step; +use Codeception\Test\Test; +use Magento\FunctionalTestingFramework\Allure\AllureHelper; use Magento\FunctionalTestingFramework\DataGenerator\Handlers\PersistedObjectHandler; -use Magento\FunctionalTestingFramework\Extension\ErrorLogger; -use Magento\FunctionalTestingFramework\Module\MagentoWebDriver; +use Magento\FunctionalTestingFramework\Suite\Handlers\SuiteObjectHandler; +use Magento\FunctionalTestingFramework\Util\Logger\LoggingUtil; +use Qameta\Allure\Allure; +use Qameta\Allure\AllureLifecycleInterface; +use Qameta\Allure\Model\StepResult; +use Magento\FunctionalTestingFramework\Test\Objects\ActionObject; +use Magento\FunctionalTestingFramework\Test\Objects\ActionGroupObject; +use Magento\FunctionalTestingFramework\Util\TestGenerator; +use Qameta\Allure\Model\TestResult; +use Qameta\Allure\Model\Status; +use Magento\FunctionalTestingFramework\Codeception\Subscriber\Console; /** * Class TestContextExtension * @SuppressWarnings(PHPMD.UnusedPrivateField) + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ -class TestContextExtension extends \Codeception\Extension +class TestContextExtension extends BaseExtension { + private const STEP_PASSED = "passed"; + + /** + * Test files cache. + * + * @var array + */ + private $testFiles = []; + + /** + * Action group step key. + * + * @var null|string + */ + private $actionGroupStepKey = null; + + /** + * Boolean value to indicate if steps are invisible steps + * + * @var boolean + */ + private $atInvisibleSteps = false; const TEST_PHASE_AFTER = "_after"; - // @codingStandardsIgnoreStart - const MAGENTO_WEB_DRIVER_CLASS = "\Magento\FunctionalTestingFramework\Module\MagentoWebDriver"; - // @codingStandardsIgnoreEnd + 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 * @var array */ - public static $events = [ - Events::TEST_START => 'testStart', - Events::TEST_FAIL => 'testFail', - Events::STEP_AFTER => 'afterStep', - Events::TEST_END => 'testEnd' - ]; + public static $events; /** - * Codeception event listener function, triggered on test start. - * @throws \Exception + * The name of the currently running test + * @var string + */ + public $currentTest; + + /** + * Initialize local vars + * * @return void + * @throws \Exception */ - public function testStart() + public function _initialize(): void { - PersistedObjectHandler::getInstance()->clearHookObjects(); - PersistedObjectHandler::getInstance()->clearTestObjects(); + $events = [ + Events::TEST_START => 'testStart', + Events::STEP_AFTER => 'afterStep', + Events::TEST_END => 'testEnd', + Events::RESULT_PRINT_AFTER => 'saveFailed' + ]; + self::$events = array_merge(parent::$events, $events); + parent::_initialize(); } /** - * Codeception event listener function, triggered on test failure. - * @param \Codeception\Event\FailEvent $e + * Codeception event listener function, triggered on test start. + * @throws \Exception * @return void */ - public function testFail(\Codeception\Event\FailEvent $e) + public function testStart(\Codeception\Event\TestEvent $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); + 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 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 */ 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(); + return $cest->getResultAggregator(); }, $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->getTest()->getTestMethod() === $cest->getTestMethod()) { + $this->attachExceptionToAllure($error->getFail(), $cest->getTestMethod()); + } + } + } + + // check for failures in all test hooks and attach in allure + if (!empty($testResultObject->failures())) { + foreach ($testResultObject->failures() as $failure) { + if ($failure->getTest()->getTestMethod() === $cest->getTestMethod()) { + $this->attachExceptionToAllure($failure->getFail(), $cest->getTestMethod()); } } } // Reset Session and Cookies after all Test Runs, workaround due to functional.suite.yml restart: true - $this->getModule(self::MAGENTO_WEB_DRIVER_CLASS)->_runAfter($e->getTest()); + $this->getDriver()->_runAfter($e->getTest()); + + $lifecycle = Allure::getLifecycle(); + $lifecycle->updateTest( + function (TestResult $testResult) { + $this->getFormattedSteps($testResult); + } + ); + + $this->addTestsInSuites($lifecycle, $cest); } /** - * Runs cest's after block, if necessary. - * @param Symfony\Component\EventDispatcher\Event $e - * @param \Codeception\TestInterface $cest + * Function to add test under the suites. + * + * @param object $lifecycle + * @param object $cest + * * @return void */ - private function runAfterBlock($e, $cest) + private function addTestsInSuites($lifecycle, $cest): void { - try { - $actorClass = $e->getTest()->getMetadata()->getCurrent('actor'); - $I = new $actorClass($cest->getScenario()); + $groupName = null; + if ($this->options['groups'] !== null) { + $group = $this->options['groups'][0]; + $groupName = $this->sanitizeGroupName($group); + } + $lifecycle->updateTest( + function (TestResult $testResult) use ($groupName, $cest) { + $labels = $testResult->getLabels(); + foreach ($labels as $label) { + if ($groupName !== null && $label->getName() === "parentSuite") { + $label->setValue(sprintf('%s\%s', $label->getValue(), $groupName)); + } + if ($label->getName() === "package") { + $className = $cest->getReportFields()['class']; + $className = preg_replace('{_[0-9]*_G}', '', $className); + $label->setValue($className); + } + } + } + ); + } + + /** + * Function which santizes any group names changed by the framework for execution in order to consolidate reporting. + * + * @param string $group + * @return string + */ + private function sanitizeGroupName($group): string + { + $suiteNames = array_keys(SuiteObjectHandler::getInstance()->getAllObjects()); + $exactMatch = in_array($group, $suiteNames); + + // if this is an existing suite name we dont' need to worry about changing it + if ($exactMatch || strpos($group, "_") === false) { + return $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 + $originalName = in_array($originalName, $suiteNames) ? $originalName : $group; + + return $originalName; + } + + /** + * @param TestResult $testResult + * @return void + * + * @SuppressWarnings(PHPMD.CyclomaticComplexity) + * @SuppressWarnings(PHPMD.NPathComplexity) + * Revisited to reduce cyclomatic complexity, left unrefactored for readability + */ + private function getFormattedSteps(TestResult $testResult): void + { + $steps = $testResult->getSteps(); + $formattedSteps = []; + $actionGroupKey = null; + foreach ($steps as $key => $step) { + if (str_contains($step->getName(), 'start before hook') + || str_contains($step->getName(), 'end before hook') + || str_contains($step->getName(), 'start after hook') + || str_contains($step->getName(), 'end after hook') + ) { + $step->setName(strtoupper($step->getName())); + } + // Remove all parameters from step because parameters already added in formatted step call_user_func(\Closure::bind( - function () use ($cest, $I) { - $cest->executeHook($I, 'after'); + function () use ($step) { + $step->parameters = []; }, null, - $cest + $step )); - } catch (\Exception $e) { - // Do not rethrow Exception + if (strpos($step->getName(), ActionGroupObject::ACTION_GROUP_CONTEXT_START) !== false) { + $step->setName(str_replace(ActionGroupObject::ACTION_GROUP_CONTEXT_START, '', $step->getName())); + $actionGroupKey = $key; + $formattedSteps[$actionGroupKey] = $step; + continue; + } + if (stripos($step->getName(), ActionGroupObject::ACTION_GROUP_CONTEXT_END) !== false) { + $actionGroupKey = null; + continue; + } + if ($actionGroupKey !== null) { + if ($step->getName() !== null) { + $formattedSteps[$actionGroupKey]->addSteps($step); + if ($step->getStatus()->jsonSerialize() !== self::STEP_PASSED) { + $formattedSteps[$actionGroupKey]->setStatus($step->getStatus()); + $actionGroupKey = null; + } + } + } else { + if ($step->getName() !== null) { + $formattedSteps[$key] = $step; + } + } } + /** @var StepResult[] $formattedSteps*/ + $formattedSteps = array_values($formattedSteps); + + // No public function for setting the testResult steps + call_user_func(\Closure::bind( + function () use ($testResult, $formattedSteps) { + $testResult->steps = $formattedSteps; + }, + null, + $testResult + )); } /** @@ -128,24 +301,244 @@ 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'); + + $previousException = null; + if ($exception instanceof \PHPUnit\Framework\ExceptionWrapper) { + $previousException = $exception->getPreviousWrapped(); + } elseif ($exception instanceof \Throwable) { + $previousException = $exception->getPrevious(); + } + + if ($previousException !== null) { + $this->attachExceptionToAllure($previousException, $testMethod); + } + } + + /** + * Codeception event listener function, triggered before step. + * Check if it's a new page. + * + * @param \Codeception\Event\StepEvent $e + * @return void + * @throws \Exception + */ + public function beforeStep(\Codeception\Event\StepEvent $e) + { + + if ($this->pageChanged($e->getStep())) { + $this->getDriver()->cleanJsError(); + } + } + + /** + * @param \Codeception\Event\StepEvent $e + * @return string|void + * + * @SuppressWarnings(PHPMD.CyclomaticComplexity) + * @SuppressWarnings(PHPMD.NPathComplexity) + * Revisited to reduce cyclomatic complexity, left unrefactored for readability + */ + public function stepName(\Codeception\Event\StepEvent $e) + { + $stepAction = $e->getStep()->getAction(); + 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; + if (!($e->getStep() instanceof Comment)) { + $stepKey = $this->retrieveStepKeyForAllure($e->getStep(), $e->getTest()->getMetadata()->getFilename()); + $isActionGroup = ( + strpos( + $e->getStep()->__toString(), + ActionGroupObject::ACTION_GROUP_CONTEXT_START + ) !== false + ); + if ($isActionGroup) { + preg_match(TestGenerator::ACTION_GROUP_STEP_KEY_REGEX, $e->getStep()->__toString(), $matches); + if (!empty($matches['actionGroupStepKey'])) { + $this->actionGroupStepKey = ucfirst($matches['actionGroupStepKey']); + } + } + } + // DO NOT alter action if actionGroup is starting, need the exact actionGroup name for good logging + if (strpos($stepAction, ActionGroupObject::ACTION_GROUP_CONTEXT_START) === false) { + $stepAction = $e->getStep()->getHumanizedActionWithoutArguments(); + } + $stepArgs = $e->getStep()->getArgumentsAsString($argumentsLength); + $stepName = ''; + + if (isset($stepName)) { + $stepName .= '[' . $stepKey . '] '; + if (empty($stepKey)) { + $stepName = ""; + } + } + $stepName .= $stepAction . ' ' . $stepArgs; + // Strip control characters so that report generation does not fail + $stepName = preg_replace('/[[:cntrl:]]/', '', $stepName); + if (stripos($stepName, "\mftf\helper")) { + preg_match("/\[(.*?)\]/", $stepName, $matches); + $stepKeyData = preg_split('/\s+/', ucwords($matches[1])); + if (count($stepKeyData) > 0) { + $this->actionGroupStepKey = (isset($this->actionGroupStepKey)) + ?$this->actionGroupStepKey + : ""; + $stepKeyHelper = str_replace($this->actionGroupStepKey, '', lcfirst(implode("", $stepKeyData))); + $stepName= '['.$stepKeyHelper.'] '.preg_replace('#\[.*\]#', '', $stepName); + } + } + return ucfirst($stepName); + } + /** * Codeception event listener function, triggered after step. * Calls ErrorLogger to log JS errors encountered. * @param \Codeception\Event\StepEvent $e * @return void + * @throws \Exception */ public function afterStep(\Codeception\Event\StepEvent $e) { - // @codingStandardsIgnoreStart - $webDriver = $this->getModule("\Magento\FunctionalTestingFramework\Module\MagentoWebDriver")->webDriver; - // @codingStandardsIgnoreEnd - ErrorLogger::getInstance()->logErrors($webDriver, $e); + $lifecycle = Allure::getLifecycle(); + $stepName = $this->stepName($e); + $lifecycle->updateStep( + function (StepResult $step) use ($stepName) { + $step->setName($stepName); + } + ); + $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); + } + + /** + * Saves failed tests from last codecept run command into a file in _output directory + * Removes file if there were no failures in last run command + * @param \Codeception\Event\PrintResultEvent $e + * @return void + */ + public function saveFailed(\Codeception\Event\PrintResultEvent $e) + { + $file = $this->getLogDir() . self::TEST_FAILED_FILE; + $result = $e->getResult(); + $output = []; + + // Remove previous file regardless if we're writing a new file + if (is_file($file)) { + unlink($file); + } + + foreach ($result->failures() as $fail) { + $output[] = $this->localizePath(\Codeception\Test\Descriptor::getTestFullName($fail->getTest())); + } + foreach ($result->errors() as $fail) { + $output[] = $this->localizePath(\Codeception\Test\Descriptor::getTestFullName($fail->getTest())); + } + foreach ($result->incomplete() as $fail) { + $output[] = $this->localizePath(\Codeception\Test\Descriptor::getTestFullName($fail->getTest())); + } + + if (empty($output)) { + return; + } + + file_put_contents($file, implode("\n", $output)); + } + + /** + * Returns localized path to string, for writing failed file. + * @param string $path + * @return string + */ + protected function localizePath($path) + { + $root = realpath($this->getRootDir()) . DIRECTORY_SEPARATOR; + if (substr($path, 0, strlen($root)) === $root) { + return substr($path, strlen($root)); + } + return $path; + } + + /** + * Reading stepKey from file. + * + * @param Step $step + * @param string $filePath + * @return string|null + */ + private function retrieveStepKeyForAllure(Step $step, string $filePath) + { + $stepKey = null; + $stepLine = $step->getLineNumber(); + $stepLine = $stepLine - 1; + + //If the step's filepath is different from the test, it's a comment action. + if ($this->getRootDir() . $step->getFilePath() != $filePath) { + return ""; + } + + if (!array_key_exists($filePath, $this->testFiles)) { + $this->testFiles[$filePath] = explode(PHP_EOL, file_get_contents($filePath)); + } + + preg_match(TestGenerator::ACTION_STEP_KEY_REGEX, $this->testFiles[$filePath][$stepLine], $matches); + if (!empty($matches['stepKey'])) { + $stepKey = $matches['stepKey']; + } + if ($this->actionGroupStepKey !== null) { + $stepKey = str_replace($this->actionGroupStepKey, '', $stepKey); + } + + $stepKey = $stepKey === '[]' ? null : $stepKey; + return $stepKey; } } diff --git a/src/Magento/FunctionalTestingFramework/Filter/FilterInterface.php b/src/Magento/FunctionalTestingFramework/Filter/FilterInterface.php new file mode 100644 index 000000000..1f3b88889 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Filter/FilterInterface.php @@ -0,0 +1,27 @@ +<?php +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ + +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..cef8a1dc9 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Filter/FilterList.php @@ -0,0 +1,57 @@ +<?php +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ + +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..21beca18e --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Filter/Test/ExcludeGroup.php @@ -0,0 +1,58 @@ +<?php +/** + * Copyright 2021 Adobe + * All Rights Reserved. + */ + +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..ff3052287 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Filter/Test/IncludeGroup.php @@ -0,0 +1,58 @@ +<?php +/** + * Copyright 2021 Adobe + * All Rights Reserved. + */ + +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..fb7d67f51 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Filter/Test/Severity.php @@ -0,0 +1,68 @@ +<?php +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ + +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..38a11a0a4 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Helper/Code/ClassReader.php @@ -0,0 +1,51 @@ +<?php +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ + +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..19646fed6 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Helper/Helper.php @@ -0,0 +1,15 @@ +<?php +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ + +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..ff484dc59 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Helper/HelperContainer.php @@ -0,0 +1,67 @@ +<?php +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ + +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..74575154d --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Module/MagentoActionProxies.php @@ -0,0 +1,141 @@ +<?php +/** + * Copyright 2019 Adobe + * All Rights Reserved. + */ + +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..c0b0eb143 100644 --- a/src/Magento/FunctionalTestingFramework/Module/MagentoAssert.php +++ b/src/Magento/FunctionalTestingFramework/Module/MagentoAssert.php @@ -1,7 +1,7 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2018 Adobe + * All Rights Reserved. */ namespace Magento\FunctionalTestingFramework\Module; @@ -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/MagentoRestDriver.php b/src/Magento/FunctionalTestingFramework/Module/MagentoRestDriver.php deleted file mode 100644 index 513d92dfb..000000000 --- a/src/Magento/FunctionalTestingFramework/Module/MagentoRestDriver.php +++ /dev/null @@ -1,673 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ - -namespace Magento\FunctionalTestingFramework\Module; - -use Codeception\Module\REST; -use Magento\FunctionalTestingFramework\Module\MagentoSequence; -use Magento\FunctionalTestingFramework\Util\ConfigSanitizerUtil; -use Flow\JSONPath; - -/** - * MagentoRestDriver module provides Magento REST WebService. - * - * This module can be used either with frameworks or PHPBrowser. - * If a framework module is connected, the testing will occur in the application directly. - * Otherwise, a PHPBrowser should be specified as a dependency to send requests and receive responses from a server. - * - * ## Configuration - * - * * url *optional* - the url of api - * - * This module requires PHPBrowser or any of Framework modules enabled. - * - * ### Example - * - * modules: - * enabled: - * - MagentoRestDriver: - * depends: PhpBrowser - * url: 'http://magento_base_url/rest/default/V1/' - * - * - * ## Public Properties - * - * * headers - array of headers going to be sent. - * * params - array of sent data - * * response - last response (string) - * - * ## Parts - * - * * Json - actions for validating Json responses (no Xml responses) - * * Xml - actions for validating XML responses (no Json responses) - * - * ## Conflicts - * - * Conflicts with SOAP module - * - */ -class MagentoRestDriver extends REST -{ - /** - * HTTP methods supported by REST. - */ - const HTTP_METHOD_GET = 'GET'; - const HTTP_METHOD_DELETE = 'DELETE'; - const HTTP_METHOD_PUT = 'PUT'; - const HTTP_METHOD_POST = 'POST'; - - /** - * Module required fields. - * - * @var array - */ - protected $requiredFields = [ - 'url', - 'username', - 'password' - ]; - - /** - * Module configurations. - * - * @var array - */ - protected $config = [ - 'url' => '', - 'username' => '', - 'password' => '' - ]; - - /** - * Admin tokens for Magento webapi access. - * - * @var array - */ - protected static $adminTokens = []; - - /** - * Before suite. - * - * @param array $settings - * @return void - */ - public function _beforeSuite($settings = []) - { - parent::_beforeSuite($settings); - if (empty($this->config['url']) || empty($this->config['username']) || empty($this->config['password'])) { - return; - } - - $this->config = ConfigSanitizerUtil::sanitizeWebDriverConfig($this->config, ['url']); - - $this->haveHttpHeader('Content-Type', 'application/json'); - $this->sendPOST( - 'integration/admin/token', - ['username' => $this->config['username'], 'password' => $this->config['password']] - ); - $token = substr($this->grabResponse(), 1, strlen($this->grabResponse())-2); - $this->seeResponseCodeIs(\Codeception\Util\HttpCode::OK); - $this->haveHttpHeader('Authorization', 'Bearer ' . $token); - self::$adminTokens[$this->config['username']] = $token; - // @codingStandardsIgnoreStart - $this->getModule('\Magento\FunctionalTestingFramework\Module\MagentoSequence')->_initialize(); - // @codingStandardsIgnoreEnd - } - - /** - * After suite. - * @return void - */ - public function _afterSuite() - { - parent::_afterSuite(); - $this->deleteHeader('Authorization'); - } - - /** - * Get admin auth token by username and password. - * - * @param string $username - * @param string $password - * @param boolean $newToken - * @return string - * @part json - * @part xml - */ - public function getAdminAuthToken($username = null, $password = null, $newToken = false) - { - $username = $username !== null ? $username : $this->config['username']; - $password = $password !== null ? $password : $this->config['password']; - - // Use existing token if it exists - if (!$newToken - && (isset(self::$adminTokens[$username]) || array_key_exists($username, self::$adminTokens))) { - return self::$adminTokens[$username]; - } - $this->haveHttpHeader('Content-Type', 'application/json'); - $this->sendPOST('integration/admin/token', ['username' => $username, 'password' => $password]); - $token = substr($this->grabResponse(), 1, strlen($this->grabResponse())-2); - $this->seeResponseCodeIs(\Codeception\Util\HttpCode::OK); - self::$adminTokens[$username] = $token; - return $token; - } - - /** - * Admin token authentication for a given user. - * - * @param string $username - * @param string $password - * @param boolean $newToken - * @part json - * @part xml - * @return void - */ - public function amAdminTokenAuthenticated($username = null, $password = null, $newToken = false) - { - $username = $username !== null ? $username : $this->config['username']; - $password = $password !== null ? $password : $this->config['password']; - - $this->haveHttpHeader('Content-Type', 'application/json'); - if ($newToken || !isset(self::$adminTokens[$username])) { - $this->sendPOST('integration/admin/token', ['username' => $username, 'password' => $password]); - $token = substr($this->grabResponse(), 1, strlen($this->grabResponse()) - 2); - $this->seeResponseCodeIs(\Codeception\Util\HttpCode::OK); - self::$adminTokens[$username] = $token; - } - $this->amBearerAuthenticated(self::$adminTokens[$username]); - } - - /** - * Send REST API request. - * - * @param string $endpoint - * @param string $httpMethod - * @param array $params - * @param string $grabByJsonPath - * @param boolean $decode - * @return mixed - * @throws \LogicException - * @part json - * @part xml - */ - public function sendRestRequest($endpoint, $httpMethod, $params = [], $grabByJsonPath = null, $decode = true) - { - $this->amAdminTokenAuthenticated(); - switch ($httpMethod) { - case self::HTTP_METHOD_GET: - $this->sendGET($endpoint, $params); - break; - case self::HTTP_METHOD_POST: - $this->sendPOST($endpoint, $params); - break; - case self::HTTP_METHOD_PUT: - $this->sendPUT($endpoint, $params); - break; - case self::HTTP_METHOD_DELETE: - $this->sendDELETE($endpoint, $params); - break; - default: - throw new \LogicException("HTTP method '{$httpMethod}' is not supported."); - } - $this->seeResponseCodeIs(\Codeception\Util\HttpCode::OK); - - if (!$decode && $grabByJsonPath === null) { - return $this->grabResponse(); - } elseif (!$decode) { - return $this->grabDataFromResponseByJsonPath($grabByJsonPath); - } else { - return \GuzzleHttp\json_decode($this->grabResponse()); - } - } - - /** - * Create a category in Magento. - * - * @param array $categoryData - * @return array|mixed - * @part json - * @part xml - */ - public function requireCategory($categoryData = []) - { - if (!$categoryData) { - $categoryData = $this->getCategoryApiData(); - } - $categoryData = $this->sendRestRequest( - self::$categoryEndpoint, - self::HTTP_METHOD_POST, - ['category' => $categoryData] - ); - return $categoryData; - } - - /** - * Create a simple product in Magento. - * - * @param integer $categoryId - * @param array $simpleProductData - * @return array|mixed - * @part json - * @part xml - */ - public function requireSimpleProduct($categoryId = 0, $simpleProductData = []) - { - if (!$simpleProductData) { - $simpleProductData = $this->getProductApiData('simple', $categoryId); - } - $simpleProductData = $this->sendRestRequest( - self::$productEndpoint, - self::HTTP_METHOD_POST, - ['product' => $simpleProductData] - ); - return $simpleProductData; - } - - /** - * Create a configurable product in Magento. - * - * @param integer $categoryId - * @param array $configurableProductData - * @return array|mixed - * @part json - * @part xml - */ - public function requireConfigurableProduct($categoryId = 0, $configurableProductData = []) - { - $configurableProductData = $this->getProductApiData('configurable', $categoryId, $configurableProductData); - $this->sendRestRequest( - self::$productEndpoint, - self::HTTP_METHOD_POST, - ['product' => $configurableProductData] - ); - - $attributeData = $this->getProductAttributeApiData(); - $attribute = $this->sendRestRequest( - self::$productAttributesEndpoint, - self::HTTP_METHOD_POST, - $attributeData - ); - $options = $this->sendRestRequest( - sprintf(self::$productAttributesOptionsEndpoint, $attribute->attribute_code), - self::HTTP_METHOD_GET - ); - - $attributeSetData = $this->getAssignAttributeToAttributeSetApiData($attribute->attribute_code); - $this->sendRestRequest( - self::$productAttributeSetEndpoint, - self::HTTP_METHOD_POST, - $attributeSetData - ); - - $simpleProduct1Data = $this->getProductApiData('simple', $categoryId); - array_push( - $simpleProduct1Data['custom_attributes'], - [ - 'attribute_code' => $attribute->attribute_code, - 'value' => $options[1]->value - ] - ); - $simpleProduct1Id = $this->sendRestRequest( - self::$productEndpoint, - self::HTTP_METHOD_POST, - ['product' => $simpleProduct1Data] - )->id; - - $simpleProduct2Data = $this->getProductApiData('simple', $categoryId); - array_push( - $simpleProduct2Data['custom_attributes'], - [ - 'attribute_code' => $attribute->attribute_code, - 'value' => $options[2]->value - ] - ); - $simpleProduct2Id = $this->sendRestRequest( - self::$productEndpoint, - self::HTTP_METHOD_POST, - ['product' => $simpleProduct2Data] - )->id; - - $tAttributes[] = [ - 'id' => $attribute->attribute_id, - 'code' => $attribute->attribute_code - ]; - - $tOptions = [ - intval($options[1]->value), - intval($options[2]->value) - ]; - - $configOptions = $this->getConfigurableProductOptionsApiData($tAttributes, $tOptions); - - $configurableProductData = $this->getConfigurableProductApiData( - $configOptions, - [$simpleProduct1Id, $simpleProduct2Id], - $configurableProductData - ); - - $configurableProductData = $this->sendRestRequest( - self::$productEndpoint . '/' . $configurableProductData['sku'], - self::HTTP_METHOD_PUT, - ['product' => $configurableProductData] - ); - return $configurableProductData; - } - - /** - * Create a product attribute in Magento. - * - * @param string $code - * @return array|mixed - * @part json - * @part xml - */ - public function requireProductAttribute($code = 'attribute') - { - $attributeData = $this->getProductAttributeApiData($code); - $attributeData = $this->sendRestRequest( - self::$productAttributesEndpoint, - self::HTTP_METHOD_POST, - $attributeData - ); - return $attributeData; - } - - /** - * Create a customer in Magento. - * - * @param array $customerData - * @param string $password - * @return array|mixed - * @part json - * @part xml - */ - public function requireCustomer(array $customerData = [], $password = '123123qW') - { - if (!$customerData) { - $customerData = $this->getCustomerApiData(); - } - $customerData = $this->getCustomerApiDataWithPassword($customerData, $password); - $customerData = $this->sendRestRequest( - self::$customersEndpoint, - self::HTTP_METHOD_POST, - $customerData - ); - return $customerData; - } - - /** - * Get category api data. - * - * @param array $categoryData - * @return array - * @part json - * @part xml - */ - public function getCategoryApiData($categoryData = []) - { - $faker = \Faker\Factory::create(); - $sq = sqs(); - return array_replace_recursive( - [ - 'parent_id' => '2', - 'name' => 'category' . $sq, - 'is_active' => true, - 'include_in_menu' => true, - 'available_sort_by' => ['position', 'name'], - 'custom_attributes' => [ - ['attribute_code' => 'url_key', 'value' => 'category' . $sq], - ['attribute_code' => 'description', 'value' => $faker->text(20)], - ['attribute_code' => 'meta_title', 'value' => $faker->text(20)], - ['attribute_code' => 'meta_keywords', 'value' => $faker->text(20)], - ['attribute_code' => 'meta_description', 'value' => $faker->text(20)], - ['attribute_code' => 'display_mode', 'value' => 'PRODUCTS'], - ['attribute_code' => 'landing_page', 'value' => ''], - ['attribute_code' => 'is_anchor', 'value' => '0'], - ['attribute_code' => 'custom_use_parent_settings', 'value' => '0'], - ['attribute_code' => 'custom_apply_to_products', 'value' => '0'], - ['attribute_code' => 'custom_design', 'value' => ''], - ['attribute_code' => 'page_layout', 'value' => ''], - ['attribute_code' => 'custom_design_to', 'value' => $faker->date($format = 'm/d/Y')], - ['attribute_code' => 'custom_design_from', 'value' => $faker->date($format = 'm/d/Y', 'now')] - ] - ], - $categoryData - ); - } - - /** - * Get simple product api data. - * - * @param string $type - * @param integer $categoryId - * @param array $productData - * @return array - * @part json - * @part xml - */ - public function getProductApiData($type = 'simple', $categoryId = 0, $productData = []) - { - $faker = \Faker\Factory::create(); - $sq = sqs(); - return array_replace_recursive( - [ - 'sku' => $type . '_product_sku' . $sq, - 'name' => $type . '_product' . $sq, - 'visibility' => 4, - 'type_id' => $type, - 'price' => $faker->randomFloat(2, 1), - 'status' => 1, - 'attribute_set_id' => 4, - 'extension_attributes' => [ - 'stock_item' => ['is_in_stock' => 1, 'qty' => $faker->numberBetween(100, 9000)] - ], - 'custom_attributes' => [ - ['attribute_code' => 'url_key', 'value' => $type . '_product' . $sq], - ['attribute_code' => 'tax_class_id', 'value' => 2], - ['attribute_code' => 'category_ids', 'value' => $categoryId], - ], - ], - $productData - ); - } - - /** - * Get Customer Api data. - * - * @param array $customerData - * @return array - * @part json - * @part xml - */ - public function getCustomerApiData($customerData = []) - { - $faker = \Faker\Factory::create(); - return array_replace_recursive( - [ - 'firstname' => $faker->firstName, - 'middlename' => $faker->firstName, - 'lastname' => $faker->lastName, - 'email' => $faker->email, - 'gender' => rand(0, 1), - 'group_id' => 1, - 'store_id' => 1, - 'website_id' => 1, - 'custom_attributes' => [ - [ - 'attribute_code' => 'disable_auto_group_change', - 'value' => '0', - ], - ], - ], - $customerData - ); - } - - /** - * Get customer data including password. - * - * @param array $customerData - * @param string $password - * @return array - * @part json - * @part xml - */ - public function getCustomerApiDataWithPassword($customerData = [], $password = '123123qW') - { - return ['customer' => self::getCustomerApiData($customerData), 'password' => $password]; - } - - /** - * @param string $code - * @param array $attributeData - * @return array - * @part json - * @part xml - */ - public function getProductAttributeApiData($code = 'attribute', $attributeData = []) - { - $sq = sqs(); - return array_replace_recursive( - [ - 'attribute' => [ - 'attribute_code' => $code . $sq, - 'frontend_labels' => [ - [ - 'store_id' => 0, - 'label' => $code . $sq - ], - ], - 'is_required' => false, - 'is_unique' => false, - 'is_visible' => true, - 'scope' => 'global', - 'default_value' => '', - 'frontend_input' => 'select', - 'is_visible_on_front' => true, - 'is_searchable' => true, - 'is_visible_in_advanced_search' => true, - 'is_filterable' => true, - 'is_filterable_in_search' => true, - //'is_used_in_grid' => true, - //'is_visible_in_grid' => true, - //'is_filterable_in_grid' => true, - 'used_in_product_listing' => true, - 'is_used_for_promo_rules' => true, - 'options' => [ - [ - 'label' => 'option1', - 'value' => '', - 'sort_order' => 0, - 'is_default' => true, - 'store_labels' => [ - [ - 'store_id' => 0, - 'label' => 'option1' - ], - [ - 'store_id' => 1, - 'label' => 'option1' - ] - ] - ], - [ - 'label' => 'option2', - 'value' => '', - 'sort_order' => 1, - 'is_default' => false, - 'store_labels' => [ - [ - 'store_id' => 0, - 'label' => 'option2' - ], - [ - 'store_id' => 1, - 'label' => 'option2' - ] - ] - ] - ] - ], - ], - $attributeData - ); - } - - /** - * @param array $attributes - * @param array $optionIds - * @return array - * @part json - * @part xml - */ - public function getConfigurableProductOptionsApiData($attributes, $optionIds) - { - $configurableProductOptions = []; - foreach ($attributes as $attribute) { - $attributeItem = [ - 'attribute_id' => (string)$attribute['id'], - 'label' => $attribute['code'], - 'values' => [] - ]; - foreach ($optionIds as $optionId) { - $attributeItem['values'][] = ['value_index' => $optionId]; - } - $configurableProductOptions [] = $attributeItem; - } - return $configurableProductOptions; - } - - /** - * @param array $configurableProductOptions - * @param array $childProductIds - * @param array $configurableProduct - * @param integer $categoryId - * @return array - * @part json - * @part xml - */ - public function getConfigurableProductApiData( - array $configurableProductOptions, - array $childProductIds, - array $configurableProduct = [], - int $categoryId = 0 - ) { - if (!$configurableProduct) { - $configurableProduct = $this->getProductApiData('configurable', $categoryId); - } - $configurableProduct = array_merge_recursive( - $configurableProduct, - [ - 'extension_attributes' => [ - 'configurable_product_options' => $configurableProductOptions, - 'configurable_product_links' => $childProductIds, - ], - ] - ); - return $configurableProduct; - } - - /** - * @param string $attributeCode - * @param integer $attributeSetId - * @param integer $attributeGroupId - * @return array - * @part json - * @part xml - */ - public function getAssignAttributeToAttributeSetApiData( - $attributeCode, - int $attributeSetId = 4, - int $attributeGroupId = 7 - ) { - return [ - 'attributeSetId' => $attributeSetId, - 'attributeGroupId' => $attributeGroupId, - 'attributeCode' => $attributeCode, - 'sortOrder' => 0 - ]; - } -} diff --git a/src/Magento/FunctionalTestingFramework/Module/MagentoSequence.php b/src/Magento/FunctionalTestingFramework/Module/MagentoSequence.php index 6273965dd..7c1b915b3 100644 --- a/src/Magento/FunctionalTestingFramework/Module/MagentoSequence.php +++ b/src/Magento/FunctionalTestingFramework/Module/MagentoSequence.php @@ -1,13 +1,13 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ // @codingStandardsIgnoreFile namespace Magento\FunctionalTestingFramework\Module; -use Codeception\Module\Sequence; +use Magento\FunctionalTestingFramework\Codeception\Module\Sequence; use Codeception\Exception\ModuleException; /** @@ -16,7 +16,7 @@ */ class MagentoSequence extends Sequence { - protected $config = ['prefix' => '']; + protected array $config = ['prefix' => '']; } if (!function_exists('msq') && !function_exists('msqs')) { require_once __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'Util' . DIRECTORY_SEPARATOR . 'msq.php'; diff --git a/src/Magento/FunctionalTestingFramework/Module/MagentoWebDriver.php b/src/Magento/FunctionalTestingFramework/Module/MagentoWebDriver.php index 6c680784c..e5b1c70d8 100644 --- a/src/Magento/FunctionalTestingFramework/Module/MagentoWebDriver.php +++ b/src/Magento/FunctionalTestingFramework/Module/MagentoWebDriver.php @@ -1,24 +1,32 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ namespace Magento\FunctionalTestingFramework\Module; +use Codeception\Lib\Actor\Shared\Pause; use Codeception\Module\WebDriver; use Codeception\Test\Descriptor; use Codeception\TestInterface; +use Magento\FunctionalTestingFramework\Allure\AllureHelper; use Facebook\WebDriver\Interactions\WebDriverActions; 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\Support\AttachmentSupport; +use Qameta\Allure\Allure; +use Magento\FunctionalTestingFramework\DataTransport\Protocol\CurlTransport; +use Qameta\Allure\Io\DataSourceFactory; use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; /** @@ -40,34 +48,32 @@ * ``` * * @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"]' - ]; - - /** - * The module required fields, to be set in the suite .yml configuration file. - * - * @var array - */ - protected $requiredFields = [ - 'url', - 'backend_name', - 'username', - 'password', - 'browser' + '//div[@data-role="spinner"]', + '//div[contains(@class,"file-uploader-spinner")]', + '//div[contains(@class,"image-uploader-spinner")]', + '//div[contains(@class,"uploader")]//div[@class="file-row"]', ]; /** @@ -105,14 +111,31 @@ class MagentoWebDriver extends WebDriver */ private $htmlReport; + /** + * Array to store Javascript errors + * + * @var string[] + */ + private $jsErrors = []; + + /** + * Contains last execution times for Cron + * + * @var int[] + */ + private $cronExecution = []; + /** * Sanitizes config, then initializes using parent. + * * @return void */ public function _initialize() { $this->config = ConfigSanitizerUtil::sanitizeWebDriverConfig($this->config); + parent::_initialize(); + $this->cleanJsError(); } /** @@ -120,14 +143,16 @@ public function _initialize() * * @return void */ - public function _resetConfig() + public function _resetConfig():void { parent::_resetConfig(); $this->config = ConfigSanitizerUtil::sanitizeWebDriverConfig($this->config); + $this->cleanJsError(); } /** * Remap parent::_after, called in TestContextExtension + * * @param TestInterface $test * @return void */ @@ -138,21 +163,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() { @@ -162,6 +198,7 @@ public function _getUrl() "Module connection failure. The URL for client can't bre retrieved" ); } + return $this->config['url']; } @@ -169,16 +206,17 @@ public function _getUrl() * Uri of currently opened page. * * @return string - * @api * @throws ModuleException + * @api */ - public function _getCurrentUri() + public function _getCurrentUri():string { $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); + + return Uri::retrieveUri((string)$url); } /** @@ -187,9 +225,12 @@ public function _getCurrentUri() * @param string $url * @return void */ - public function dontSeeCurrentUrlEquals($url) + public function dontSeeCurrentUrlEquals($url):void { - $this->assertNotEquals($url, $this->webDriver->getCurrentURL()); + $actualUrl = $this->webDriver->getCurrentURL(); + $comparison = "Expected: $url\nActual: $actualUrl"; + AllureHelper::addAttachmentToCurrentStep($comparison, 'Comparison'); + $this->assertNotEquals($url, $actualUrl); } /** @@ -198,9 +239,12 @@ public function dontSeeCurrentUrlEquals($url) * @param string $regex * @return void */ - public function dontSeeCurrentUrlMatches($regex) + public function dontSeeCurrentUrlMatches($regex):void { - $this->assertNotRegExp($regex, $this->webDriver->getCurrentURL()); + $actualUrl = $this->webDriver->getCurrentURL(); + $comparison = "Expected: $regex\nActual: $actualUrl"; + AllureHelper::addAttachmentToCurrentStep($comparison, 'Comparison'); + $this->assertNotRegExp($regex, $actualUrl); } /** @@ -209,9 +253,12 @@ public function dontSeeCurrentUrlMatches($regex) * @param string $needle * @return void */ - public function dontSeeInCurrentUrl($needle) + public function dontSeeInCurrentUrl($needle):void { - $this->assertNotContains($needle, $this->webDriver->getCurrentURL()); + $actualUrl = $this->webDriver->getCurrentURL(); + $comparison = "Expected: $needle\nActual: $actualUrl"; + AllureHelper::addAttachmentToCurrentStep($comparison, 'Comparison'); + $this->assertStringNotContainsString($needle, $actualUrl); } /** @@ -220,7 +267,7 @@ public function dontSeeInCurrentUrl($needle) * @param string|null $regex * @return string */ - public function grabFromCurrentUrl($regex = null) + public function grabFromCurrentUrl($regex = null):string { $fullUrl = $this->webDriver->getCurrentURL(); if (!$regex) { @@ -234,6 +281,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]; } @@ -243,9 +291,12 @@ public function grabFromCurrentUrl($regex = null) * @param string $url * @return void */ - public function seeCurrentUrlEquals($url) + public function seeCurrentUrlEquals($url):void { - $this->assertEquals($url, $this->webDriver->getCurrentURL()); + $actualUrl = $this->webDriver->getCurrentURL(); + $comparison = "Expected: $url\nActual: $actualUrl"; + AllureHelper::addAttachmentToCurrentStep($comparison, 'Comparison'); + $this->assertEquals($url, $actualUrl); } /** @@ -254,9 +305,12 @@ public function seeCurrentUrlEquals($url) * @param string $regex * @return void */ - public function seeCurrentUrlMatches($regex) + public function seeCurrentUrlMatches($regex):void { - $this->assertRegExp($regex, $this->webDriver->getCurrentURL()); + $actualUrl = $this->webDriver->getCurrentURL(); + $comparison = "Expected: $regex\nActual: $actualUrl"; + AllureHelper::addAttachmentToCurrentStep($comparison, 'Comparison'); + $this->assertRegExp($regex, $actualUrl); } /** @@ -265,9 +319,12 @@ public function seeCurrentUrlMatches($regex) * @param string $needle * @return void */ - public function seeInCurrentUrl($needle) + public function seeInCurrentUrl($needle):void { - $this->assertContains($needle, $this->webDriver->getCurrentURL()); + $actualUrl = $this->webDriver->getCurrentURL(); + $comparison = "Expected: $needle\nActual: $actualUrl"; + AllureHelper::addAttachmentToCurrentStep($comparison, 'Comparison'); + $this->assertStringContainsString(urldecode($needle), urldecode($actualUrl)); } /** @@ -291,13 +348,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'; @@ -320,8 +377,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) { @@ -358,8 +415,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) { @@ -374,12 +431,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); @@ -392,34 +451,26 @@ public function waitForLoadingMaskToDisappear($timeout = null) } /** - * Verify that there are no JavaScript errors in the console. + * Format input to specified currency in locale specified + * @link https://php.net/manual/en/numberformatter.formatcurrency.php * - * @throws ModuleException - * @return void + * @param float $value + * @param string $locale + * @param string $currency + * @return string + * @throws TestFrameworkException */ - public function dontSeeJsError() + public function formatCurrency(float $value, $locale, $currency) { - $logs = $this->webDriver->manage()->getLog('browser'); - foreach ($logs as $log) { - if ($log['level'] == 'SEVERE') { - throw new ModuleException($this, 'Errors in JavaScript: ' . json_encode($log)); + $formatter = \NumberFormatter::create($locale, \NumberFormatter::CURRENCY); + if ($formatter && !empty($formatter)) { + $result = $formatter->formatCurrency($value, $currency); + if ($result) { + return $result; } } - } - /** - * @param float $money - * @param string $locale - * @return array - */ - public function formatMoney(float $money, $locale = 'en_US.UTF-8') - { - $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]; + throw new TestFrameworkException('Invalid attributes used in formatCurrency.'); } /** @@ -431,6 +482,7 @@ public function formatMoney(float $money, $locale = 'en_US.UTF-8') public function parseFloat($floatString) { $floatString = str_replace(',', '', $floatString); + return floatval($floatString); } @@ -441,7 +493,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) { @@ -452,6 +504,7 @@ public function mSetLocale(int $category, $locale) /** * Reset Locale setting. + * * @return void */ public function mResetLocale() @@ -466,6 +519,7 @@ public function mResetLocale() /** * Scroll to the Top of the Page. + * * @return void */ public function scrollToTopOfPage() @@ -474,48 +528,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|null $timeout + * @param string|null $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'), '/'); + + $apiURL = UrlFormatter::format( + $baseUrl . '/' . ltrim(getenv('MAGENTO_CLI_COMMAND_PATH'), '/'), + false + ); $executor = new CurlTransport(); $executor->write( $apiURL, [ - 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(); $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; } @@ -525,8 +666,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) { @@ -553,7 +694,7 @@ public function conditionalClick($selector, $dependentSelector, $visible) * @param string $selector * @return void */ - public function clearField($selector) + public function clearField($selector):void { $this->fillField($selector, ""); } @@ -575,12 +716,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 */ @@ -595,41 +737,98 @@ public function _before(TestInterface $test) /** * Override for codeception's default dragAndDrop to include offset options. + * * @param string $source * @param string $target * @param integer $xOffset * @param integer $yOffset * @return void */ - public function dragAndDrop($source, $target, $xOffset = null, $yOffset = null) + public function dragAndDrop($source, $target, $xOffset = null, $yOffset = null):void { + $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); } } /** - * Function used to fill sensitive crednetials with user data, data is decrypted immediately prior to fill to avoid + * 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. * * @param string $field * @param string $value * @return void + * @throws TestFrameworkException */ public function fillSecretField($field, $value) { @@ -637,9 +836,55 @@ 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); } + /** + * Function used to create data that contains sensitive credentials in a <createData> <field> override. + * The data is decrypted immediately prior to data creation to avoid exposure in console or log. + * + * @param string $command + * @param integer|null $timeout + * @param string|null $arguments + * @throws TestFrameworkException + * @return string + */ + 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); + if ($decryptedCommand === false) { + throw new TestFrameworkException("\nFailed to decrypt magentoCLI command {$command}\n"); + } + return $this->magentoCLI($decryptedCommand, $timeout, $arguments); + } + + /** + * Function used to verify sensitive credentials in the data, data is decrypted immediately prior to see to avoid + * exposure in console or log. + * + * @param string $field + * @param string $value + * @return void + * @throws TestFrameworkException + */ + public function seeInSecretField(string $field, string $value):void + { + // to protect any secrets from being printed to console the values are executed only at the webdriver level as a + // decrypted value + + $decryptedValue = CredentialStore::getInstance()->decryptSecretValue($value); + if ($decryptedValue === false) { + throw new TestFrameworkException("\nFailed to decrypt value {$value} for field {$field}\n"); + } + $this->seeInField($field, $decryptedValue); + } + /** * Override for _failed method in Codeception method. Adds png and html attachments to allure report * following parent execution of test failure processing. @@ -654,15 +899,26 @@ 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) { - throw new \RuntimeException("Suite condition failure: \n" . $fail->getMessage()); + if ($this->current_test === null) { + throw new \RuntimeException("Suite condition failure: \n" + . " Something went wrong with selenium server/chrome driver : \n . + {$fail->getMessage()}\n{$fail->getTraceAsString()}"); } - - $this->addAttachment($this->pngReport, $test->getMetadata()->getName() . '.png', 'image/png'); - $this->addAttachment($this->htmlReport, $test->getMetadata()->getName() . '.html', 'text/html'); - + AllureHelper::doAddAttachment( + DataSourceFactory::fromFile($this->pngReport), + $test->getMetadata()->getName() . '.png', + 'image/png' + ); + AllureHelper::doAddAttachment( + DataSourceFactory::fromFile($this->htmlReport), + $test->getMetadata()->getName() . '.html', + 'text/html' + ); $this->debug("Failure due to : {$fail->getMessage()}"); $this->debug("Screenshot saved to {$this->pngReport}"); $this->debug("Html saved to {$this->htmlReport}"); @@ -670,12 +926,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); } @@ -689,24 +946,242 @@ 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) + public function amOnPage($page):void { - parent::amOnPage($page); + (0 === strpos($page, 'http')) ? parent::amOnUrl($page) : parent::amOnPage($page); $this->waitForPageLoad(); } /** - * Turn Readiness check on or off + * Clean Javascript errors in internal array * - * @param boolean $check + * @return void + */ + public function cleanJsError() + { + $this->jsErrors = []; + } + + /** + * Save Javascript error message to internal array + * + * @param string $errMsg + * @return void + */ + public function setJsError($errMsg) + { + $this->jsErrors[] = $errMsg; + } + + /** + * Get all Javascript errors + * + * @return string + */ + private function getJsErrors() + { + $errors = ''; + + if (!empty($this->jsErrors)) { + $errors = 'Errors in JavaScript:'; + foreach ($this->jsErrors as $jsError) { + $errors .= "\n" . $jsError; + } + } + + return $errors; + } + + /** + * Verify that there is no JavaScript error in browser logs + * + * @return void + */ + public function dontSeeJsError() + { + $this->assertEmpty($this->jsErrors, $this->getJsErrors()); + } + + /** + * Takes a screenshot of the current window and saves it to `tests/_output/debug`. + * + * This function is copied over from the original Codeception WebDriver so that we still have visibility of + * the screenshot filename to be passed to the AllureHelper. + * + * @param string $name + * @return void + */ + public function makeScreenshot($name = null):void + { + if (empty($name)) { + $name = uniqid(date("Y-m-d_H-i-s_")); + } + $debugDir = codecept_log_dir() . 'debug'; + if (!is_dir($debugDir)) { + mkdir($debugDir, 0777); + } + $screenName = $debugDir . DIRECTORY_SEPARATOR . $name . '.png'; + $this->_saveScreenshot($screenName); + $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); + + if (isset($cronGroups[0]) && !isset($cronGroups[1])) { + $arguments .= ' --bootstrap=standaloneProcessStarted=1'; + } + + $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):void + { + 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):void + { + 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(); + } + + /** + * @param string $selector + * @param string $expected + * @return void + */ + public function seeNumberOfElements($selector, $expected): void + { + $counted = count($this->matchVisible($selector)); + if (is_array($expected)) { + [$floor, $ceil] = $expected; + $this->assertTrue( + $floor <= $counted && $ceil >= $counted, + 'Number of elements counted differs from expected range' + ); + } else { + $this->assertSame( + (int)$expected, + (int)$counted, + 'Number of elements counted differs from expected number' + ); + } + } + + /** + * @param string $text + * @param string $selector + * @return void + */ + public function see($text, $selector = null): void + { + $text = (isset($text)) + ? (string)$text + : ""; + if (!$selector) { + $this->assertPageContains($text); + return; + } + + $this->enableImplicitWait(); + $nodes = $this->matchVisible($selector); + $this->disableImplicitWait(); + $this->assertNodesContain($text, $nodes, $selector); + } + + /** + * @param string $text + * @param string $selector * @return void */ - public function skipReadinessCheck($check) + public function dontSee($text, $selector = null): void { - $this->config['skipReadiness'] = $check; + $text = (isset($text)) + ? (string)$text + : ""; + if (!$selector) { + $this->assertPageNotContains($text); + } else { + $nodes = $this->matchVisible($selector); + $this->assertNodesNotContain($text, $nodes, $selector); + } } } diff --git a/src/Magento/FunctionalTestingFramework/Module/MagentoWebDriverDoctor.php b/src/Magento/FunctionalTestingFramework/Module/MagentoWebDriverDoctor.php new file mode 100644 index 000000000..3c8b20205 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Module/MagentoWebDriverDoctor.php @@ -0,0 +1,168 @@ +<?php +/** + * Copyright 2019 Adobe + * All Rights Reserved. + */ + +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->config['http_proxy'], + $this->config['http_proxy_port'] + ); + 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..a3e19deb2 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Module/Util/ModuleUtils.php @@ -0,0 +1,36 @@ +<?php +/** + * Copyright 2022 Adobe + * All Rights Reserved. + */ + +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.php b/src/Magento/FunctionalTestingFramework/ObjectManager.php index 8b0f34537..1f2f83e70 100644 --- a/src/Magento/FunctionalTestingFramework/ObjectManager.php +++ b/src/Magento/FunctionalTestingFramework/ObjectManager.php @@ -1,7 +1,7 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ namespace Magento\FunctionalTestingFramework; @@ -39,8 +39,8 @@ class ObjectManager extends \Magento\FunctionalTestingFramework\ObjectManager\Ob * @param array $sharedInstances */ public function __construct( - \Magento\FunctionalTestingFramework\ObjectManager\Factory $factory = null, - \Magento\FunctionalTestingFramework\ObjectManager\ConfigInterface $config = null, + ?\Magento\FunctionalTestingFramework\ObjectManager\Factory $factory = null, + ?\Magento\FunctionalTestingFramework\ObjectManager\ConfigInterface $config = null, array $sharedInstances = [] ) { parent::__construct($factory, $config, $sharedInstances); diff --git a/src/Magento/FunctionalTestingFramework/ObjectManager/Config.php b/src/Magento/FunctionalTestingFramework/ObjectManager/Config.php index 3133de1ef..a95f9e5b3 100644 --- a/src/Magento/FunctionalTestingFramework/ObjectManager/Config.php +++ b/src/Magento/FunctionalTestingFramework/ObjectManager/Config.php @@ -1,7 +1,7 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ namespace Magento\FunctionalTestingFramework\ObjectManager; diff --git a/src/Magento/FunctionalTestingFramework/ObjectManager/Config/Config.php b/src/Magento/FunctionalTestingFramework/ObjectManager/Config/Config.php index c8e6c8292..df1bbe377 100644 --- a/src/Magento/FunctionalTestingFramework/ObjectManager/Config/Config.php +++ b/src/Magento/FunctionalTestingFramework/ObjectManager/Config/Config.php @@ -1,8 +1,9 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ + namespace Magento\FunctionalTestingFramework\ObjectManager\Config; use Magento\FunctionalTestingFramework\ObjectManager\DefinitionInterface; @@ -76,7 +77,7 @@ class Config implements \Magento\FunctionalTestingFramework\ObjectManager\Config * @param RelationsInterface|null $relations * @param DefinitionInterface|null $definitions */ - public function __construct(RelationsInterface $relations = null, DefinitionInterface $definitions = null) + public function __construct(?RelationsInterface $relations = null, ?DefinitionInterface $definitions = null) { $this->relations = $relations ? : new RelationsRuntime(); $this->definitions = $definitions ? : new DefinitionRuntime(); @@ -153,6 +154,7 @@ public function getPreference($type) * @param string $type * @return array * @SuppressWarnings(PHPMD.CyclomaticComplexity) + * Revisited to reduce cyclomatic complexity, left unrefactored for readability */ protected function collectConfiguration($type) { @@ -194,7 +196,6 @@ protected function collectConfiguration($type) * * @param array $configuration * @return void - * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ protected function mergeConfiguration(array $configuration) { @@ -207,28 +208,39 @@ protected function mergeConfiguration(array $configuration) break; default: - $key = ltrim($key, '\\'); - if (isset($curConfig['type'])) { - $this->virtualTypes[$key] = ltrim($curConfig['type'], '\\'); - } - if (isset($curConfig['arguments'])) { - if (!empty($this->mergedArguments)) { - $this->mergedArguments = []; - } - if (isset($this->arguments[$key])) { - $this->arguments[$key] = array_replace($this->arguments[$key], $curConfig['arguments']); - } else { - $this->arguments[$key] = $curConfig['arguments']; - } - } - if (isset($curConfig['shared'])) { - if (!$curConfig['shared']) { - $this->nonShared[$key] = 1; - } else { - unset($this->nonShared[$key]); - } - } - break; + $this->setConfiguration($key, $curConfig); + } + } + } + + /** + * Set configuration + * + * @param string $key + * @param array $config + * @return void + */ + private function setConfiguration($key, $config) + { + $key = ltrim($key, '\\'); + if (isset($config['type'])) { + $this->virtualTypes[$key] = ltrim($config['type'], '\\'); + } + if (isset($config['arguments'])) { + if (!empty($this->mergedArguments)) { + $this->mergedArguments = []; + } + if (isset($this->arguments[$key])) { + $this->arguments[$key] = array_replace($this->arguments[$key], $config['arguments']); + } else { + $this->arguments[$key] = $config['arguments']; + } + } + if (isset($config['shared'])) { + if (!$config['shared']) { + $this->nonShared[$key] = 1; + } else { + unset($this->nonShared[$key]); } } } diff --git a/src/Magento/FunctionalTestingFramework/ObjectManager/Config/Mapper/ArgumentParser.php b/src/Magento/FunctionalTestingFramework/ObjectManager/Config/Mapper/ArgumentParser.php index 33089e7a0..3e2ae3c71 100644 --- a/src/Magento/FunctionalTestingFramework/ObjectManager/Config/Mapper/ArgumentParser.php +++ b/src/Magento/FunctionalTestingFramework/ObjectManager/Config/Mapper/ArgumentParser.php @@ -1,8 +1,9 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ + namespace Magento\FunctionalTestingFramework\ObjectManager\Config\Mapper; use Magento\FunctionalTestingFramework\Config\Converter\Dom\Flat as FlatConverter; diff --git a/src/Magento/FunctionalTestingFramework/ObjectManager/Config/Mapper/Dom.php b/src/Magento/FunctionalTestingFramework/ObjectManager/Config/Mapper/Dom.php index bf11e0003..83414bdbf 100644 --- a/src/Magento/FunctionalTestingFramework/ObjectManager/Config/Mapper/Dom.php +++ b/src/Magento/FunctionalTestingFramework/ObjectManager/Config/Mapper/Dom.php @@ -1,8 +1,9 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ + namespace Magento\FunctionalTestingFramework\ObjectManager\Config\Mapper; use Magento\FunctionalTestingFramework\Data\Argument\InterpreterInterface; @@ -33,8 +34,8 @@ class Dom implements \Magento\FunctionalTestingFramework\Config\ConverterInterfa */ public function __construct( InterpreterInterface $argumentInterpreter, - BooleanUtils $booleanUtils = null, - ArgumentParser $argumentParser = null + ?BooleanUtils $booleanUtils = null, + ?ArgumentParser $argumentParser = null ) { $this->argumentInterpreter = $argumentInterpreter; $this->booleanUtils = $booleanUtils ?: new BooleanUtils(); @@ -48,16 +49,13 @@ public function __construct( * @return array * @throws \Exception * @todo this method has high cyclomatic complexity in order to avoid performance issues - * @SuppressWarnings(PHPMD.CyclomaticComplexity) - * @SuppressWarnings(PHPMD.NPathComplexity) - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ 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) { @@ -76,41 +74,14 @@ 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) { $typeData['type'] = $attributeType->nodeValue; } } - $typeArguments = []; - /** @var \DOMNode $typeChildNode */ - foreach ($node->childNodes as $typeChildNode) { - 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) { - continue; - } - $argumentName = $argumentNode->attributes->getNamedItem('name')->nodeValue; - $argumentData = $this->argumentParser->parse($argumentNode); - $typeArguments[$argumentName] = $this->argumentInterpreter->evaluate( - $argumentData - ); - } - break; - default: - throw new \Exception( - "Invalid application config. Unknown node: {$typeChildNode->nodeName}." - ); - } - } - - $typeData['arguments'] = $typeArguments; + $typeData['arguments'] = $this->setTypeArguments($node); $output[$typeNodeAttributes->getNamedItem('name')->nodeValue] = $typeData; break; default: @@ -120,4 +91,42 @@ public function convert($config) return $output; } + + /** Read typeChildNodes and set typeArguments + * @param DOMNode $node + * @return mixed + * @throws \Exception + */ + private function setTypeArguments($node) + { + $typeArguments = []; + + foreach ($node->childNodes as $typeChildNode) { + /** @var \DOMNode $typeChildNode */ + 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) { + continue; + } + $argumentName = $argumentNode->attributes->getNamedItem('name')->nodeValue; + $argumentData = $this->argumentParser->parse($argumentNode); + $typeArguments[$argumentName] = $this->argumentInterpreter->evaluate( + $argumentData + ); + } + break; + + default: + throw new \Exception( + "Invalid application config. Unknown node: {$typeChildNode->nodeName}." + ); + } + } + return $typeArguments; + } } diff --git a/src/Magento/FunctionalTestingFramework/ObjectManager/Config/Reader/Dom.php b/src/Magento/FunctionalTestingFramework/ObjectManager/Config/Reader/Dom.php index 876e949cb..e592af2b6 100644 --- a/src/Magento/FunctionalTestingFramework/ObjectManager/Config/Reader/Dom.php +++ b/src/Magento/FunctionalTestingFramework/ObjectManager/Config/Reader/Dom.php @@ -1,8 +1,9 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ + namespace Magento\FunctionalTestingFramework\ObjectManager\Config\Reader; /** @@ -65,6 +66,6 @@ public function __construct( */ protected function _createConfigMerger($mergerClass, $initialContents) { - return new $mergerClass($initialContents, $this->_idAttributes, self::TYPE_ATTRIBUTE, $this->_perFileSchema); + return new $mergerClass($initialContents, $this->idAttributes, self::TYPE_ATTRIBUTE, $this->perFileSchema); } } diff --git a/src/Magento/FunctionalTestingFramework/ObjectManager/Config/Reader/DomFactory.php b/src/Magento/FunctionalTestingFramework/ObjectManager/Config/Reader/DomFactory.php index 27d5518ad..b25eb5e1d 100644 --- a/src/Magento/FunctionalTestingFramework/ObjectManager/Config/Reader/DomFactory.php +++ b/src/Magento/FunctionalTestingFramework/ObjectManager/Config/Reader/DomFactory.php @@ -1,10 +1,9 @@ <?php /** - * Config reader factory - * - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ + namespace Magento\FunctionalTestingFramework\ObjectManager\Config\Reader; /** diff --git a/src/Magento/FunctionalTestingFramework/ObjectManager/Config/SchemaLocator.php b/src/Magento/FunctionalTestingFramework/ObjectManager/Config/SchemaLocator.php index aabf0e8f6..22e9294c9 100644 --- a/src/Magento/FunctionalTestingFramework/ObjectManager/Config/SchemaLocator.php +++ b/src/Magento/FunctionalTestingFramework/ObjectManager/Config/SchemaLocator.php @@ -1,7 +1,7 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ namespace Magento\FunctionalTestingFramework\ObjectManager\Config; diff --git a/src/Magento/FunctionalTestingFramework/ObjectManager/ConfigInterface.php b/src/Magento/FunctionalTestingFramework/ObjectManager/ConfigInterface.php index 53f3ba95a..8772ac365 100644 --- a/src/Magento/FunctionalTestingFramework/ObjectManager/ConfigInterface.php +++ b/src/Magento/FunctionalTestingFramework/ObjectManager/ConfigInterface.php @@ -1,8 +1,9 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ + namespace Magento\FunctionalTestingFramework\ObjectManager; /** diff --git a/src/Magento/FunctionalTestingFramework/ObjectManager/ConfigLoader/Primary.php b/src/Magento/FunctionalTestingFramework/ObjectManager/ConfigLoader/Primary.php index 265127c35..1ce6426d5 100644 --- a/src/Magento/FunctionalTestingFramework/ObjectManager/ConfigLoader/Primary.php +++ b/src/Magento/FunctionalTestingFramework/ObjectManager/ConfigLoader/Primary.php @@ -1,7 +1,7 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ namespace Magento\FunctionalTestingFramework\ObjectManager\ConfigLoader; diff --git a/src/Magento/FunctionalTestingFramework/ObjectManager/Definition/Runtime.php b/src/Magento/FunctionalTestingFramework/ObjectManager/Definition/Runtime.php index f77e7d110..f2cc89f25 100644 --- a/src/Magento/FunctionalTestingFramework/ObjectManager/Definition/Runtime.php +++ b/src/Magento/FunctionalTestingFramework/ObjectManager/Definition/Runtime.php @@ -1,7 +1,7 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ namespace Magento\FunctionalTestingFramework\ObjectManager\Definition; @@ -29,7 +29,7 @@ class Runtime implements \Magento\FunctionalTestingFramework\ObjectManager\Defin * Runtime constructor. * @param \Magento\FunctionalTestingFramework\Code\Reader\ClassReader|null $reader */ - public function __construct(\Magento\FunctionalTestingFramework\Code\Reader\ClassReader $reader = null) + public function __construct(?\Magento\FunctionalTestingFramework\Code\Reader\ClassReader $reader = null) { $this->reader = $reader ? : new \Magento\FunctionalTestingFramework\Code\Reader\ClassReader(); } diff --git a/src/Magento/FunctionalTestingFramework/ObjectManager/DefinitionInterface.php b/src/Magento/FunctionalTestingFramework/ObjectManager/DefinitionInterface.php index 124230fa3..05ab3a9b8 100644 --- a/src/Magento/FunctionalTestingFramework/ObjectManager/DefinitionInterface.php +++ b/src/Magento/FunctionalTestingFramework/ObjectManager/DefinitionInterface.php @@ -1,7 +1,7 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ namespace Magento\FunctionalTestingFramework\ObjectManager; diff --git a/src/Magento/FunctionalTestingFramework/ObjectManager/Factory.php b/src/Magento/FunctionalTestingFramework/ObjectManager/Factory.php index 9b795d58f..f3b65115c 100644 --- a/src/Magento/FunctionalTestingFramework/ObjectManager/Factory.php +++ b/src/Magento/FunctionalTestingFramework/ObjectManager/Factory.php @@ -1,7 +1,7 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ namespace Magento\FunctionalTestingFramework\ObjectManager; @@ -31,8 +31,8 @@ class Factory extends \Magento\FunctionalTestingFramework\ObjectManager\Factory\ */ public function __construct( ConfigInterface $config, - \Magento\FunctionalTestingFramework\ObjectManagerInterface $objectManager = null, - DefinitionInterface $definitions = null, + ?\Magento\FunctionalTestingFramework\ObjectManagerInterface $objectManager = null, + ?DefinitionInterface $definitions = null, $globalArguments = [] ) { parent::__construct($config, $objectManager, $definitions, $globalArguments); @@ -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 []; } @@ -103,6 +104,7 @@ public function prepareArguments($object, $method, array $arguments = []) * * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.NPathComplexity) + * Revisited to reduce cyclomatic complexity, left unrefactored for readability */ protected function resolveArguments($requestedType, array $parameters, array $arguments = []) { @@ -179,8 +181,10 @@ protected function resolveArguments($requestedType, array $parameters, array $ar * * @param array $array * @return void + * * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.NPathComplexity) + * Revisited to reduce cyclomatic complexity, left unrefactored for readability */ protected function parseArray(&$array) { @@ -219,14 +223,13 @@ protected function parseArray(&$array) * @param array $arguments * @return object * @throws \Exception - * - * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ 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 dbe6aa497..b4d14106a 100644 --- a/src/Magento/FunctionalTestingFramework/ObjectManager/Factory/Dynamic/Developer.php +++ b/src/Magento/FunctionalTestingFramework/ObjectManager/Factory/Dynamic/Developer.php @@ -1,8 +1,9 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ + namespace Magento\FunctionalTestingFramework\ObjectManager\Factory\Dynamic; /** @@ -54,8 +55,8 @@ class Developer implements \Magento\FunctionalTestingFramework\ObjectManager\Fac */ public function __construct( \Magento\FunctionalTestingFramework\ObjectManager\ConfigInterface $config, - \Magento\FunctionalTestingFramework\ObjectManagerInterface $objectManager = null, - \Magento\FunctionalTestingFramework\ObjectManager\DefinitionInterface $definitions = null, + ?\Magento\FunctionalTestingFramework\ObjectManagerInterface $objectManager = null, + ?\Magento\FunctionalTestingFramework\ObjectManager\DefinitionInterface $definitions = null, $globalArguments = [] ) { $this->config = $config; @@ -87,6 +88,7 @@ public function setObjectManager(\Magento\FunctionalTestingFramework\ObjectManag * * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.NPathComplexity) + * Revisited to reduce cyclomatic complexity, left unrefactored for readability */ protected function resolveArguments($requestedType, array $parameters, array $arguments = []) { @@ -171,14 +173,14 @@ protected function parseArray(&$array) * @return object * @throws \Exception * - * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPCPD) */ 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/FactoryInterface.php b/src/Magento/FunctionalTestingFramework/ObjectManager/FactoryInterface.php index b1ad87597..847b33cc0 100644 --- a/src/Magento/FunctionalTestingFramework/ObjectManager/FactoryInterface.php +++ b/src/Magento/FunctionalTestingFramework/ObjectManager/FactoryInterface.php @@ -1,8 +1,9 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ + namespace Magento\FunctionalTestingFramework\ObjectManager; /** diff --git a/src/Magento/FunctionalTestingFramework/ObjectManager/ObjectHandlerInterface.php b/src/Magento/FunctionalTestingFramework/ObjectManager/ObjectHandlerInterface.php index 1bc79cbbb..f0ad2dfa7 100644 --- a/src/Magento/FunctionalTestingFramework/ObjectManager/ObjectHandlerInterface.php +++ b/src/Magento/FunctionalTestingFramework/ObjectManager/ObjectHandlerInterface.php @@ -1,7 +1,7 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ namespace Magento\FunctionalTestingFramework\ObjectManager; @@ -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/ObjectManager/ObjectManager.php b/src/Magento/FunctionalTestingFramework/ObjectManager/ObjectManager.php index 57c5cc39c..334124790 100644 --- a/src/Magento/FunctionalTestingFramework/ObjectManager/ObjectManager.php +++ b/src/Magento/FunctionalTestingFramework/ObjectManager/ObjectManager.php @@ -1,7 +1,7 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ namespace Magento\FunctionalTestingFramework\ObjectManager; diff --git a/src/Magento/FunctionalTestingFramework/ObjectManager/Relations/Runtime.php b/src/Magento/FunctionalTestingFramework/ObjectManager/Relations/Runtime.php index 192758220..4a1747467 100644 --- a/src/Magento/FunctionalTestingFramework/ObjectManager/Relations/Runtime.php +++ b/src/Magento/FunctionalTestingFramework/ObjectManager/Relations/Runtime.php @@ -1,8 +1,9 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ + namespace Magento\FunctionalTestingFramework\ObjectManager\Relations; /** @@ -28,7 +29,7 @@ class Runtime implements \Magento\FunctionalTestingFramework\ObjectManager\Relat * Runtime constructor. * @param \Magento\FunctionalTestingFramework\Code\Reader\ClassReader|null $classReader */ - public function __construct(\Magento\FunctionalTestingFramework\Code\Reader\ClassReader $classReader = null) + public function __construct(?\Magento\FunctionalTestingFramework\Code\Reader\ClassReader $classReader = null) { $this->classReader = $classReader ? : new \Magento\FunctionalTestingFramework\Code\Reader\ClassReader(); } diff --git a/src/Magento/FunctionalTestingFramework/ObjectManager/RelationsInterface.php b/src/Magento/FunctionalTestingFramework/ObjectManager/RelationsInterface.php index b1ff6b72b..28d0dcfea 100644 --- a/src/Magento/FunctionalTestingFramework/ObjectManager/RelationsInterface.php +++ b/src/Magento/FunctionalTestingFramework/ObjectManager/RelationsInterface.php @@ -1,8 +1,9 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ + namespace Magento\FunctionalTestingFramework\ObjectManager; /** diff --git a/src/Magento/FunctionalTestingFramework/ObjectManager/etc/config.xsd b/src/Magento/FunctionalTestingFramework/ObjectManager/etc/config.xsd index a8e7de5b4..34fc582ed 100644 --- a/src/Magento/FunctionalTestingFramework/ObjectManager/etc/config.xsd +++ b/src/Magento/FunctionalTestingFramework/ObjectManager/etc/config.xsd @@ -1,8 +1,8 @@ <?xml version="1.0" encoding="UTF-8"?> <!-- /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ --> <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> diff --git a/src/Magento/FunctionalTestingFramework/ObjectManagerFactory.php b/src/Magento/FunctionalTestingFramework/ObjectManagerFactory.php index c858a4035..882b9f2f2 100644 --- a/src/Magento/FunctionalTestingFramework/ObjectManagerFactory.php +++ b/src/Magento/FunctionalTestingFramework/ObjectManagerFactory.php @@ -1,8 +1,9 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ + namespace Magento\FunctionalTestingFramework; use Magento\FunctionalTestingFramework\ObjectManager\Factory; diff --git a/src/Magento/FunctionalTestingFramework/ObjectManagerInterface.php b/src/Magento/FunctionalTestingFramework/ObjectManagerInterface.php index 53cfc8d8e..0aede69ec 100644 --- a/src/Magento/FunctionalTestingFramework/ObjectManagerInterface.php +++ b/src/Magento/FunctionalTestingFramework/ObjectManagerInterface.php @@ -1,7 +1,7 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ namespace Magento\FunctionalTestingFramework; diff --git a/src/Magento/FunctionalTestingFramework/Page/Config/Dom.php b/src/Magento/FunctionalTestingFramework/Page/Config/Dom.php index bb6d08f58..852e8d594 100644 --- a/src/Magento/FunctionalTestingFramework/Page/Config/Dom.php +++ b/src/Magento/FunctionalTestingFramework/Page/Config/Dom.php @@ -1,7 +1,7 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2018 Adobe + * All Rights Reserved. */ namespace Magento\FunctionalTestingFramework\Page\Config; @@ -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->extractModuleName($filename) . - '_' . - $this->modulePathExtractor->getExtensionPath($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..ac2490e88 100644 --- a/src/Magento/FunctionalTestingFramework/Page/Config/SectionDom.php +++ b/src/Magento/FunctionalTestingFramework/Page/Config/SectionDom.php @@ -1,7 +1,7 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2018 Adobe + * All Rights Reserved. */ namespace Magento\FunctionalTestingFramework\Page\Config; @@ -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 caf2262fa..b8255bb9e 100644 --- a/src/Magento/FunctionalTestingFramework/Page/Handlers/PageObjectHandler.php +++ b/src/Magento/FunctionalTestingFramework/Page/Handlers/PageObjectHandler.php @@ -1,7 +1,7 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ namespace Magento\FunctionalTestingFramework\Page\Handlers; @@ -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; @@ -20,7 +22,8 @@ class PageObjectHandler implements ObjectHandlerInterface const MODULE = 'module'; const PARAMETERIZED = 'parameterized'; const AREA = 'area'; - const NAME_BLACKLIST_ERROR_MSG = "Page names cannot contain non alphanumeric characters.\tPage='%s'"; + const FILENAME = 'filename'; + const NAME_BLOCKLIST_ERROR_MSG = "Page names cannot contain non alphanumeric characters.\tPage='%s'"; /** * The singleton instance of this class @@ -52,24 +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)); } - $area = $pageData[self::AREA]; - $url = $pageData[self::URL]; - if ($area == 'admin') { + $filename = $pageData[self::FILENAME] ?? null; + $pageNameValidator->validateAffixes($pageName, NameValidationUtil::PAGE, $filename); + $area = $pageData[self::AREA] ?? null; + $url = $pageData[self::URL] ?? null; + + if ($area === 'admin') { $url = ltrim($url, "/"); } - $module = $pageData[self::MODULE]; + $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); + 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 459f4a9ef..091f31e48 100644 --- a/src/Magento/FunctionalTestingFramework/Page/Handlers/SectionObjectHandler.php +++ b/src/Magento/FunctionalTestingFramework/Page/Handlers/SectionObjectHandler.php @@ -1,7 +1,7 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ namespace Magento\FunctionalTestingFramework\Page\Handlers; @@ -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; @@ -22,6 +24,7 @@ class SectionObjectHandler implements ObjectHandlerInterface const LOCATOR_FUNCTION = 'locatorFunction'; const TIMEOUT = 'timeout'; const PARAMETERIZED = 'parameterized'; + const FILENAME = 'filename'; const SECTION_NAME_ERROR_MSG = "Section names cannot contain non alphanumeric characters.\tSection='%s'"; const ELEMENT_NAME_ERROR_MSG = "Element names cannot contain non alphanumeric characters.\tElement='%s'"; @@ -55,6 +58,8 @@ private function __construct() return; } + $sectionNameValidator = new NameValidationUtil(); + $elementNameValidator = new NameValidationUtil(); foreach ($parserOutput as $sectionName => $sectionData) { $elements = []; @@ -62,32 +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)); } - $elementType = $elementData[SectionObjectHandler::TYPE]; + + $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}'"); } - $this->sectionObjects[$sectionName] = new SectionObject($sectionName, $elements); + $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..b67a3a104 100644 --- a/src/Magento/FunctionalTestingFramework/Page/Objects/ElementObject.php +++ b/src/Magento/FunctionalTestingFramework/Page/Objects/ElementObject.php @@ -1,8 +1,9 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ + namespace Magento\FunctionalTestingFramework\Page\Objects; use Magento\FunctionalTestingFramework\Exceptions\XmlException; @@ -56,6 +57,13 @@ class ElementObject */ private $parameterized; + /** + * Deprecated message. + * + * @var string + */ + private $deprecated; + /** * ElementObject constructor. * @param string $name @@ -66,11 +74,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 +86,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 +161,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 3533a13f3..356925fd6 100644 --- a/src/Magento/FunctionalTestingFramework/Page/Objects/PageObject.php +++ b/src/Magento/FunctionalTestingFramework/Page/Objects/PageObject.php @@ -1,7 +1,7 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ namespace Magento\FunctionalTestingFramework\Page\Objects; @@ -58,23 +58,59 @@ class PageObject */ private $area; + /** + * Filename of where the page came from + * + * @var string + */ + 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 - */ - public function __construct($name, $url, $module, $sections, $parameterized, $area) - { + * @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; $this->sectionNames = $sections; $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; } /** @@ -87,6 +123,16 @@ public function getName() return $this->name; } + /** + * Getter for the Page Filename + * + * @return string + */ + public function getFilename() + { + return $this->filename; + } + /** * Getter for Page URL * diff --git a/src/Magento/FunctionalTestingFramework/Page/Objects/SectionObject.php b/src/Magento/FunctionalTestingFramework/Page/Objects/SectionObject.php index 9ac641bca..9cc0685c1 100644 --- a/src/Magento/FunctionalTestingFramework/Page/Objects/SectionObject.php +++ b/src/Magento/FunctionalTestingFramework/Page/Objects/SectionObject.php @@ -1,7 +1,7 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ namespace Magento\FunctionalTestingFramework\Page\Objects; @@ -25,15 +25,33 @@ class SectionObject */ private $elements = []; + /** + * Filename of where the section came from + * + * @var string + */ + private $filename; + + /** + * Deprecated message. + * + * @var string + */ + private $deprecated; + /** * SectionObject constructor. - * @param string $name - * @param array $elements + * @param string $name + * @param array $elements + * @param string|null $filename + * @param string|null $deprecated */ - public function __construct($name, $elements) + public function __construct($name, $elements, $filename = null, $deprecated = null) { $this->name = $name; $this->elements = $elements; + $this->filename = $filename; + $this->deprecated = $deprecated; } /** @@ -46,6 +64,26 @@ 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 + * + * @return string + */ + public function getFilename() + { + return $this->filename; + } + /** * Getter for an array containing all of a section's elements. * diff --git a/src/Magento/FunctionalTestingFramework/Page/etc/PageObject.xsd b/src/Magento/FunctionalTestingFramework/Page/etc/PageObject.xsd index c6f399a67..794470802 100644 --- a/src/Magento/FunctionalTestingFramework/Page/etc/PageObject.xsd +++ b/src/Magento/FunctionalTestingFramework/Page/etc/PageObject.xsd @@ -1,25 +1,16 @@ <?xml version="1.0" encoding="UTF-8"?> <!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ +/** + * Copyright 2017 Adobe + * All Rights Reserved. + */ --> <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..3c24109f9 100644 --- a/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd +++ b/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd @@ -1,21 +1,16 @@ <?xml version="1.0" encoding="UTF-8"?> <!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ +/** + * Copyright 2017 Adobe + * All Rights Reserved. + */ --> <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..e886b5940 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Page/etc/mergedPageObject.xsd @@ -0,0 +1,116 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> + +<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..cefc6a4e0 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Page/etc/mergedSectionObject.xsd @@ -0,0 +1,149 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> + +<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/ActionGroupStandardsCheck.php b/src/Magento/FunctionalTestingFramework/StaticCheck/ActionGroupStandardsCheck.php new file mode 100644 index 000000000..6f72dd35d --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/StaticCheck/ActionGroupStandardsCheck.php @@ -0,0 +1,259 @@ +<?php +/** + * Copyright 2022 Adobe + * All Rights Reserved. + */ + +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; +use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; + +/** + * Class ActionGroupArgumentsCheck + * @package Magento\FunctionalTestingFramework\StaticCheck + */ +class ActionGroupStandardsCheck implements StaticCheckInterface +{ + const ACTIONGROUP_NAME_REGEX_PATTERN = '/<actionGroup name=["\']([^\'"]*)/'; + const ERROR_LOG_FILENAME = 'mftf-standards-checks'; + const ERROR_LOG_MESSAGE = 'MFTF Action Group Unused Arguments Check'; + const STEP_KEY_REGEX_PATTERN = '/stepKey=["\']([^\'"]*)/'; + + /** + * 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) { + $actionGroupReferencesDataArray = []; + $actionGroupToArguments = []; + $contents = $filePath->getContents(); + preg_match_all( + self::STEP_KEY_REGEX_PATTERN, + preg_replace('/<!--(.|\s)*?-->/', '', $contents), + $actionGroupReferences + ); + foreach ($actionGroupReferences[0] as $actionGroupReferencesData) { + $actionGroupReferencesDataArray[] = trim( + str_replace(['stepKey', '='], [""], $actionGroupReferencesData) + ).'"'; + } + $duplicateStepKeys = array_unique( + array_diff_assoc( + $actionGroupReferencesDataArray, + array_unique( + $actionGroupReferencesDataArray + ) + ) + ); + unset($actionGroupReferencesDataArray); + if (isset($duplicateStepKeys) && count($duplicateStepKeys) > 0) { + throw new TestFrameworkException('Action group has duplicate step keys ' + .implode(",", array_unique($duplicateStepKeys))." File Path ".$filePath); + } + /** @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..af804005a --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/StaticCheck/AnnotationsCheck.php @@ -0,0 +1,262 @@ +<?php +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ + +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/ClassFileNamingCheck.php b/src/Magento/FunctionalTestingFramework/StaticCheck/ClassFileNamingCheck.php new file mode 100644 index 000000000..cda818078 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/StaticCheck/ClassFileNamingCheck.php @@ -0,0 +1,184 @@ +<?php +/** + * Copyright 2024 Adobe + * All Rights Reserved. + */ + +namespace Magento\FunctionalTestingFramework\StaticCheck; + +use Symfony\Component\Console\Input\InputInterface; +use Exception; +use Magento\FunctionalTestingFramework\Util\Script\ScriptUtil; +use Symfony\Component\Finder\SplFileInfo; + +/** + * Class ClassFileNamingCheck + * @package Magento\FunctionalTestingFramework\StaticCheck + */ +class ClassFileNamingCheck implements StaticCheckInterface +{ + const ERROR_LOG_FILENAME = 'mftf-class-file-naming-check'; + const ERROR_LOG_MESSAGE = 'MFTF Class File Naming Check'; + const ALLOW_LIST_FILENAME = 'class-file-naming-allowlist'; + const WARNING_LOG_FILENAME = 'mftf-class-file-naming-warnings'; + + /** + * Array containing all warnings found after running the execute() function. + * @var array + */ + private $warnings = []; + /** + * 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; + + /** + * @var array $allowFailureEntities + */ + private $allowFailureEntities = []; + + /** + * ScriptUtil instance + * + * @var ScriptUtil + */ + private $scriptUtil; + /** + * 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 = []; + $path = $input->getOption('path'); + if ($path) { + if (!realpath($path)) { + throw new \InvalidArgumentException('Invalid --path option: ' . $path); + } + $modulePaths[] = realpath($path); + } else { + $modulePaths = $this->scriptUtil->getAllModulePaths(); + } + foreach ($modulePaths as $modulePath) { + if (file_exists($modulePath . DIRECTORY_SEPARATOR . self::ALLOW_LIST_FILENAME)) { + $contents = file_get_contents($modulePath . DIRECTORY_SEPARATOR . self::ALLOW_LIST_FILENAME); + foreach (explode("\n", $contents) as $entity) { + $this->allowFailureEntities[$entity] = true; + } + } + } + $testXmlFiles = $this->scriptUtil->getModuleXmlFilesByScope($modulePaths, "Test"); + $actionGroupXmlFiles = $this->scriptUtil->getModuleXmlFilesByScope($modulePaths, "ActionGroup"); + $pageXmlFiles = $this->scriptUtil->getModuleXmlFilesByScope($modulePaths, "Page"); + $sectionXmlFiles = $this->scriptUtil->getModuleXmlFilesByScope($modulePaths, "Section"); + $suiteXmlFiles = $this->scriptUtil->getModuleXmlFilesByScope($modulePaths, 'Suite'); + $this->errors = []; + $this->errors += $this->findErrorsInFileSet($testXmlFiles, 'test'); + $this->errors += $this->findErrorsInFileSet($actionGroupXmlFiles, 'actionGroup'); + $this->errors += $this->findErrorsInFileSet($pageXmlFiles, 'page'); + $this->errors += $this->findErrorsInFileSet($sectionXmlFiles, 'section'); + $this->errors += $this->findErrorsInFileSet($suiteXmlFiles, 'suite'); + + // 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 + ); + if (!empty($this->warnings) && !empty($this->errors)) { + $this->output .= "\n " . $this->scriptUtil->printWarningsToFile( + $this->warnings, + StaticChecksList::getErrorFilesPath() . DIRECTORY_SEPARATOR . self::WARNING_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 errors found." + * @return string + */ + public function getOutput() + { + return $this->output; + } + + /** + * Returns Violations if found + * @param SplFileInfo $files + * @param string $fileType + * @return array + */ + public function findErrorsInFileSet($files, $fileType) + { + $errors = []; + /** @var SplFileInfo $filePath */ + + foreach ($files as $filePath) { + $fileNameWithoutExtension = pathinfo($filePath->getFilename(), PATHINFO_FILENAME); + $domDocument = new \DOMDocument(); + $domDocument->load($filePath); + $testResult = $this->getAttributesFromDOMNodeList( + $domDocument->getElementsByTagName($fileType), + ["type" => 'name'] + ); + if ($fileNameWithoutExtension != array_values($testResult[0])[0]) { + $isInAllowList = array_key_exists(array_values($testResult[0])[0], $this->allowFailureEntities); + if ($isInAllowList) { + $errorOutput = ucfirst($fileType). " name does not match with file name + {$filePath->getRealPath()}. ".ucfirst($fileType)." ".array_values($testResult[0])[0]; + $this->warnings[$filePath->getFilename()][] = $errorOutput; + continue; + } + $errorOutput = ucfirst($fileType). " name does not match with file name + {$filePath->getRealPath()}. ".ucfirst($fileType)." ".array_values($testResult[0])[0]; + $errors[$filePath->getFilename()][] = $errorOutput; + } + } + return $errors; + } + + /** + * Return attribute value for each node in DOMNodeList as an array + * + * @param DOMNodeList $nodes + * @param string $attributeName + * @return array + */ + public 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; + } +} diff --git a/src/Magento/FunctionalTestingFramework/StaticCheck/CreatedDataFromOutsideActionGroupCheck.php b/src/Magento/FunctionalTestingFramework/StaticCheck/CreatedDataFromOutsideActionGroupCheck.php new file mode 100644 index 000000000..2ac203216 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/StaticCheck/CreatedDataFromOutsideActionGroupCheck.php @@ -0,0 +1,198 @@ +<?php +/** + * Copyright 2022 Adobe + * All Rights Reserved. + */ + +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; + + /** + * @var array + */ + private $actionGroupXmlFile = []; + + /** + * 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..28cf49298 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/StaticCheck/DeprecatedEntityUsageCheck.php @@ -0,0 +1,722 @@ +<?php +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ + +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..15208eb17 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/StaticCheck/PauseActionUsageCheck.php @@ -0,0 +1,229 @@ +<?php +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ + +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/StaticCheckInterface.php b/src/Magento/FunctionalTestingFramework/StaticCheck/StaticCheckInterface.php new file mode 100644 index 000000000..f18e85cde --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/StaticCheck/StaticCheckInterface.php @@ -0,0 +1,34 @@ +<?php +/** + * Copyright 2019 Adobe + * All Rights Reserved. + */ + +namespace Magento\FunctionalTestingFramework\StaticCheck; + +use Symfony\Component\Console\Input\InputInterface; + +/** + * Static check script interface + */ +interface StaticCheckInterface +{ + /** + * Executes static check script, returns output. + * @param InputInterface $input + * @return void + */ + public function execute(InputInterface $input); + + /** + * Return array containing all errors found after running the execute() function. + * @return array + */ + 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(); +} diff --git a/src/Magento/FunctionalTestingFramework/StaticCheck/StaticCheckListInterface.php b/src/Magento/FunctionalTestingFramework/StaticCheck/StaticCheckListInterface.php new file mode 100644 index 000000000..8a8c39d65 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/StaticCheck/StaticCheckListInterface.php @@ -0,0 +1,21 @@ +<?php +/** + * Copyright 2019 Adobe + * All Rights Reserved. + */ + +namespace Magento\FunctionalTestingFramework\StaticCheck; + +/** + * Contains a list of Static Check Scripts + * @api + */ +interface StaticCheckListInterface +{ + /** + * Gets list of static check script instances + * + * @return StaticCheckInterface[] + */ + public function getStaticChecks(); +} diff --git a/src/Magento/FunctionalTestingFramework/StaticCheck/StaticChecksList.php b/src/Magento/FunctionalTestingFramework/StaticCheck/StaticChecksList.php new file mode 100644 index 000000000..30fa4f1b7 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/StaticCheck/StaticChecksList.php @@ -0,0 +1,102 @@ +<?php +/** + * Copyright 2019 Adobe + * All Rights Reserved. + */ + +declare(strict_types=1); + +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 CLASS_FILE_NAMING_CHECK = 'classFileNamingCheck'; + + const STATIC_RESULTS = 'tests' . DIRECTORY_SEPARATOR .'_output' . DIRECTORY_SEPARATOR . 'static-results'; + + /** + * Property contains all static check scripts. + * + * @var StaticCheckInterface[] + */ + 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 ActionGroupStandardsCheck(), + 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(), + self::CLASS_FILE_NAMING_CHECK => new ClassFileNamingCheck(), + + ] + $checks; + + // Static checks error files directory + if (null === self::$errorFilesPath) { + self::$errorFilesPath = FilePathFormatter::format(TESTS_BP) . self::STATIC_RESULTS; + } + } + + /** + * {@inheritdoc} + */ + 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 new file mode 100644 index 000000000..57b614323 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/StaticCheck/TestDependencyCheck.php @@ -0,0 +1,305 @@ +<?php +/** + * Copyright 2019 Adobe + * All Rights Reserved. + */ + +namespace Magento\FunctionalTestingFramework\StaticCheck; + +use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; +use Magento\FunctionalTestingFramework\Exceptions\XmlException; +use Magento\FunctionalTestingFramework\Test\Objects\ActionObject; +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 + */ +class TestDependencyCheck implements StaticCheckInterface +{ + const EXTENDS_REGEX_PATTERN = '/extends=["\']([^\'"]*)/'; + const ACTIONGROUP_REGEX_PATTERN = '/ref=["\']([^\'"]*)/'; + + const ERROR_LOG_FILENAME = 'mftf-dependency-checks-errors'; + const ERROR_LOG_MESSAGE = 'MFTF File Dependency Check'; + const WARNING_LOG_FILENAME = 'mftf-dependency-checks-warnings'; + + const ALLOW_LIST_FILENAME = 'test-dependency-allowlist'; + + /** + * Array of FullModuleName => [dependencies], including flattened dependency tree + * @var array + */ + private $flattenedDependencies; + + /** + * Array of FullModuleName => PathToModule + * @var array + */ + private $moduleNameToPath; + + /** + * Array of FullModuleName => ComposerModuleName + * @var array + */ + private $moduleNameToComposerName; + + /** + * Array containing all errors found after running the execute() function. + * @var array + */ + private $errors = []; + + /** + * Array containing all warnings found after running the execute() function. + * @var array + */ + private $warnings = []; + /** + * Array containing warnings found while iterating through files + * @var array + */ + private $tempWarnings = []; + + /** + * String representing the output summary found after running the execute() function. + * @var string + */ + private $output; + + /** + * Array containing all entities after resolving references. + * @var array + */ + private $allEntities = []; + + /** + * ScriptUtil instance + * + * @var ScriptUtil + */ + private $scriptUtil; + + /** + * @var TestDependencyUtil + */ + private $testDependencyUtil; + + /** + * @var array $allowFailureEntities + */ + private $allowFailureEntities = []; + + /** + * 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->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." + ); + } + + // Build array of entities found in allow-list files + // Expect one entity per file line, no commas or anything else + foreach ($allModules as $modulePath) { + if (file_exists($modulePath . DIRECTORY_SEPARATOR . self::ALLOW_LIST_FILENAME)) { + $contents = file_get_contents($modulePath . DIRECTORY_SEPARATOR . self::ALLOW_LIST_FILENAME); + foreach (explode("\n", $contents) as $entity) { + $this->allowFailureEntities[$entity] = true; + } + } + } + + $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 + ); + + $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->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); + $this->errors += $this->findErrorsInFileSet($actionGroupXmlFiles); + $this->errors += $this->findErrorsInFileSet($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 + ); + if (!empty($this->warnings) && !empty($this->errors)) { + $this->output .= "\n " . $this->scriptUtil->printWarningsToFile( + $this->warnings, + StaticChecksList::getErrorFilesPath() . DIRECTORY_SEPARATOR . self::WARNING_LOG_FILENAME . '.txt', + self::ERROR_LOG_MESSAGE + ); + } + } + + /** + * Return array containing all errors found after running the execute() function. + * @return array + */ + public function getErrors(): array + { + 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(): string + { + return $this->output??""; + } + + /** + * Finds all reference errors in given set of files + * @param Finder $files + * @return array + * @throws XmlException + */ + private function findErrorsInFileSet(Finder $files): array + { + $testErrors = []; + foreach ($files as $filePath) { + $this->allEntities = []; + $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 + $this->allEntities = array_merge( + $this->allEntities, + $this->scriptUtil->resolveEntityReferences($braceReferences[0], $contents) + ); + + // resolve parameterized references + $this->allEntities = array_merge( + $this->allEntities, + $this->scriptUtil->resolveParametrizedReferences($braceReferences[2], $contents) + ); + + // resolve entity by names + $this->allEntities = array_merge( + $this->allEntities, + $this->scriptUtil->resolveEntityByNames($actionGroupReferences[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($moduleName); + $testErrors = array_merge($testErrors, $this->setErrorOutput($violatingReferences, $filePath)); + $this->warnings = array_merge($this->warnings, $this->setErrorOutput($this->tempWarnings, $filePath)); + } + return $testErrors; + } + + /** + * Find violating references + * + * @param string $moduleName + * @return array + */ + private function findViolatingReferences(string $moduleName): array + { + // Find Violations + $violatingReferences = []; + $currentModule = $this->moduleNameToComposerName[$moduleName]; + $modulesReferencedInTest = $this->testDependencyUtil->getModuleDependenciesFromReferences( + $this->allEntities, + $this->moduleNameToComposerName, + $this->moduleNameToPath + ); + $moduleDependencies = $this->flattenedDependencies[$moduleName]; + foreach ($modulesReferencedInTest as $entityName => $files) { + $isInAllowList = array_key_exists($entityName, $this->allowFailureEntities); + $valid = false; + foreach ($files as $module) { + if (array_key_exists($module, $moduleDependencies) || $module === $currentModule) { + $valid = true; + break; + } + } + if (!$valid) { + if ($isInAllowList) { + $this->tempWarnings[$entityName] = $files; + continue; + } + $violatingReferences[$entityName] = $files; + } + } + + return $violatingReferences; + } + + /** + * Builds and returns error output for violating references + * + * @param array $violatingReferences + * @return array + */ + private function setErrorOutput(array $violatingReferences, $path): array + { + $testErrors = []; + + if (!empty($violatingReferences)) { + // Build error output + $errorOutput = "\nFile \"{$path->getRealPath()}\""; + $errorOutput .= "\ncontains entity references that violate dependency constraints:\n\t\t"; + foreach ($violatingReferences as $entityName => $files) { + $errorOutput .= "\n\t {$entityName} from module(s): " . implode(", ", $files); + } + $testErrors[$path->getRealPath()][] = $errorOutput; + } + + return $testErrors; + } +} diff --git a/src/Magento/FunctionalTestingFramework/StaticCheck/UnusedEntityCheck.php b/src/Magento/FunctionalTestingFramework/StaticCheck/UnusedEntityCheck.php new file mode 100644 index 000000000..5cf306568 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/StaticCheck/UnusedEntityCheck.php @@ -0,0 +1,644 @@ +<?php +/** + * Copyright 2022 Adobe + * All Rights Reserved. + */ + +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; + + /** + * @var array + */ + private $errors = []; + + /** + * @var string + */ + private $output = ''; + + /** + * 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 string + */ + public function getOutput() + { + return $this->output; + } +} diff --git a/src/Magento/FunctionalTestingFramework/Stdlib/BooleanUtils.php b/src/Magento/FunctionalTestingFramework/Stdlib/BooleanUtils.php index 70f933ec6..8cece9259 100644 --- a/src/Magento/FunctionalTestingFramework/Stdlib/BooleanUtils.php +++ b/src/Magento/FunctionalTestingFramework/Stdlib/BooleanUtils.php @@ -1,8 +1,9 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ + namespace Magento\FunctionalTestingFramework\Stdlib; /** 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..96c589fde --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Suite/Config/SuiteDom.php @@ -0,0 +1,84 @@ +<?php +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ + +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 a84f6b1b3..09c0a745f 100644 --- a/src/Magento/FunctionalTestingFramework/Suite/Generators/GroupClassGenerator.php +++ b/src/Magento/FunctionalTestingFramework/Suite/Generators/GroupClassGenerator.php @@ -1,11 +1,12 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ 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,13 +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'); - $rawPhp = str_replace(["\t", "\n"], "", $step); - $multipleCommands = explode(";", $rawPhp, -1); + $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 . ";", $actionEntries, 'webDriver'); + $actionEntries = $this->replaceReservedTesterFunctions( + $command . PHP_EOL, + $actionEntries, + self::FUNCTION_PLACEHOLDER + ); } return $actionEntries; @@ -192,13 +210,24 @@ private function buildWebDriverActionsMustacheArray($action, $actionEntries) */ private function replaceReservedTesterFunctions($formattedStep, $actionEntries, $actor) { + $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]; } } @@ -219,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; } @@ -249,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 d4b044cf8..3867571f7 100644 --- a/src/Magento/FunctionalTestingFramework/Suite/Handlers/SuiteObjectHandler.php +++ b/src/Magento/FunctionalTestingFramework/Suite/Handlers/SuiteObjectHandler.php @@ -1,10 +1,15 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ + 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 +17,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 @@ -23,7 +30,7 @@ class SuiteObjectHandler implements ObjectHandlerInterface * * @var SuiteObjectHandler */ - private static $SUITE_OBJECT_HANLDER_INSTANCE; + private static $instance; /** * Array of suite objects keyed by suite name. @@ -33,27 +40,35 @@ class SuiteObjectHandler implements ObjectHandlerInterface private $suiteObjects; /** - * SuiteObjectHandler constructor. + * Avoids instantiation of SuiteObjectHandler by new. + * @return void */ private function __construct() { - // empty constructor + } + + /** + * Avoids instantiation of SuiteObjectHandler by clone. + * @return void + */ + private function __clone() + { } /** * Function to enforce singleton design pattern * * @return ObjectHandlerInterface - * @throws XmlException + * @throws FastFailException */ - public static function getInstance() + public static function getInstance(): ObjectHandlerInterface { - if (self::$SUITE_OBJECT_HANLDER_INSTANCE == null) { - self::$SUITE_OBJECT_HANLDER_INSTANCE = new SuiteObjectHandler(); - self::$SUITE_OBJECT_HANLDER_INSTANCE->initSuiteData(); + if (self::$instance === null) { + self::$instance = new SuiteObjectHandler(); + self::$instance->initSuiteData(); } - return self::$SUITE_OBJECT_HANLDER_INSTANCE; + return self::$instance; } /** @@ -62,10 +77,12 @@ public static function getInstance() * @param string $objectName * @return SuiteObject */ - public function getObject($objectName) + 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]; } @@ -75,7 +92,7 @@ public function getObject($objectName) * * @return array */ - public function getAllObjects() + public function getAllObjects(): array { return $this->suiteObjects; } @@ -84,8 +101,9 @@ public function getAllObjects() * Function which return all tests referenced by suites. * * @return array + * @throws TestFrameworkException */ - public function getAllTestReferences() + public function getAllTestReferences(): array { $testsReferencedInSuites = []; $suites = $this->getAllObjects(); @@ -105,11 +123,16 @@ public function getAllTestReferences() * * @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..6fe3cbef9 100644 --- a/src/Magento/FunctionalTestingFramework/Suite/Objects/SuiteObject.php +++ b/src/Magento/FunctionalTestingFramework/Suite/Objects/SuiteObject.php @@ -1,12 +1,17 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ + 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 +46,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 +84,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 +99,7 @@ public function getTests() * @param TestObject[] $includeTests * @param TestObject[] $excludeTests * @return TestObject[] + * @throws TestFrameworkException */ private function resolveTests($includeTests, $excludeTests) { @@ -95,12 +111,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 +160,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/Parsers/SuiteDataParser.php b/src/Magento/FunctionalTestingFramework/Suite/Parsers/SuiteDataParser.php index eb10fb88d..2d5971d03 100644 --- a/src/Magento/FunctionalTestingFramework/Suite/Parsers/SuiteDataParser.php +++ b/src/Magento/FunctionalTestingFramework/Suite/Parsers/SuiteDataParser.php @@ -1,8 +1,9 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ + namespace Magento\FunctionalTestingFramework\Suite\Parsers; use Magento\FunctionalTestingFramework\Config\DataInterface; diff --git a/src/Magento/FunctionalTestingFramework/Suite/Service/SuiteGeneratorService.php b/src/Magento/FunctionalTestingFramework/Suite/Service/SuiteGeneratorService.php new file mode 100644 index 000000000..d875ccde9 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Suite/Service/SuiteGeneratorService.php @@ -0,0 +1,161 @@ +<?php +/** + * Copyright 2021 Adobe + * All Rights Reserved. + */ + +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 7f3efdbfc..b4a395361 100644 --- a/src/Magento/FunctionalTestingFramework/Suite/SuiteGenerator.php +++ b/src/Magento/FunctionalTestingFramework/Suite/SuiteGenerator.php @@ -1,23 +1,32 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ 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'; @@ -33,7 +42,7 @@ class SuiteGenerator * * @var SuiteGenerator */ - private static $SUITE_GENERATOR_INSTANCE; + private static $instance; /** * Group Class Generator initialized in constructor. @@ -43,28 +52,37 @@ class SuiteGenerator private $groupClassGenerator; /** - * SuiteGenerator constructor. + * Avoids instantiation of LoggingUtil by new. + * @return void */ private function __construct() { $this->groupClassGenerator = new GroupClassGenerator(); } + /** + * Avoids instantiation of SuiteGenerator by clone. + * @return void + */ + private function __clone() + { + } + /** * Singleton method which is used to retrieve the instance of the suite generator. * * @return SuiteGenerator */ - public static function getInstance() + public static function getInstance(): SuiteGenerator { - if (!self::$SUITE_GENERATOR_INSTANCE) { + if (!self::$instance) { // clear any previous configurations before any generation occurs. self::clearPreviousGroupPreconditions(); self::clearPreviousSessionConfigEntries(); - self::$SUITE_GENERATOR_INSTANCE = new SuiteGenerator(); + self::$instance = new SuiteGenerator(); } - return self::$SUITE_GENERATOR_INSTANCE; + return self::$instance; } /** @@ -73,23 +91,36 @@ public static function getInstance() * * @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) { } } } @@ -108,6 +139,108 @@ public function generateSuite($suiteName) $this->generateSuiteFromTest($suiteName, []); } + /** + * Function which generate Testgroupmembership file. + * + * @param object $testManifest + * @return void + * @throws \Exception + */ + public function generateTestgroupmembership($testManifest): void + { + $suites = $this->getSuitesDetails($testManifest); + + // Path to groups folder + $baseDir = FilePathFormatter::format(TESTS_MODULE_PATH); + $path = $baseDir .'_generated/groups'; + + $allGroupsContent = $this->readAllGroupFiles($path); + + // Output file path + $memberShipFilePath = $baseDir.'_generated/testgroupmembership.txt'; + $testCaseNumber = 0; + + if (!empty($allGroupsContent)) { + foreach ($allGroupsContent as $groupId => $groupInfo) { + foreach ($groupInfo as $testName) { + // If file has -g then it is test suite + if (str_contains($testName, '-g')) { + $suitename = explode(" ", $testName); + $suitename[1] = trim($suitename[1]); + + if (!empty($suites[$suitename[1]])) { + foreach ($suites[$suitename[1]] as $key => $test) { + $suiteTest = sprintf('%s:%s:%s:%s', $groupId, $key, $suitename[1], $test); + file_put_contents($memberShipFilePath, $suiteTest . PHP_EOL, FILE_APPEND); + } + } + } else { + $defaultSuiteTest = sprintf('%s:%s:%s', $groupId, $testCaseNumber, $testName); + file_put_contents($memberShipFilePath, $defaultSuiteTest, FILE_APPEND); + } + $testCaseNumber++; + } + $testCaseNumber = 0; + } + } + } + + /** + * Function to format suites details + * + * @param object $testManifest + * @return array $suites + */ + private function getSuitesDetails($testManifest): array + { + // Get suits and subsuites data array + $suites = $testManifest->getSuiteConfig(); + + // Add subsuites array[2nd dimension] to main array[1st dimension] to access it directly later + if (!empty($suites)) { + foreach ($suites as $subSuites) { + if (!empty($subSuites)) { + foreach ($subSuites as $subSuiteName => $suiteTestNames) { + if (!is_numeric($subSuiteName)) { + $suites[$subSuiteName] = $suiteTestNames; + } else { + continue; + } + } + } + } + } + return $suites; + } + + /** + * Function to read all group* text files inside /groups folder + * + * @param object $path + * @return array $allGroupsContent + */ + private function readAllGroupFiles($path): array + { + // Read all group files + if (is_dir($path)) { + $groupFiles = glob("$path/group*.txt"); + if ($groupFiles === false) { + throw new RuntimeException("glob(): error with '$path'"); + } + sort($groupFiles, SORT_NATURAL); + } + + // Read each file in the reverse order and form an array with groupId as key + $groupNumber = 0; + $allGroupsContent = []; + while (!empty($groupFiles)) { + $group = array_pop($groupFiles); + $allGroupsContent[$groupNumber] = file($group); + $groupNumber++; + } + return $allGroupsContent; + } + /** * Function which takes a suite name and a set of test names. The function then generates all relevant supporting * files and classes for the suite. The function takes an optional argument for suites which are split by a parallel @@ -117,34 +250,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}\"" + ); + } + + $groupNamespace = $this->generateGroupFile($suiteName, $relevantTests, $originalSuiteName); + + $this->appendEntriesToConfig($suiteName, $fullPath, $groupNamespace); - $this->appendEntriesToConfig($suiteName, $fullPath, $groupNamespace); - LoggingUtil::getInstance()->getLogger(SuiteGenerator::class)->info( - "suite generated", - ['suite' => $suiteName, 'relative_path' => $relativePath] - ); + 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); } /** @@ -162,12 +343,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]); } } @@ -183,7 +366,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 + } } } @@ -214,7 +406,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); } } @@ -240,21 +432,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); } /** @@ -265,43 +443,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); } /** @@ -316,32 +474,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 642671246..db968e5f8 100644 --- a/src/Magento/FunctionalTestingFramework/Suite/Util/SuiteObjectExtractor.php +++ b/src/Magento/FunctionalTestingFramework/Suite/Util/SuiteObjectExtractor.php @@ -1,11 +1,14 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ + 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 +16,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,16 +36,22 @@ 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'; + /** + * TestHookObjectExtractor initialized in constructor. + * + * @var TestHookObjectExtractor + */ + private $testHookObjectExtractor; + /** * SuiteObjectExtractor constructor */ public function __construct() { - // empty constructor + $this->testHookObjectExtractor = new TestHookObjectExtractor(); } /** @@ -39,15 +59,15 @@ public function __construct() * * @param array $parsedSuiteData * @return array - * @throws XmlException - * @SuppressWarnings(PHPMD.CyclomaticComplexity) + * @throws FastFailException + * * @SuppressWarnings(PHPMD.NPathComplexity) - * @throws \Exception + * @SuppressWarnings(PHPMD.CyclomaticComplexity) + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ public function parseSuiteDataIntoObjects($parsedSuiteData) { $suiteObjects = []; - $testHookObjectExtractor = new TestHookObjectExtractor(); // make sure there are suites defined before trying to parse as objects. if (!array_key_exists(self::SUITE_ROOT_TAG, $parsedSuiteData)) { @@ -60,74 +80,84 @@ public function parseSuiteDataIntoObjects($parsedSuiteData) continue; } - // validate the name used isn't 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\""); - } + $this->validateSuiteName($parsedSuite); - $suiteHooks = []; + try { + // extract include and exclude references + $groupTestsToInclude = $parsedSuite[self::INCLUDE_TAG_NAME] ?? []; + $groupTestsToExclude = $parsedSuite[self::EXCLUDE_TAG_NAME] ?? []; - //Check for collisions between suite name and existing group name - $suiteName = $parsedSuite[self::NAME]; - $testGroupConflicts = TestObjectHandler::getInstance()->getTestsByGroup($suiteName); - if (!empty($testGroupConflicts)) { - $testGroupConflictsFileNames = ""; - foreach ($testGroupConflicts as $test) { - $testGroupConflictsFileNames .= $test->getFilename() . "\n"; + // 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]; } - $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); - } - //extract include and exclude references - $groupTestsToInclude = $parsedSuite[self::INCLUDE_TAG_NAME] ?? []; - $groupTestsToExclude = $parsedSuite[self::EXCLUDE_TAG_NAME] ?? []; + // it's ok if failed in exclude + $exclude = $this->extractTestObjectsFromSuiteRef($groupTestsToExclude); + $excludeTests = $exclude['objects'] ?? []; - // resolve references as test objects - $includeTests = $this->extractTestObjectsFromSuiteRef($groupTestsToInclude); - $excludeTests = $this->extractTestObjectsFromSuiteRef($groupTestsToExclude); + // parse any object hooks + $suiteHooks = $this->parseObjectHooks($parsedSuite); - // parse any object hooks - if (array_key_exists(TestObjectExtractor::TEST_BEFORE_HOOK, $parsedSuite)) { - $suiteHooks[TestObjectExtractor::TEST_BEFORE_HOOK] = $testHookObjectExtractor->extractHook( - $parsedSuite[self::NAME], - TestObjectExtractor::TEST_BEFORE_HOOK, - $parsedSuite[TestObjectExtractor::TEST_BEFORE_HOOK] - ); - } - if (array_key_exists(TestObjectExtractor::TEST_AFTER_HOOK, $parsedSuite)) { - $suiteHooks[TestObjectExtractor::TEST_AFTER_HOOK] = $testHookObjectExtractor->extractHook( - $parsedSuite[self::NAME], - TestObjectExtractor::TEST_AFTER_HOOK, - $parsedSuite[TestObjectExtractor::TEST_AFTER_HOOK] + // 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"); + } - 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] - )); - } - // check if suite hooks are empty/not included and there are no included tests/groups/modules - $noHooks = count($suiteHooks) == 0 || - ( - empty($suiteHooks['before']->getActions()) && - empty($suiteHooks['after']->getActions()) + GenerationErrorHandler::getInstance()->addError( + 'suite', + $parsedSuite[self::NAME], + self::class . ': Unable to parse suite ' . $e->getMessage() ); - // if suite body is empty throw error - if ($noHooks && empty($includeTests) && empty($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 @@ -143,122 +173,175 @@ public function parseSuiteDataIntoObjects($parsedSuiteData) } /** - * Wrapper method for resolving suite reference data, checks type of suite reference and calls corresponding - * resolver for each suite reference. + * Throws exception for suite names meeting the below conditions: + * 1. the name used is using special char or the "default" reserved name + * 2. collisions between suite name and existing group name * - * @param array $suiteReferences - * @return array - * @throws \Exception + * @param array $parsedSuite + * @return void + * @throws FastFailException */ - private function extractTestObjectsFromSuiteRef($suiteReferences) + private function validateSuiteName($parsedSuite) { - $testObjectList = []; - foreach ($suiteReferences as $suiteRefName => $suiteRefData) { - if (!is_array($suiteRefData)) { - continue; - } + //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 FastFailException("A Suite can not have the name \"default\""); + } - 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; + $suiteName = $parsedSuite[self::NAME]; + //check for collisions between suite and existing group names + $testGroupConflicts = TestObjectHandler::getInstance()->getTestsByGroup($suiteName); + if (!empty($testGroupConflicts)) { + $testGroupConflictsFileNames = ""; + foreach ($testGroupConflicts as $test) { + $testGroupConflictsFileNames .= $test->getFilename() . "\n"; } + $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 FastFailException($exceptionmessage); } - - return $testObjectList; } /** - * Takes an array of modules/files and resolves to an array of test objects. + * Parse object hooks * - * @param string $moduleName - * @param string $moduleFilePath + * @param array $parsedSuite * @return array - * @throws \Exception + * @throws XmlException + * @throws TestReferenceException */ - private function extractModuleAndFiles($moduleName, $moduleFilePath) + private function parseObjectHooks($parsedSuite) { - if (empty($moduleFilePath)) { - return $this->resolveModulePathTestNames($moduleName); + $suiteHooks = []; + + if (array_key_exists(TestObjectExtractor::TEST_BEFORE_HOOK, $parsedSuite)) { + $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)) { + $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; } - return $this->resolveFilePathTestNames($moduleFilePath, $moduleName); + 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] + )); + } + return $suiteHooks; } /** - * Takes a filepath (and optionally a module name) and resolves to a test object. + * Check if suite hooks are empty/not included and there are no included tests/groups/modules * - * @param string $filename - * @param null $moduleName - * @return TestObject[] - * @throws Exception + * @param array $suiteHooks + * @param array $includeTests + * @param array $excludeTests + * @return boolean */ - private function resolveFilePathTestNames($filename, $moduleName = null) + private function isSuiteEmpty($suiteHooks, $includeTests, $excludeTests) { - $filepath = $filename; - if (!strstr($filepath, DIRECTORY_SEPARATOR)) { - $filepath = TESTS_MODULE_PATH . - DIRECTORY_SEPARATOR . - $moduleName . - DIRECTORY_SEPARATOR . - 'Test' . - DIRECTORY_SEPARATOR . - $filename; - } + $noHooks = count($suiteHooks) === 0 || + ( + empty($suiteHooks['before']->getActions()) && + empty($suiteHooks['after']->getActions()) + ); - if (!file_exists($filepath)) { - throw new Exception("Could not find file ${filename}"); + if ($noHooks && empty($includeTests) && empty($excludeTests)) { + return true; } + return false; + } - $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); + /** + * Wrapper method for resolving suite reference data, checks type of suite reference and calls corresponding + * resolver for each suite reference. + * + * @param array $suiteReferences + * @return array + * @throws FastFailException + */ + private function extractTestObjectsFromSuiteRef($suiteReferences) + { + $testObjectList = []; + $errCount = 0; + foreach ($suiteReferences as $suiteRefName => $suiteRefData) { + if (!is_array($suiteRefData)) { + continue; + } + + 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 $testObjects; + return [ + 'status' => $errCount, + 'objects' => $testObjectList + ]; } /** - * Takes a single module name and resolves to an array of tests contained within specified module. + * Return all test objects for a module * * @param string $moduleName - * @return array - * @throws \Exception + * @return TestObject[] + * @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..9a1891879 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Suite/etc/mergedSuiteSchema.xsd @@ -0,0 +1,72 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> + +<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..b737140ce 100644 --- a/src/Magento/FunctionalTestingFramework/Suite/etc/suiteSchema.xsd +++ b/src/Magento/FunctionalTestingFramework/Suite/etc/suiteSchema.xsd @@ -1,70 +1,17 @@ <?xml version="1.0" encoding="UTF-8"?> <!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ +/** + * Copyright 2017 Adobe + * All Rights Reserved. + */ --> <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 953db9d0c..599b5791e 100644 --- a/src/Magento/FunctionalTestingFramework/Suite/views/SuiteClass.mustache +++ b/src/Magento/FunctionalTestingFramework/Suite/views/SuiteClass.mustache @@ -2,7 +2,17 @@ namespace Group; +use Facebook\WebDriver\Remote\RemoteWebDriver; +use Facebook\WebDriver\Remote\DesiredCapabilities; 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; +use Facebook\WebDriver\Chrome\ChromeOptions; /** * Group class is Codeception Extension which is allowed to handle to all internal events. @@ -19,40 +29,59 @@ 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(); if ($this->preconditionFailure != null) { //if our preconditions fail, we need to mark all the tests as incomplete. - $e->getTest()->getMetadata()->setIncomplete($this->preconditionFailure); + $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 { {{> 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"); } } @@ -64,7 +93,6 @@ class {{suiteName}} extends \Codeception\GroupObject $this->executePostConditions($e); } - private function executePostConditions(\Codeception\Event\TestEvent $e) { if ($this->currentTestRun == $this->testCount) { @@ -77,7 +105,7 @@ class {{suiteName}} extends \Codeception\GroupObject //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(); + return $cest->getResultAggregator(); }, $cest )); @@ -85,7 +113,7 @@ class {{suiteName}} extends \Codeception\GroupObject if (!empty($errors)) { foreach ($errors as $error) { - if ($error->failedTest()->getTestMethod() == $cest->getName()) { + if ($error->getTest()->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"); @@ -98,8 +126,85 @@ 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) { + try { + $chromeOptions = new ChromeOptions(); + $capabilities = DesiredCapabilities::chrome(); + $remoteWebDriver = RemoteWebDriver::createBySessionID($session['id'], $wdHost, + $webDriverConfig['connection_timeout'], $webDriverConfig['request_timeout'], true, $capabilities); + $remoteWebDriver->quit(); + } catch (\Exception $exception) { + print("Failed trying to quit WebDriver session. Exception message: " . $exception->getMessage() . " Test execution will continue." . PHP_EOL); + // Session already closed so nothing to do + } + } + } + } + + /** + * 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; + } +} \ No newline at end of file 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..b48b81770 100644 --- a/src/Magento/FunctionalTestingFramework/System/Code/ClassReader.php +++ b/src/Magento/FunctionalTestingFramework/System/Code/ClassReader.php @@ -1,7 +1,7 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ namespace Magento\FunctionalTestingFramework\System\Code; @@ -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 86ecc6d0d..f5fd2955b 100644 --- a/src/Magento/FunctionalTestingFramework/Test/Config/ActionGroupDom.php +++ b/src/Magento/FunctionalTestingFramework/Test/Config/ActionGroupDom.php @@ -1,8 +1,9 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2018 Adobe + * All Rights Reserved. */ + namespace Magento\FunctionalTestingFramework\Test\Config; use Magento\FunctionalTestingFramework\Exceptions\Collector\ExceptionCollector; @@ -29,12 +30,27 @@ public function initDom($xml, $filename = null) { $dom = parent::initDom($xml, $filename); - if (strpos($filename, self::ACTION_GROUP_FILE_NAME_ENDING)) { - $actionGroupNodes = $dom->getElementsByTagName('actionGroup'); - foreach ($actionGroupNodes as $actionGroupNode) { + if ($this->checkFilenameSuffix($filename, self::ACTION_GROUP_FILE_NAME_ENDING)) { + $actionGroupsNode = $dom->getElementsByTagName('actionGroups')[0]; + + $this->testsValidationUtil->validateChildUniqueness( + $actionGroupsNode, + $filename, + null + ); + + // 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->validationUtil->validateChildUniqueness( + $this->actionsValidationUtil->validateChildUniqueness( $actionGroupNode, $filename, $actionGroupNode->getAttribute(self::ACTION_GROUP_META_NAME_ATTRIBUTE) diff --git a/src/Magento/FunctionalTestingFramework/Test/Config/Converter/Dom/Flat.php b/src/Magento/FunctionalTestingFramework/Test/Config/Converter/Dom/Flat.php index dd72264b9..d1d43f656 100644 --- a/src/Magento/FunctionalTestingFramework/Test/Config/Converter/Dom/Flat.php +++ b/src/Magento/FunctionalTestingFramework/Test/Config/Converter/Dom/Flat.php @@ -1,7 +1,7 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ namespace Magento\FunctionalTestingFramework\Test\Config\Converter\Dom; @@ -18,6 +18,7 @@ class Flat implements ConverterInterface const REMOVE_KEY_ATTRIBUTE = 'keyForRemoval'; const EXTENDS_ATTRIBUTE = 'extends'; const TEST_HOOKS = ['before', 'after']; + const VALID_COMMENT_PARENT = ['test', 'before', 'after', 'actionGroup']; /** * Array node configuration. @@ -69,13 +70,14 @@ public function convert($source) * @return string|array * @throws \UnexpectedValueException * @SuppressWarnings(PHPMD.CyclomaticComplexity) + * Revisited to reduce cyclomatic complexity, left unrefactored for readability */ 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); @@ -88,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')); @@ -119,11 +121,18 @@ 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 && + in_array($node->parentNode->nodeName, self::VALID_COMMENT_PARENT)) { + $uniqid = uniqid($node->nodeName); + $value[$uniqid] = [ + 'value' => trim($node->nodeValue), + 'nodeName' => $node->nodeName, + ]; } } $result = $this->getNodeAttributes($source); @@ -154,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 2909cf2d2..af8f48275 100644 --- a/src/Magento/FunctionalTestingFramework/Test/Config/Dom.php +++ b/src/Magento/FunctionalTestingFramework/Test/Config/Dom.php @@ -1,7 +1,7 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2018 Adobe + * All Rights Reserved. */ namespace Magento\FunctionalTestingFramework\Test\Config; @@ -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 @@ -19,7 +20,7 @@ */ class Dom extends \Magento\FunctionalTestingFramework\Config\MftfDom { - const TEST_FILE_NAME_ENDING = 'Test'; + const TEST_FILE_NAME_ENDING = 'Test.xml'; const TEST_META_FILENAME_ATTRIBUTE = 'filename'; const TEST_META_NAME_ATTRIBUTE = 'name'; const TEST_HOOK_NAMES = ["after", "before"]; @@ -27,10 +28,22 @@ class Dom extends \Magento\FunctionalTestingFramework\Config\MftfDom const TEST_MERGE_POINTER_AFTER = "insertAfter"; /** - * NodeValidationUtil + * NodeValidationUtil for test actions * @var DuplicateNodeValidationUtil */ - protected $validationUtil; + protected $actionsValidationUtil; + + /** + * NodeValidationUtil for test names + * @var DuplicateNodeValidationUtil + */ + protected $testsValidationUtil; + + /** + * SingleNodePerFileValidationUtil + * @var SingleNodePerFileValidationUtil + */ + protected $singleNodePerFileValidationUtil; /** * ExceptionCollector @@ -57,7 +70,9 @@ public function __construct( $schemaFile = null, $errorFormat = self::ERROR_FORMAT_DEFAULT ) { - $this->validationUtil = new DuplicateNodeValidationUtil('stepKey', $exceptionCollector); + $this->actionsValidationUtil = new DuplicateNodeValidationUtil('stepKey', $exceptionCollector); + $this->testsValidationUtil = new DuplicateNodeValidationUtil('name', $exceptionCollector); + $this->singleNodePerFileValidationUtil = new SingleNodePerFileValidationUtil($exceptionCollector); $this->exceptionCollector = $exceptionCollector; parent::__construct( $xml, @@ -81,10 +96,24 @@ public function initDom($xml, $filename = null) { $dom = parent::initDom($xml, $filename); - if (strpos($filename, self::TEST_FILE_NAME_ENDING)) { - $testNodes = $dom->getElementsByTagName('test'); - foreach ($testNodes as $testNode) { + // Cannot rely on filename to ensure this file is a Test.xml + if ($dom->getElementsByTagName('tests')->length > 0) { + $testsNode = $dom->getElementsByTagName('tests')[0]; + $this->testsValidationUtil->validateChildUniqueness( + $testsNode, + $filename, + null + ); + // 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( @@ -102,7 +131,7 @@ public function initDom($xml, $filename = null) ); } - $this->validationUtil->validateChildUniqueness( + $this->actionsValidationUtil->validateChildUniqueness( $testNode, $filename, $testNode->getAttribute(self::TEST_META_NAME_ATTRIBUTE) @@ -111,14 +140,14 @@ public function initDom($xml, $filename = null) $afterNode = $testNode->getElementsByTagName('after')->item(0); if (isset($beforeNode)) { - $this->validationUtil->validateChildUniqueness( + $this->actionsValidationUtil->validateChildUniqueness( $beforeNode, $filename, $testNode->getAttribute(self::TEST_META_NAME_ATTRIBUTE) . "/before" ); } if (isset($afterNode)) { - $this->validationUtil->validateChildUniqueness( + $this->actionsValidationUtil->validateChildUniqueness( $afterNode, $filename, $testNode->getAttribute(self::TEST_META_NAME_ATTRIBUTE) . "/after" @@ -144,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 994467bd3..6328e6a04 100644 --- a/src/Magento/FunctionalTestingFramework/Test/Handlers/ActionGroupObjectHandler.php +++ b/src/Magento/FunctionalTestingFramework/Test/Handlers/ActionGroupObjectHandler.php @@ -1,17 +1,20 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ + 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 @@ -21,13 +24,14 @@ 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 * * @var ActionGroupObjectHandler */ - private static $ACTION_GROUP_OBJECT_HANDLER; + private static $instance; /** * Array of action groups indexed by name @@ -47,23 +51,25 @@ class ActionGroupObjectHandler implements ObjectHandlerInterface * Singleton getter for instance of ActionGroupObjectHandler * * @return ActionGroupObjectHandler + * @throws XmlException */ - public static function getInstance() + public static function getInstance(): ActionGroupObjectHandler { - if (!self::$ACTION_GROUP_OBJECT_HANDLER) { - self::$ACTION_GROUP_OBJECT_HANDLER = new ActionGroupObjectHandler(); - self::$ACTION_GROUP_OBJECT_HANDLER->initActionGroups(); + if (!self::$instance) { + self::$instance = new ActionGroupObjectHandler(); } - return self::$ACTION_GROUP_OBJECT_HANDLER; + return self::$instance; } /** * ActionGroupObjectHandler constructor. + * @throws XmlException */ private function __construct() { $this->extendUtil = new ObjectExtensionUtil(); + $this->initActionGroups(); } /** @@ -71,6 +77,8 @@ private function __construct() * * @param string $actionGroupName * @return ActionGroupObject + * @throws TestFrameworkException + * @throws XmlException */ public function getObject($actionGroupName) { @@ -86,8 +94,10 @@ public function getObject($actionGroupName) * Function to return all objects for which the handler is responsible * * @return array + * @throws TestFrameworkException + * @throws XmlException */ - public function getAllObjects() + public function getAllObjects(): array { foreach ($this->actionGroups as $actionGroupName => $actionGroup) { $this->actionGroups[$actionGroupName] = $this->extendActionGroup($actionGroup); @@ -99,6 +109,7 @@ public function getAllObjects() * Method which populates field array with objects from parsed action_group.xml * * @return void + * @throws XmlException * @SuppressWarnings(PHPMD.UnusedPrivateMethod) */ private function initActionGroups() @@ -107,9 +118,19 @@ private function initActionGroups() $parsedActionGroups = $actionGroupParser->readActionGroupData(); $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 + ); + } - foreach ($parsedActionGroups[ActionGroupObjectHandler::ACTION_GROUP_ROOT] as - $actionGroupName => $actionGroupData) { if (!is_array($actionGroupData)) { continue; } @@ -117,6 +138,7 @@ private function initActionGroups() $this->actionGroups[$actionGroupName] = $actionGroupObjectExtractor->extractActionGroup($actionGroupData); } + $actionGroupNameValidator->summarize(NameValidationUtil::ACTION_GROUP_NAME); } /** @@ -124,10 +146,17 @@ private function initActionGroups() * * @param ActionGroupObject $actionGroupObject * @return ActionGroupObject + * @throws XmlException + * @throws TestFrameworkException */ - private function extendActionGroup($actionGroupObject) + private function extendActionGroup($actionGroupObject): ActionGroupObject { if ($actionGroupObject->getParentName() !== null) { + if ($actionGroupObject->getParentName() === $actionGroupObject->getName()) { + throw new TestFrameworkException( + 'Mftf Action Group can not extend from itself: ' . $actionGroupObject->getName() + ); + } return $this->extendUtil->extendActionGroup($actionGroupObject); } return $actionGroupObject; diff --git a/src/Magento/FunctionalTestingFramework/Test/Handlers/TestObjectHandler.php b/src/Magento/FunctionalTestingFramework/Test/Handlers/TestObjectHandler.php index 19cd025c9..69130a2dc 100644 --- a/src/Magento/FunctionalTestingFramework/Test/Handlers/TestObjectHandler.php +++ b/src/Magento/FunctionalTestingFramework/Test/Handlers/TestObjectHandler.php @@ -1,11 +1,14 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2018 Adobe + * All Rights Reserved. */ + 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; use Magento\FunctionalTestingFramework\ObjectManager\ObjectHandlerInterface; @@ -14,14 +17,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 @@ -48,13 +56,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; @@ -78,7 +87,7 @@ private function __construct() public function getObject($testName) { if (!array_key_exists($testName, $this->tests)) { - throw new TestReferenceException("Test ${testName} not defined in xml."); + throw new TestReferenceException("Test {$testName} not defined in xml."); } $testObject = $this->tests[$testName]; @@ -89,13 +98,42 @@ 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) { - $this->tests[$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() + ); + } } - return $this->tests; + + 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; } /** @@ -103,55 +141,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()] = $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(); + } } /** @@ -159,10 +271,17 @@ 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() + ); + } return $this->extendUtil->extendTest($testObject); } return $testObject; diff --git a/src/Magento/FunctionalTestingFramework/Test/Objects/ActionGroupObject.php b/src/Magento/FunctionalTestingFramework/Test/Objects/ActionGroupObject.php index 67c1ba25c..48e76b871 100644 --- a/src/Magento/FunctionalTestingFramework/Test/Objects/ActionGroupObject.php +++ b/src/Magento/FunctionalTestingFramework/Test/Objects/ActionGroupObject.php @@ -1,16 +1,13 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ namespace Magento\FunctionalTestingFramework\Test\Objects; use Magento\FunctionalTestingFramework\Exceptions\TestReferenceException; -use Magento\FunctionalTestingFramework\Test\Handlers\ActionGroupObjectHandler; -use Magento\FunctionalTestingFramework\Test\Util\ActionGroupObjectExtractor; use Magento\FunctionalTestingFramework\Test\Util\ActionMergeUtil; -use Magento\FunctionalTestingFramework\Test\Util\ObjectExtension; /** * Class ActionGroupObject @@ -19,22 +16,28 @@ class ActionGroupObject { const ACTION_GROUP_ORIGIN_NAME = "actionGroupName"; const ACTION_GROUP_ORIGIN_TEST_REF = "testInvocationRef"; + const ACTION_GROUP_DESCRIPTION = "description"; + const ACTION_GROUP_PAGE = "page"; + const ACTION_GROUP_CONTEXT_START = "Entering Action Group "; + const ACTION_GROUP_CONTEXT_END = "Exiting Action Group "; const STEPKEY_REPLACEMENT_ENABLED_TYPES = [ "executeJS", "magentoCLI", "generateDate", - "formatMoney", + "formatCurrency", "deleteData", "getData", "updateData", "createData", "grabAttributeFrom", "grabCookie", + "grabCookieAttributes", "grabFromCurrentUrl", "grabMultiple", "grabPageSource", "grabTextFrom", - "grabValueFrom" + "grabValueFrom", + "getOTP" ]; /** @@ -64,6 +67,13 @@ class ActionGroupObject */ private $arguments; + /** + * An array used to store annotation information to values + * + * @var array + */ + private $annotations; + /** * String of parent Action Group * @@ -71,25 +81,69 @@ class ActionGroupObject */ private $parentActionGroup; + /** + * Filename where actionGroup came from + * + * @var string + */ + private $filename; + + /** + * Holds on to the result of extractStepKeys() to increase test generation performance. + * + * @var string[] + */ + private $cachedStepKeys = null; + + /** + * Deprecation message. + * + * @var string|null + */ + private $deprecated; + /** * ActionGroupObject constructor. * * @param string $name + * @param array $annotations * @param ArgumentObject[] $arguments * @param array $actions * @param string $parentActionGroup + * @param string $filename + * @param string|null $deprecated */ - public function __construct($name, $arguments, $actions, $parentActionGroup) - { + public function __construct( + $name, + $annotations, + $arguments, + $actions, + $parentActionGroup, + $filename = null, + $deprecated = null + ) { $this->varAttributes = array_merge( ActionObject::SELECTOR_ENABLED_ATTRIBUTES, ActionObject::DATA_ENABLED_ATTRIBUTES ); $this->varAttributes[] = ActionObject::ACTION_ATTRIBUTE_URL; $this->name = $name; + $this->annotations = $annotations; $this->arguments = $arguments; $this->parsedActions = $actions; $this->parentActionGroup = $parentActionGroup; + $this->filename = $filename; + $this->deprecated = $deprecated; + } + + /** + * Returns deprecated messages. + * + * @return string|null + */ + public function getDeprecated() + { + return $this->deprecated; } /** @@ -158,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); @@ -183,13 +240,16 @@ 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() ); } + $resolvedActions = $this->addContextCommentsToActionList($resolvedActions, $actionReferenceKey); + return $resolvedActions; } @@ -204,7 +264,7 @@ private function resolveAttributesWithArguments($arguments, $attributes) // $regexPattern match on: $matches[0] {{section.element(arg.field)}} // $matches[1] = section.element // $matches[2] = arg.field - $regexPattern = '/{{([^(}]+)\(*([^)}]+)*\)*}}/'; + $regexPattern = '/{{([^(}]+)\(*([^)]+)*?\)*}}/'; $newActionAttributes = []; foreach ($attributes as $attributeKey => $attributeValue) { @@ -387,16 +447,18 @@ private function replacePersistedArgument($replacement, $attributeValue, $fullVa */ public function extractStepKeys() { - $originalKeys = []; - foreach ($this->parsedActions as $action) { - //limit actions returned to list that are relevant - foreach (self::STEPKEY_REPLACEMENT_ENABLED_TYPES as $actionValue) { - if ($actionValue === $action->getType()) { + if ($this->cachedStepKeys === null) { + $originalKeys = []; + foreach ($this->parsedActions as $action) { + //limit actions returned to list that are relevant + if (in_array($action->getType(), self::STEPKEY_REPLACEMENT_ENABLED_TYPES)) { $originalKeys[] = $action->getStepKey(); } } + $this->cachedStepKeys = $originalKeys; } - return $originalKeys; + + return $this->cachedStepKeys; } /** @@ -409,6 +471,16 @@ public function getName() return $this->name; } + /** + * Getter for the Action Group Filename + * + * @return string + */ + public function getFilename() + { + return $this->filename; + } + /** * Getter for the Parent Action Group Name * @@ -439,6 +511,16 @@ public function getArguments() return $this->arguments; } + /** + * Getter for the Action Group Annotations + * + * @return array + */ + public function getAnnotations() + { + return $this->annotations; + } + /** * Searches through ActionGroupObject's arguments and returns first argument wi * @param string $name @@ -450,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])) { @@ -481,4 +563,37 @@ private function replaceCreateDataKeys($action, $replacementStepKeys) return $resolvedActionAttributes; } + + /** + * Adds comment ActionObjects before and after given actionList for context setting. + * @param array $actionList + * @param string $actionReferenceKey + * @return array + */ + 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], + null, + ActionObject::MERGE_ACTION_ORDER_BEFORE, + null, + $deprecationNotices + ); + $endAction = new ActionObject( + $actionEndComment, + ActionObject::ACTION_TYPE_COMMENT, + [ActionObject::ACTION_ATTRIBUTE_USERINPUT => $actionEndComment] + ); + return [$startAction->getStepKey() => $startAction] + $actionList + [$endAction->getStepKey() => $endAction]; + } } diff --git a/src/Magento/FunctionalTestingFramework/Test/Objects/ActionObject.php b/src/Magento/FunctionalTestingFramework/Test/Objects/ActionObject.php index 1efb0f603..c82b5cfae 100644 --- a/src/Magento/FunctionalTestingFramework/Test/Objects/ActionObject.php +++ b/src/Magento/FunctionalTestingFramework/Test/Objects/ActionObject.php @@ -1,8 +1,9 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ + namespace Magento\FunctionalTestingFramework\Test\Objects; use Magento\FunctionalTestingFramework\Config\MftfApplicationConfig; @@ -23,6 +24,7 @@ */ class ActionObject { + const COMMENT_ACTION = '#comment'; const __ENV = "_ENV"; const __CREDS = "_CREDS"; const RUNTIME_REFERENCES = [ @@ -52,15 +54,17 @@ class ActionObject "function", 'filterSelector', 'optionSelector', - "command" + "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']; + 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'; @@ -68,6 +72,13 @@ class ActionObject const ACTION_ATTRIBUTE_SELECTOR = 'selector'; const ACTION_ATTRIBUTE_VARIABLE_REGEX_PARAMETER = '/\(.+\)/'; const ACTION_ATTRIBUTE_VARIABLE_REGEX_PATTERN = '/({{[\w]+\.[\w\[\]]+}})|({{[\w]+\.[\w]+\((?(?!}}).)+\)}})/'; + const STRING_PARAMETER_REGEX = "/'[^']+'/"; + 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 @@ -76,6 +87,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) * @@ -134,6 +152,7 @@ class ActionObject * @param string|null $linkedAction * @param string $order * @param array $actionOrigin + * @param array $deprecatedUsage */ public function __construct( $stepKey, @@ -141,19 +160,42 @@ public function __construct( $actionAttributes, $linkedAction = null, $order = ActionObject::MERGE_ACTION_ORDER_BEFORE, - $actionOrigin = null + $actionOrigin = null, + $deprecatedUsage = [] ) { $this->stepKey = $stepKey; - $this->type = $type; + $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) { + if ($order === ActionObject::MERGE_ACTION_ORDER_AFTER) { $this->orderOffset = 1; } } + /** + * Retrieve default timeout in seconds for 'wait*' actions + * + * @return integer + */ + public static function getDefaultWaitTimeout() + { + return getenv('WAIT_TIMEOUT'); + } + + /** + * Retrieve default timeout for 'magentoCLI' or 'magentoCLISecret' in seconds + * + * @return integer + */ + public static function getDefaultMagentoCLIWaitTimeout() + { + $timeout = getenv('MAGENTO_CLI_WAIT_TIMEOUT'); + return !empty($timeout) ? $timeout : self::DEFAULT_COMMAND_WAIT_TIMEOUT; + } + /** * This function returns the string property stepKey. * @@ -177,7 +219,7 @@ public function getType() /** * Getter for actionOrigin * - * @return string + * @return array */ public function getActionOrigin() { @@ -254,45 +296,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. * - * @return void * @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); @@ -300,47 +384,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. @@ -368,6 +430,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 @@ -392,7 +470,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] ); @@ -454,8 +532,6 @@ private function stripAndSplitReference($reference) */ private function stripAndReturnParameters($reference) { - // 'string', or 'string,!@#$%^&*()_+, ' - $literalParametersRegex = "/'[^']+'/"; $postCleanupDelimiter = "::::"; preg_match(ActionObject::ACTION_ATTRIBUTE_VARIABLE_REGEX_PARAMETER, $reference, $matches); @@ -463,8 +539,9 @@ private function stripAndReturnParameters($reference) $strippedReference = ltrim(rtrim($matches[0], ")"), "("); // Pull out all 'string' references, as they can contain 'string with , comma in it' - preg_match_all($literalParametersRegex, $strippedReference, $literalReferences); - $strippedReference = preg_replace($literalParametersRegex, '&&stringReference&&', $strippedReference); + // 'string', or 'string,!@#$%^&*()_+, ' + preg_match_all(self::STRING_PARAMETER_REGEX, $strippedReference, $literalReferences); + $strippedReference = preg_replace(self::STRING_PARAMETER_REGEX, '&&stringReference&&', $strippedReference); // Sanitize 'string, data.field,$persisted.field$' => 'string::::data.field::::$persisted.field$' $strippedReference = preg_replace('/,/', ', ', $strippedReference); @@ -513,17 +590,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()}\"", @@ -533,7 +617,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)) { @@ -542,7 +634,7 @@ private function findAndReplaceReferences($objectHandler, $inputString) } if ($replacement === null) { - if (get_class($objectHandler) != DataObjectHandler::class) { + if (!($objectHandler instanceof DataObjectHandler)) { return $this->findAndReplaceReferences(DataObjectHandler::getInstance(), $outputString); } else { throw new TestReferenceException( @@ -575,7 +667,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) . "'", @@ -593,7 +685,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()}'", @@ -634,7 +726,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]; @@ -658,13 +750,18 @@ private function resolveEntityDataObjectReference($obj, $match) private function resolveParameterization($isParameterized, $replacement, $match, $object) { if ($isParameterized) { - $parameterList = $this->stripAndReturnParameters($match); + $parameterList = $this->stripAndReturnParameters($match) ?: []; $resolvedReplacement = $this->matchParameterReferences($replacement, $parameterList); } 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; } @@ -682,26 +779,7 @@ private function matchParameterReferences($reference, $parameters) { preg_match_all('/{{[\w.]+}}/', $reference, $varMatches); $varMatches[0] = array_unique($varMatches[0]); - if (count($varMatches[0]) > count($parameters)) { - if (is_array($parameters)) { - $parametersGiven = implode(",", $parameters); - } elseif ($parameters == null) { - $parametersGiven = "NONE"; - } else { - $parametersGiven = $parameters; - } - throw new TestReferenceException( - "Parameter Resolution Failed: Not enough parameters given for reference " . - $reference . ". Parameters Given: " . $parametersGiven, - ["reference" => $reference, "parametersGiven" => $parametersGiven] - ); - } elseif (count($varMatches[0]) < count($parameters)) { - throw new TestReferenceException( - "Parameter Resolution Failed: Too many parameters given for reference " . - $reference . ". Parameters Given: " . implode(", ", $parameters), - ["reference" => $reference, "parametersGiven" => $parameters] - ); - } + $this->checkParameterCount($varMatches[0], $parameters, $reference); //Attempt to Resolve {{data}} references to actual output. Trim parameter for whitespace before processing it. //If regex matched it means that it's either a 'StringLiteral' or $key.data$/$$key.data$$ reference. @@ -710,7 +788,7 @@ private function matchParameterReferences($reference, $parameters) $resolvedParameters = []; foreach ($parameters as $parameter) { $parameter = trim($parameter); - preg_match_all("/[$'][\w\D]+[$']/", $parameter, $stringOrPersistedMatch); + preg_match_all("/[$'][\w\D]*[$']/", $parameter, $stringOrPersistedMatch); preg_match_all('/{\$[a-z][a-zA-Z\d]+}/', $parameter, $variableMatch); if (!empty($stringOrPersistedMatch[0])) { $resolvedParameters[] = ltrim(rtrim($parameter, "'"), "'"); @@ -730,4 +808,53 @@ private function matchParameterReferences($reference, $parameters) } return $reference; } + + /** + * Checks count of parameters versus matches + * + * @param array $matches + * @param array $parameters + * @param string $reference + * @return void + * @throws \Exception + */ + private function checkParameterCount($matches, $parameters, $reference) + { + if (count($matches) > count($parameters)) { + if (is_array($parameters)) { + $parametersGiven = implode(",", $parameters); + } elseif ($parameters === null) { + $parametersGiven = "NONE"; + } else { + $parametersGiven = $parameters; + } + throw new TestReferenceException( + "Parameter Resolution Failed: Not enough parameters given for reference " . + $reference . ". Parameters Given: " . $parametersGiven, + ["reference" => $reference, "parametersGiven" => $parametersGiven] + ); + } elseif (count($matches) < count($parameters)) { + throw new TestReferenceException( + "Parameter Resolution Failed: Too many parameters given for reference " . + $reference . ". Parameters Given: " . implode(", ", $parameters), + ["reference" => $reference, "parametersGiven" => $parameters] + ); + } elseif (count($matches) === 0) { + throw new TestReferenceException( + "Parameter Resolution Failed: No parameter matches found in parameterized element with selector " . + $reference, + ["reference" => $reference] + ); + } + } + + /** + * Returns array of deprecated usages in Action. + * + * @return array + */ + public function getDeprecatedUsages() + { + return $this->deprecatedUsage; + } } diff --git a/src/Magento/FunctionalTestingFramework/Test/Objects/ArgumentObject.php b/src/Magento/FunctionalTestingFramework/Test/Objects/ArgumentObject.php index c829e66b3..93dfe8855 100644 --- a/src/Magento/FunctionalTestingFramework/Test/Objects/ArgumentObject.php +++ b/src/Magento/FunctionalTestingFramework/Test/Objects/ArgumentObject.php @@ -1,7 +1,7 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2018 Adobe + * All Rights Reserved. */ namespace Magento\FunctionalTestingFramework\Test\Objects; @@ -104,8 +104,8 @@ public function getResolvedValue($isInnerArgument) * Takes in boolean to determine if the replacement is being done with an inner argument (as in if it's a parameter) * * Example Type Non Inner Inner - * {{XML.DATA}}: {{XML.DATA}} XML.DATA - * $TEST.DATA$: $TEST.DATA$ $TEST.DATA$ + * {{XML.DATA}} {{XML.DATA}} XML.DATA + * $TEST.DATA$ $TEST.DATA$ $TEST.DATA$ * stringLiteral stringLiteral 'stringLiteral' * * @param boolean $isInnerArgument @@ -114,6 +114,11 @@ public function getResolvedValue($isInnerArgument) private function resolveStringArgument($isInnerArgument) { if ($isInnerArgument) { + if (preg_match('/{{[\w.\[\]]+}}/', $this->value)) { + return ltrim(rtrim($this->value, "}"), "{"); + } elseif (preg_match('/\${1,2}[\w.\[\]]+\${1,2}/', $this->value)) { + return $this->value; + } return "'" . $this->value . "'"; } else { return $this->value; diff --git a/src/Magento/FunctionalTestingFramework/Test/Objects/TestHookObject.php b/src/Magento/FunctionalTestingFramework/Test/Objects/TestHookObject.php index 379082632..2e8e96c29 100644 --- a/src/Magento/FunctionalTestingFramework/Test/Objects/TestHookObject.php +++ b/src/Magento/FunctionalTestingFramework/Test/Objects/TestHookObject.php @@ -1,7 +1,7 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2018 Adobe + * All Rights Reserved. */ namespace Magento\FunctionalTestingFramework\Test\Objects; diff --git a/src/Magento/FunctionalTestingFramework/Test/Objects/TestObject.php b/src/Magento/FunctionalTestingFramework/Test/Objects/TestObject.php index 30c3a9004..49d7d63d0 100644 --- a/src/Magento/FunctionalTestingFramework/Test/Objects/TestObject.php +++ b/src/Magento/FunctionalTestingFramework/Test/Objects/TestObject.php @@ -1,7 +1,7 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ namespace Magento\FunctionalTestingFramework\Test\Objects; @@ -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 * @@ -71,6 +93,27 @@ class TestObject */ private $parentTest; + /** + * Holds on to the result of getOrderedActions() to increase test generation performance. + * + * @var ActionObject[] + */ + 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. * @@ -80,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; } /** @@ -121,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 * @@ -128,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; } @@ -144,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; } @@ -192,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. * @@ -207,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) { @@ -245,7 +337,7 @@ public function getAnnotationByName($name) */ public function getCustomData() { - return $this->customData; + return null; } /** @@ -255,8 +347,12 @@ public function getCustomData() */ public function getOrderedActions() { - $mergeUtil = new ActionMergeUtil($this->getName(), "Test"); - return $mergeUtil->resolveActionSteps($this->parsedSteps); + if ($this->cachedOrderedActions === null) { + $mergeUtil = new ActionMergeUtil($this->getName(), "Test"); + $this->cachedOrderedActions = $mergeUtil->resolveActionSteps($this->parsedSteps); + } + + return $this->cachedOrderedActions; } /** diff --git a/src/Magento/FunctionalTestingFramework/Test/Parsers/ActionGroupDataParser.php b/src/Magento/FunctionalTestingFramework/Test/Parsers/ActionGroupDataParser.php index 4709d3aa8..9b78904f3 100644 --- a/src/Magento/FunctionalTestingFramework/Test/Parsers/ActionGroupDataParser.php +++ b/src/Magento/FunctionalTestingFramework/Test/Parsers/ActionGroupDataParser.php @@ -1,7 +1,7 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ namespace Magento\FunctionalTestingFramework\Test\Parsers; @@ -11,8 +11,14 @@ /** * Class ActionGroupDataParser */ + class ActionGroupDataParser { + /** + * @var DataInterface + */ + private $actionGroupData; + /** * ActionGroupDataParser constructor. * diff --git a/src/Magento/FunctionalTestingFramework/Test/Parsers/TestDataParser.php b/src/Magento/FunctionalTestingFramework/Test/Parsers/TestDataParser.php index 18044c1af..d90d4078e 100644 --- a/src/Magento/FunctionalTestingFramework/Test/Parsers/TestDataParser.php +++ b/src/Magento/FunctionalTestingFramework/Test/Parsers/TestDataParser.php @@ -1,35 +1,48 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ 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 new file mode 100644 index 000000000..882e23796 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Test/Util/ActionGroupAnnotationExtractor.php @@ -0,0 +1,37 @@ +<?php +/** + * Copyright 2019 Adobe + * All Rights Reserved. + */ + +namespace Magento\FunctionalTestingFramework\Test\Util; + +use Magento\FunctionalTestingFramework\Util\Logger\LoggingUtil; + +/** + * Class AnnotationExtractor + */ +class ActionGroupAnnotationExtractor extends AnnotationExtractor +{ + /** + * 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 boolean $validateAnnotations + * @return array + * @throws \Exception + */ + public function extractAnnotations($testAnnotations, $filename, $validateAnnotations = true) + { + $annotationObjects = []; + $annotations = $this->stripDescriptorTags($testAnnotations, parent::NODE_NAME); + + foreach ($annotations as $annotationKey => $annotationData) { + $annotationObjects[$annotationKey] = $annotationData[parent::ANNOTATION_VALUE]; + } + + return $annotationObjects; + } +} diff --git a/src/Magento/FunctionalTestingFramework/Test/Util/ActionGroupObjectExtractor.php b/src/Magento/FunctionalTestingFramework/Test/Util/ActionGroupObjectExtractor.php index 04f739af6..a3cb079e5 100644 --- a/src/Magento/FunctionalTestingFramework/Test/Util/ActionGroupObjectExtractor.php +++ b/src/Magento/FunctionalTestingFramework/Test/Util/ActionGroupObjectExtractor.php @@ -1,7 +1,7 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ namespace Magento\FunctionalTestingFramework\Test\Util; @@ -9,7 +9,10 @@ use Magento\FunctionalTestingFramework\Data\Argument\Interpreter\Argument; use Magento\FunctionalTestingFramework\Exceptions\XmlException; 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 @@ -18,6 +21,7 @@ class ActionGroupObjectExtractor extends BaseObjectExtractor { const DEFAULT_VALUE = 'defaultValue'; const ACTION_GROUP_ARGUMENTS = 'arguments'; + const ACTION_GROUP_ANNOTATIONS = 'annotations'; const FILENAME = 'filename'; const ACTION_GROUP_INSERT_BEFORE = "insertBefore"; const ACTION_GROUP_INSERT_AFTER = "insertAfter"; @@ -30,12 +34,20 @@ class ActionGroupObjectExtractor extends BaseObjectExtractor */ private $actionObjectExtractor; + /** + * Annotation Extractor object + * + * @var AnnotationExtractor + */ + private $annotationExtractor; + /** * ActionGroupObjectExtractor constructor. */ public function __construct() { $this->actionObjectExtractor = new ActionObjectExtractor(); + $this->annotationExtractor = new ActionGroupAnnotationExtractor(); } /** @@ -48,20 +60,40 @@ 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, self::NODE_NAME, self::ACTION_GROUP_ARGUMENTS, self::NAME, + self::ACTION_GROUP_ANNOTATIONS, 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 + + try { + $annotations = $this->annotationExtractor->extractAnnotations( + $actionGroupData[self::ACTION_GROUP_ANNOTATIONS] ?? [], + $actionGroupData[self::FILENAME] + ); + } catch (\Exception $error) { + throw new XmlException($error->getMessage() . " in Action Group " . $actionGroupData[self::FILENAME]); + } + try { $actions = $this->actionObjectExtractor->extractActions($actionData); } catch (\Exception $error) { @@ -74,9 +106,12 @@ public function extractActionGroup($actionGroupData) return new ActionGroupObject( $actionGroupData[self::NAME], + $annotations, $arguments, $actions, - $actionGroupReference + $actionGroupReference, + $actionGroupData[self::FILENAME], + $deprecated ); } @@ -95,6 +130,11 @@ private function extractArguments($arguments) self::NODE_NAME ); + // Filtering XML comments from action group arguments. + $argData = array_filter($argData, function ($key) { + return strpos($key, ActionObject::COMMENT_ACTION) === false; + }, ARRAY_FILTER_USE_KEY); + foreach ($argData as $argName => $argValue) { $parsedArguments[] = new ArgumentObject( $argValue[ArgumentObject::ARGUMENT_NAME], diff --git a/src/Magento/FunctionalTestingFramework/Test/Util/ActionMergeUtil.php b/src/Magento/FunctionalTestingFramework/Test/Util/ActionMergeUtil.php index 0a6bc7957..84ede9738 100644 --- a/src/Magento/FunctionalTestingFramework/Test/Util/ActionMergeUtil.php +++ b/src/Magento/FunctionalTestingFramework/Test/Util/ActionMergeUtil.php @@ -1,7 +1,7 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ namespace Magento\FunctionalTestingFramework\Test\Util; @@ -23,12 +23,13 @@ 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'; + const APPROVED_ACTIONS = ['fillField', 'magentoCLI', 'field', 'seeInField']; + const SECRET_MAPPING = ['fillField' => 'fillSecretField', 'magentoCLI' => 'magentoCLISecret', + 'seeInField' => 'seeInSecretField']; + const CREDS_REGEX = "/{{_CREDS\.([\w|\/]+)}}/"; /** * Array holding final resulting steps @@ -83,7 +84,6 @@ public function resolveActionSteps($parsedSteps, $skipActionGroupResolution = fa { $this->mergeActions($parsedSteps); $this->insertWaits(); - $this->insertReadinessSkips(); if ($skipActionGroupResolution) { return $this->orderedSteps; @@ -95,7 +95,7 @@ public function resolveActionSteps($parsedSteps, $skipActionGroupResolution = fa /** * Takes an array of actions and resolves any references to secret fields. The function then validates whether the - * refernece is valid and replaces the function name accordingly to hide arguments at runtime. + * reference is valid and replaces the function name accordingly to hide arguments at runtime. * * @param ActionObject[] $resolvedActions * @return ActionObject[] @@ -106,22 +106,29 @@ private function resolveSecretFieldAccess($resolvedActions) $actions = []; foreach ($resolvedActions as $resolvedAction) { $action = $resolvedAction; - $hasSecretRef = $this->actionAttributeContainsSecretRef($resolvedAction->getCustomActionAttributes()); + $actionHasSecretRef = $this->actionAttributeContainsSecretRef($resolvedAction->getCustomActionAttributes()); + $actionType = $resolvedAction->getType(); - if ($resolvedAction->getType() !== 'fillField' && $hasSecretRef) { - throw new TestReferenceException("You cannot reference secret data outside of fill field actions"); + if ($actionHasSecretRef && !(in_array($actionType, self::APPROVED_ACTIONS))) { + throw new TestReferenceException("You cannot reference secret data outside " . + "of the fillField, magentoCLI, seeInField and createData actions"); } - if ($resolvedAction->getType() === 'fillField' && $hasSecretRef) { - $action = new ActionObject( - $action->getStepKey(), - 'fillSecretField', - $action->getCustomActionAttributes(), - $action->getLinkedAction(), - $action->getActionOrigin() - ); + // Do NOT remap actions that don't need it. + if (isset(self::SECRET_MAPPING[$actionType]) && $actionHasSecretRef) { + $actionType = self::SECRET_MAPPING[$actionType]; } + $action = new ActionObject( + $action->getStepKey(), + $actionType, + $action->getCustomActionAttributes(), + $action->getLinkedAction(), + $action->getOrderOffset(), + $action->getActionOrigin(), + $action->getDeprecatedUsages() + ); + $actions[$action->getStepKey()] = $action; } @@ -141,7 +148,7 @@ private function actionAttributeContainsSecretRef($actionAttributes) return $this->actionAttributeContainsSecretRef($actionAttribute); } - preg_match_all("/{{_CREDS\.([\w]+)}}/", $actionAttribute, $matches); + preg_match_all(self::CREDS_REGEX, $actionAttribute, $matches); if (!empty($matches[0])) { return true; @@ -164,10 +171,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; @@ -223,54 +230,29 @@ 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. * * @param array $parsedSteps * @return void - * @throws XmlException + * @throws TestReferenceException */ private function sortActions($parsedSteps) { foreach ($parsedSteps as $parsedStep) { - $parsedStep->resolveReferences(); - if ($parsedStep->getLinkedAction()) { - $this->stepsToMerge[$parsedStep->getStepKey()] = $parsedStep; - } else { - $this->orderedSteps[$parsedStep->getStepKey()] = $parsedStep; + try { + $parsedStep->resolveReferences(); + + if ($parsedStep->getLinkedAction()) { + $this->stepsToMerge[$parsedStep->getStepKey()] = $parsedStep; + } else { + $this->orderedSteps[$parsedStep->getStepKey()] = $parsedStep; + } + } catch (\Exception $e) { + throw new TestReferenceException( + $e->getMessage() . + ".\nException occurred parsing action at StepKey \"" . $parsedStep->getStepKey() . "\"" + ); } } } diff --git a/src/Magento/FunctionalTestingFramework/Test/Util/ActionObjectExtractor.php b/src/Magento/FunctionalTestingFramework/Test/Util/ActionObjectExtractor.php index 5cd7b7ac5..7f28078ed 100644 --- a/src/Magento/FunctionalTestingFramework/Test/Util/ActionObjectExtractor.php +++ b/src/Magento/FunctionalTestingFramework/Test/Util/ActionObjectExtractor.php @@ -1,7 +1,7 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ namespace Magento\FunctionalTestingFramework\Test\Util; @@ -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'; @@ -58,7 +59,10 @@ public function extractActions($testActions, $testName = null) $stepKeyRefs = []; foreach ($testActions as $actionName => $actionData) { - $stepKey = $actionData[self::TEST_STEP_MERGE_KEY]; + // Removing # from nodeName to match stepKey requirements + $stepKey = strpos($actionData[self::NODE_NAME], ActionObject::COMMENT_ACTION) === false + ? $actionData[self::TEST_STEP_MERGE_KEY] + : str_replace("#", "", $actionName); $actionType = $actionData[self::NODE_NAME]; if (empty($stepKey)) { @@ -66,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( @@ -81,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; } @@ -152,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; } @@ -164,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 @@ -184,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; } @@ -217,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; } @@ -264,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 3b857e29f..0a159de83 100644 --- a/src/Magento/FunctionalTestingFramework/Test/Util/AnnotationExtractor.php +++ b/src/Magento/FunctionalTestingFramework/Test/Util/AnnotationExtractor.php @@ -1,13 +1,16 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ 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; /** @@ -54,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); @@ -67,36 +72,37 @@ public function extractAnnotations($testAnnotations, $filename) // parse the Test annotations foreach ($annotations as $annotationKey => $annotationData) { + if (strpos($annotationKey, ActionObject::ACTION_TYPE_COMMENT) !== false) { + continue; + } $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; @@ -151,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; } } @@ -160,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) { @@ -206,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); } } @@ -227,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..59116a39b 100644 --- a/src/Magento/FunctionalTestingFramework/Test/Util/BaseObjectExtractor.php +++ b/src/Magento/FunctionalTestingFramework/Test/Util/BaseObjectExtractor.php @@ -1,7 +1,7 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2018 Adobe + * All Rights Reserved. */ namespace Magento\FunctionalTestingFramework\Test\Util; @@ -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 e67037940..255eacb7e 100644 --- a/src/Magento/FunctionalTestingFramework/Test/Util/ObjectExtensionUtil.php +++ b/src/Magento/FunctionalTestingFramework/Test/Util/ObjectExtensionUtil.php @@ -1,12 +1,13 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2018 Adobe + * All Rights Reserved. */ 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() . @@ -129,9 +136,11 @@ public function extendActionGroup($actionGroupObject) // Create new Action Group object to return $extendedActionGroup = new ActionGroupObject( $actionGroupObject->getName(), + $actionGroupObject->getAnnotations(), $extendedArguments, $newActions, - $actionGroupObject->getParentName() + $actionGroupObject->getParentName(), + $actionGroupObject->getFilename() ); return $extendedActionGroup; } @@ -191,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; } } @@ -203,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( @@ -225,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..0649a356f 100644 --- a/src/Magento/FunctionalTestingFramework/Test/Util/TestHookObjectExtractor.php +++ b/src/Magento/FunctionalTestingFramework/Test/Util/TestHookObjectExtractor.php @@ -1,11 +1,13 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2018 Adobe + * All Rights Reserved. */ 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 acad18572..c4df02269 100644 --- a/src/Magento/FunctionalTestingFramework/Test/Util/TestObjectExtractor.php +++ b/src/Magento/FunctionalTestingFramework/Test/Util/TestObjectExtractor.php @@ -1,20 +1,24 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ 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,22 +84,26 @@ 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 = []; $testHooks = []; $filename = $testData['filename'] ?? null; - $fileNames = explode(",", $filename); + $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,17 +115,38 @@ 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 $testAnnotations["features"] = [$module]; + // Always try to append test file names in description annotation, i.e. displaying test files title only + // 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] + ); + } + // extract before if (array_key_exists(self::TEST_BEFORE_HOOK, $testData) && is_array($testData[self::TEST_BEFORE_HOOK])) { $testHooks[self::TEST_BEFORE_HOOK] = $this->testHookObjectExtractor->extractHook( @@ -141,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( @@ -149,10 +182,38 @@ public function extractTestData($testData) $testAnnotations, $testHooks, $filename, - $testReference + $testReference, + $deprecated ); } catch (XmlException $exception) { throw new XmlException($exception->getMessage() . ' in Test ' . $filename); } } + + /** + * Append names of test files, including merge files, in description annotation + * + * @param array $fileNames + * + * @return string + */ + private function appendFileNamesInDescriptionAnnotation(array $fileNames) + { + $filePaths = '<h3>Test files</h3>'; + + foreach ($fileNames as $fileName) { + if (!empty($fileName) && realpath($fileName) !== false) { + $fileName = realpath($fileName); + $relativeFileName = ltrim( + str_replace(MAGENTO_BP, '', $fileName), + DIRECTORY_SEPARATOR + ); + if (!empty($relativeFileName)) { + $filePaths .= $relativeFileName . '<br>'; + } + } + } + + return $filePaths; + } } diff --git a/src/Magento/FunctionalTestingFramework/Test/etc/Actions/assertActions.xsd b/src/Magento/FunctionalTestingFramework/Test/etc/Actions/assertActions.xsd index 4fdde9990..699bb98e1 100644 --- a/src/Magento/FunctionalTestingFramework/Test/etc/Actions/assertActions.xsd +++ b/src/Magento/FunctionalTestingFramework/Test/etc/Actions/assertActions.xsd @@ -1,9 +1,9 @@ <?xml version="1.0" encoding="UTF-8"?> <!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ +/** + * Copyright 2018 Adobe + * All Rights Reserved. + */ --> <xs:schema elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema"> @@ -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/clickActions.xsd b/src/Magento/FunctionalTestingFramework/Test/etc/Actions/clickActions.xsd index 614352246..4d469fe84 100644 --- a/src/Magento/FunctionalTestingFramework/Test/etc/Actions/clickActions.xsd +++ b/src/Magento/FunctionalTestingFramework/Test/etc/Actions/clickActions.xsd @@ -1,9 +1,9 @@ <?xml version="1.0" encoding="UTF-8"?> <!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ +/** + * Copyright 2018 Adobe + * All Rights Reserved. + */ --> <xs:schema elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema"> diff --git a/src/Magento/FunctionalTestingFramework/Test/etc/Actions/commonAttributes.xsd b/src/Magento/FunctionalTestingFramework/Test/etc/Actions/commonAttributes.xsd index 22c05ea7d..863b9e56c 100644 --- a/src/Magento/FunctionalTestingFramework/Test/etc/Actions/commonAttributes.xsd +++ b/src/Magento/FunctionalTestingFramework/Test/etc/Actions/commonAttributes.xsd @@ -1,9 +1,9 @@ <?xml version="1.0" encoding="UTF-8"?> <!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ +/** + * Copyright 2018 Adobe + * All Rights Reserved. + */ --> <xs:schema elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema"> <xs:attributeGroup name="commonActionAttributes"> @@ -31,13 +31,6 @@ </xs:annotation> </xs:attribute> - <xs:attribute type="xs:boolean" name="skipReadiness"> - <xs:annotation> - <xs:documentation> - Flag for skipping readiness check - </xs:documentation> - </xs:annotation> - </xs:attribute> </xs:attributeGroup> <xs:attribute type="xs:string" name="userInput" id="userInput"> diff --git a/src/Magento/FunctionalTestingFramework/Test/etc/Actions/customActions.xsd b/src/Magento/FunctionalTestingFramework/Test/etc/Actions/customActions.xsd index 3a002e126..0e9c3a939 100644 --- a/src/Magento/FunctionalTestingFramework/Test/etc/Actions/customActions.xsd +++ b/src/Magento/FunctionalTestingFramework/Test/etc/Actions/customActions.xsd @@ -1,9 +1,9 @@ <?xml version="1.0" encoding="UTF-8"?> <!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ +/** + * Copyright 2018 Adobe + * All Rights Reserved. + */ --> <xs:schema elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema"> @@ -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..b65e588ab 100644 --- a/src/Magento/FunctionalTestingFramework/Test/etc/Actions/dataActions.xsd +++ b/src/Magento/FunctionalTestingFramework/Test/etc/Actions/dataActions.xsd @@ -1,9 +1,9 @@ <?xml version="1.0" encoding="UTF-8"?> <!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ +/** + * Copyright 2018 Adobe + * All Rights Reserved. + */ --> <xs:schema elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema"> @@ -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/dontSeeActions.xsd b/src/Magento/FunctionalTestingFramework/Test/etc/Actions/dontSeeActions.xsd index 3fda54071..8aa7ea237 100644 --- a/src/Magento/FunctionalTestingFramework/Test/etc/Actions/dontSeeActions.xsd +++ b/src/Magento/FunctionalTestingFramework/Test/etc/Actions/dontSeeActions.xsd @@ -1,9 +1,9 @@ <?xml version="1.0" encoding="UTF-8"?> <!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ +/** + * Copyright 2018 Adobe + * All Rights Reserved. + */ --> <xs:schema elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema"> @@ -172,6 +172,11 @@ </xs:annotation> <xs:simpleContent> <xs:extension base="xs:string"> + <!-- + For backwards compatibility we accept both html and userInput. + If html is specified, then html is used. Otherwise userInput is used. + --> + <xs:attribute ref="html"/> <xs:attribute ref="userInput"/> <xs:attributeGroup ref="commonActionAttributes"/> </xs:extension> diff --git a/src/Magento/FunctionalTestingFramework/Test/etc/Actions/grabActions.xsd b/src/Magento/FunctionalTestingFramework/Test/etc/Actions/grabActions.xsd index 9109489aa..d5e36d9fc 100644 --- a/src/Magento/FunctionalTestingFramework/Test/etc/Actions/grabActions.xsd +++ b/src/Magento/FunctionalTestingFramework/Test/etc/Actions/grabActions.xsd @@ -1,9 +1,9 @@ <?xml version="1.0" encoding="UTF-8"?> <!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ +/** + * Copyright 2018 Adobe + * All Rights Reserved. + */ --> <xs:schema elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema"> @@ -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/seeActions.xsd b/src/Magento/FunctionalTestingFramework/Test/etc/Actions/seeActions.xsd index 97d6ef67b..ab5dbb6e5 100644 --- a/src/Magento/FunctionalTestingFramework/Test/etc/Actions/seeActions.xsd +++ b/src/Magento/FunctionalTestingFramework/Test/etc/Actions/seeActions.xsd @@ -1,9 +1,9 @@ <?xml version="1.0" encoding="UTF-8"?> <!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ +/** + * Copyright 2018 Adobe + * All Rights Reserved. + */ --> <xs:schema elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema"> diff --git a/src/Magento/FunctionalTestingFramework/Test/etc/Actions/switchToActions.xsd b/src/Magento/FunctionalTestingFramework/Test/etc/Actions/switchToActions.xsd index b648d87b2..08759582d 100644 --- a/src/Magento/FunctionalTestingFramework/Test/etc/Actions/switchToActions.xsd +++ b/src/Magento/FunctionalTestingFramework/Test/etc/Actions/switchToActions.xsd @@ -1,9 +1,9 @@ <?xml version="1.0" encoding="UTF-8"?> <!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ +/** + * Copyright 2018 Adobe + * All Rights Reserved. + */ --> <xs:schema elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema"> diff --git a/src/Magento/FunctionalTestingFramework/Test/etc/Actions/waitActions.xsd b/src/Magento/FunctionalTestingFramework/Test/etc/Actions/waitActions.xsd index 5350d9d3e..94a24808a 100644 --- a/src/Magento/FunctionalTestingFramework/Test/etc/Actions/waitActions.xsd +++ b/src/Magento/FunctionalTestingFramework/Test/etc/Actions/waitActions.xsd @@ -1,9 +1,9 @@ <?xml version="1.0" encoding="UTF-8"?> <!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ +/** + * Copyright 2018 Adobe + * All Rights Reserved. + */ --> <xs:schema elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema"> @@ -20,7 +20,11 @@ <xs:element type="waitForJSType" name="waitForJS" minOccurs="0" maxOccurs="unbounded"/> <xs:element type="waitForLoadingMaskToDisappearType" name="waitForLoadingMaskToDisappear" minOccurs="0" maxOccurs="unbounded"/> <xs:element type="waitForPageLoadType" name="waitForPageLoad" minOccurs="0" maxOccurs="unbounded"/> + <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> @@ -165,12 +169,42 @@ <xs:complexType name="waitForPageLoadType"> <xs:annotation> <xs:documentation> - Waits up to given time for page to have finished loading.. + Waits up to given time for page to have finished loading. + </xs:documentation> + </xs:annotation> + <xs:simpleContent> + <xs:extension base="xs:string"> + <xs:attribute ref="time"/> + <xs:attributeGroup ref="commonActionAttributes"/> + </xs:extension> + </xs:simpleContent> + </xs:complexType> + + <xs:complexType name="waitForPwaElementNotVisibleType"> + <xs:annotation> + <xs:documentation> + Waits up to given time for a PWA Element to disappear from the screen using JavaScript. </xs:documentation> </xs:annotation> <xs:simpleContent> <xs:extension base="xs:string"> <xs:attribute ref="time"/> + <xs:attribute ref="selector"/> + <xs:attributeGroup ref="commonActionAttributes"/> + </xs:extension> + </xs:simpleContent> + </xs:complexType> + + <xs:complexType name="waitForPwaElementVisibleType"> + <xs:annotation> + <xs:documentation> + Waits up to given time for a PWA Element to appear on the screen using JavaScript. + </xs:documentation> + </xs:annotation> + <xs:simpleContent> + <xs:extension base="xs:string"> + <xs:attribute ref="time"/> + <xs:attribute ref="selector"/> <xs:attributeGroup ref="commonActionAttributes"/> </xs:extension> </xs:simpleContent> @@ -192,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 619d21e9f..ae0a38c50 100644 --- a/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd +++ b/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd @@ -1,46 +1,17 @@ <?xml version="1.0" encoding="UTF-8"?> <!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ +/** + * Copyright 2018 Adobe + * All Rights Reserved. + */ --> <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: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 90671c340..46e77a25b 100644 --- a/src/Magento/FunctionalTestingFramework/Test/etc/actionTypeTags.xsd +++ b/src/Magento/FunctionalTestingFramework/Test/etc/actionTypeTags.xsd @@ -1,9 +1,9 @@ <?xml version="1.0" encoding="UTF-8"?> <!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ +/** + * Copyright 2018 Adobe + * All Rights Reserved. + */ --> <xs:schema elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema"> @@ -34,10 +34,9 @@ <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="fillFieldType" name="fillSecretField" minOccurs="0" maxOccurs="unbounded"/> <xs:element type="loadSessionSnapshotType" name="loadSessionSnapshot" minOccurs="0" maxOccurs="unbounded"/> <xs:element type="makeScreenshotType" name="makeScreenshot" minOccurs="0" maxOccurs="unbounded"/> <xs:element type="maximizeWindowType" name="maximizeWindow" minOccurs="0" maxOccurs="unbounded"/> @@ -45,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"/> @@ -71,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"> @@ -250,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> @@ -291,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> @@ -432,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. @@ -445,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..f1ca89c8a --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Test/etc/mergedActionGroupSchema.xsd @@ -0,0 +1,76 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ +--> + +<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 b663bb99d..cb0631a86 100644 --- a/src/Magento/FunctionalTestingFramework/Test/etc/mergedTestSchema.xsd +++ b/src/Magento/FunctionalTestingFramework/Test/etc/mergedTestSchema.xsd @@ -1,9 +1,9 @@ <?xml version="1.0" encoding="UTF-8"?> <!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ +/** + * Copyright 2018 Adobe + * All Rights Reserved. + */ --> <xs:schema elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema"> @@ -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> @@ -130,6 +137,5 @@ </xs:annotation> </xs:attribute> <xs:attributeGroup ref="commonActionAttributes"/> - <xs:attribute type="xs:boolean" name="skipReadiness" use="prohibited"/> </xs:complexType> </xs:schema> diff --git a/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd b/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd index f9b09a86f..a57f4c1d4 100644 --- a/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd +++ b/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd @@ -1,9 +1,9 @@ <?xml version="1.0" encoding="UTF-8"?> <!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ +/** + * Copyright 2017 Adobe + * All Rights Reserved. + */ --> <xs:schema elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema"> @@ -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..134f7fed4 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Upgrade/RemoveModuleFileInSuiteFiles.php @@ -0,0 +1,152 @@ +<?php +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ + +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..3b1b5b734 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Upgrade/RemoveUnusedArguments.php @@ -0,0 +1,65 @@ +<?php +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ + +namespace Magento\FunctionalTestingFramework\Upgrade; + +use Magento\FunctionalTestingFramework\StaticCheck\ActionGroupStandardsCheck; +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 ActionGroupStandardsCheck(); + /** @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..ac6731d2f --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Upgrade/RenameMetadataFiles.php @@ -0,0 +1,73 @@ +<?php +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ + +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..c45d23193 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Upgrade/SplitMultipleEntitiesFiles.php @@ -0,0 +1,237 @@ +<?php +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ + +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..7e676ffdb --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Upgrade/UpdateAssertionSchema.php @@ -0,0 +1,167 @@ +<?php +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ + +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..f35568313 100644 --- a/src/Magento/FunctionalTestingFramework/Upgrade/UpdateTestSchemaPaths.php +++ b/src/Magento/FunctionalTestingFramework/Upgrade/UpdateTestSchemaPaths.php @@ -1,14 +1,16 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2018 Adobe + * All Rights Reserved. */ 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..4f713902f 100644 --- a/src/Magento/FunctionalTestingFramework/Upgrade/UpgradeInterface.php +++ b/src/Magento/FunctionalTestingFramework/Upgrade/UpgradeInterface.php @@ -1,12 +1,13 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2018 Adobe + * All Rights Reserved. */ 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..22fd98d9d 100644 --- a/src/Magento/FunctionalTestingFramework/Upgrade/UpgradeScriptList.php +++ b/src/Magento/FunctionalTestingFramework/Upgrade/UpgradeScriptList.php @@ -1,8 +1,9 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2018 Adobe + * All Rights Reserved. */ + declare(strict_types=1); namespace Magento\FunctionalTestingFramework\Upgrade; @@ -28,7 +29,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/Upgrade/UpgradeScriptListInterface.php b/src/Magento/FunctionalTestingFramework/Upgrade/UpgradeScriptListInterface.php index a96316797..00202c93c 100644 --- a/src/Magento/FunctionalTestingFramework/Upgrade/UpgradeScriptListInterface.php +++ b/src/Magento/FunctionalTestingFramework/Upgrade/UpgradeScriptListInterface.php @@ -1,8 +1,9 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2018 Adobe + * All Rights Reserved. */ + namespace Magento\FunctionalTestingFramework\Upgrade; /** diff --git a/src/Magento/FunctionalTestingFramework/Util/ComposerModuleResolver.php b/src/Magento/FunctionalTestingFramework/Util/ComposerModuleResolver.php new file mode 100644 index 000000000..029c2674c --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Util/ComposerModuleResolver.php @@ -0,0 +1,180 @@ +<?php +/** + * Copyright 2019 Adobe + * All Rights Reserved. + */ + +namespace Magento\FunctionalTestingFramework\Util; + +use Magento\FunctionalTestingFramework\Composer\ComposerInstall; +use Magento\FunctionalTestingFramework\Composer\ComposerPackage; +use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; + +/** + * Composer Based Module Resolver + */ +class ComposerModuleResolver +{ + /** + * Code path array from composer json search + * + * @var array + */ + private $searchedTestModules = null; + + /** + * Code path array from composer installed test packages + * + * @var array + */ + private $installedTestModules = null; + + /** + * ComposerModuleResolver constructor + */ + public function __construct() + { + } + + /** + * Get code paths for installed test modules + * + * @param string $rootComposerFile + * @return array + * @throws TestFrameworkException + */ + public function getComposerInstalledTestModules($rootComposerFile) + { + if (null !== $this->installedTestModules) { + return $this->installedTestModules; + } + + if (!file_exists($rootComposerFile) || basename($rootComposerFile, '.json') !== 'composer') { + throw new TestFrameworkException("Invalid root composer json file: {$rootComposerFile}"); + } + + $this->installedTestModules = []; + $composer = new ComposerInstall($rootComposerFile); + + foreach ($composer->getInstalledTestPackages() as $packageName => $packageData) { + $suggestedModuleNames = $packageData[ComposerInstall::PACKAGE_SUGGESTED_MAGENTO_MODULES]; + $path = $packageData[ComposerInstall::PACKAGE_INSTALLEDPATH]; + $this->installedTestModules[$path] = $suggestedModuleNames; + } + return $this->installedTestModules; + } + + /** + * Get code paths by searching test module composer json file from input directories + * + * @param array $directories + * @return array + * @throws TestFrameworkException + */ + public function getTestModulesFromPaths($directories) + { + if (null !== $this->searchedTestModules) { + return $this->searchedTestModules; + } + + $this->searchedTestModules = []; + foreach ($directories as $directory) { + $this->searchedTestModules = array_merge_recursive( + $this->searchedTestModules, + $this->getTestModules($directory) + ); + } + return $this->searchedTestModules; + } + + /** + * Get code paths by searching test module composer json file from input directory + * + * @param string $directory + * @return array + * @throws TestFrameworkException + */ + private function getTestModules($directory) + { + $normalizedDir = realpath($directory); + if (!is_dir($normalizedDir)) { + throw new TestFrameworkException("Invalid directory: {$directory}"); + } + + // Find all composer json files under directory + $modules = []; + $fileList = $this->findComposerJsonFilesAtDepth($normalizedDir, 2); + foreach ($fileList as $file) { + // Parse composer json for test module name and path information + $composerInfo = new ComposerPackage($file); + if ($composerInfo->isMftfTestPackage()) { + $modulePath = str_replace( + DIRECTORY_SEPARATOR . 'composer.json', + '', + $file + ); + $suggestedMagentoModuleNames = $composerInfo->getSuggestedMagentoModules(); + if (array_key_exists($modulePath, $modules)) { + $modules[$modulePath] = array_merge($modules[$modulePath], $suggestedMagentoModuleNames); + } else { + $modules[$modulePath] = $suggestedMagentoModuleNames; + } + } + } + return $modules; + } + + /** + * Find absolute paths of all composer json files in a given directory + * + * @param string $directory + * @return array + */ + private function findAllComposerJsonFiles($directory) + { + $directory = realpath($directory); + $jsonPattern = DIRECTORY_SEPARATOR . "composer.json"; + $subDirectoryPattern = DIRECTORY_SEPARATOR . "*"; + + $jsonFileList = []; + foreach (glob($directory . $subDirectoryPattern, GLOB_ONLYDIR) as $dir) { + $jsonFileList = array_merge_recursive($jsonFileList, self::findAllComposerJsonFiles($dir)); + } + + $curJsonFiles = glob($directory . $jsonPattern); + if ($curJsonFiles !== false && !empty($curJsonFiles)) { + $jsonFileList = array_merge_recursive($jsonFileList, $curJsonFiles); + } + return $jsonFileList; + } + + /** + * Find absolute paths of all composer json files in a given directory at certain depths + * + * @param string $directory + * @param integer $depth + * @return array + */ + private function findComposerJsonFilesAtDepth($directory, $depth) + { + $directory = realpath($directory); + $jsonPattern = DIRECTORY_SEPARATOR . "composer.json"; + $subDirectoryPattern = DIRECTORY_SEPARATOR . "*"; + + $jsonFileList = []; + if ($depth > 0) { + foreach (glob($directory . $subDirectoryPattern, GLOB_ONLYDIR) as $dir) { + $jsonFileList = array_merge_recursive( + $jsonFileList, + self::findComposerJsonFilesAtDepth($dir, $depth-1) + ); + } + } elseif ($depth === 0) { + $jsonFileList = glob($directory . $jsonPattern); + if ($jsonFileList === false) { + $jsonFileList = []; + } + } + return $jsonFileList; + } +} diff --git a/src/Magento/FunctionalTestingFramework/Util/ConfigSanitizerUtil.php b/src/Magento/FunctionalTestingFramework/Util/ConfigSanitizerUtil.php index ce5740493..872c1ef4e 100644 --- a/src/Magento/FunctionalTestingFramework/Util/ConfigSanitizerUtil.php +++ b/src/Magento/FunctionalTestingFramework/Util/ConfigSanitizerUtil.php @@ -1,12 +1,14 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ 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/Env/EnvProcessor.php b/src/Magento/FunctionalTestingFramework/Util/Env/EnvProcessor.php index f09ab63fa..eeed8fef1 100644 --- a/src/Magento/FunctionalTestingFramework/Util/Env/EnvProcessor.php +++ b/src/Magento/FunctionalTestingFramework/Util/Env/EnvProcessor.php @@ -1,12 +1,16 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2018 Adobe + * All Rights Reserved. */ + declare(strict_types = 1); 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 +49,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..0b195e9b2 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Util/Filesystem/CestFileCreatorUtil.php @@ -0,0 +1,69 @@ +<?php +/** + * Copyright 2021 Adobe + * All Rights Reserved. + */ + +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..93a1f34c1 100644 --- a/src/Magento/FunctionalTestingFramework/Util/Filesystem/DirSetupUtil.php +++ b/src/Magento/FunctionalTestingFramework/Util/Filesystem/DirSetupUtil.php @@ -1,7 +1,7 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ namespace Magento\FunctionalTestingFramework\Util\Filesystem; @@ -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..db7da14ba --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Util/GenerationErrorHandler.php @@ -0,0 +1,177 @@ +<?php +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ + +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..35eebbdd1 100644 --- a/src/Magento/FunctionalTestingFramework/Util/Iterator/AbstractIterator.php +++ b/src/Magento/FunctionalTestingFramework/Util/Iterator/AbstractIterator.php @@ -1,7 +1,7 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ namespace Magento\FunctionalTestingFramework\Util\Iterator; @@ -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..6b5c5a3c4 100644 --- a/src/Magento/FunctionalTestingFramework/Util/Iterator/File.php +++ b/src/Magento/FunctionalTestingFramework/Util/Iterator/File.php @@ -1,7 +1,7 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ namespace Magento\FunctionalTestingFramework\Util\Iterator; @@ -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 78b0c29ef..f0badec28 100644 --- a/src/Magento/FunctionalTestingFramework/Util/Logger/LoggingUtil.php +++ b/src/Magento/FunctionalTestingFramework/Util/Logger/LoggingUtil.php @@ -1,13 +1,14 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2018 Adobe + * All Rights Reserved. */ 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 { @@ -19,64 +20,72 @@ class LoggingUtil private $loggers = []; /** - * Singleton LogginUtil Instance + * Singleton LoggingUtil Instance * * @var LoggingUtil */ - private static $INSTANCE; + private static $instance; /** * Singleton accessor for instance variable * * @return LoggingUtil */ - public static function getInstance() + public static function getInstance(): LoggingUtil { - if (self::$INSTANCE == null) { - self::$INSTANCE = new LoggingUtil(); + if (self::$instance === null) { + self::$instance = new LoggingUtil(); } - - return self::$INSTANCE; + return self::$instance; } /** - * Constructor for Logging Util + * Avoids instantiation of LoggingUtil by new. + * @return void */ private function __construct() { - // private constructor + } + + /** + * Avoids instantiation of LoggingUtil by clone. + * @return void + */ + private function __clone() + { } /** * Creates a new logger instances based on class name if it does not exist. If logger instance already exists, the * existing instance is simply returned. * - * @param string $clazz + * @param string $className * @return MftfLogger - * @throws \Exception + * @throws TestFrameworkException */ - public function getLogger($clazz) + public function getLogger($className): MftfLogger { - if ($clazz == null) { - throw new \Exception("You must pass a class to receive a logger"); + if ($className === null) { + throw new TestFrameworkException("You must pass a class name to receive a logger"); } - if (!array_key_exists($clazz, $this->loggers)) { - $logger = new MftfLogger($clazz); + if (!array_key_exists($className, $this->loggers)) { + $logger = new MftfLogger($className); $logger->pushHandler(new StreamHandler($this->getLoggingPath())); - $this->loggers[$clazz] = $logger; + $this->loggers[$className] = $logger; } - return $this->loggers[$clazz]; + return $this->loggers[$className]; } /** * Function which returns a static path to the the log file. * * @return string + * @throws TestFrameworkException */ - public function getLoggingPath() + 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 5844858a0..438ffba08 100644 --- a/src/Magento/FunctionalTestingFramework/Util/Logger/MftfLogger.php +++ b/src/Magento/FunctionalTestingFramework/Util/Logger/MftfLogger.php @@ -1,31 +1,100 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2018 Adobe + * All Rights Reserved. */ 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 { /** - * Prints a deprecation warning, as well as adding a log at the WARNING level. + * MFTF execution phase * - * @param string $message The log message. - * @param array $context The log context. + * @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 boolean $verbose * @return void */ - public function deprecation($message, array $context = []) + public function deprecation($message, array $context = [], $verbose = false) { $message = "DEPRECATION: " . $message; + // print during test generation including metadata + if ((array_key_exists('operationType', $context) || + $this->phase === MftfApplicationConfig::GENERATION_PHASE) && $verbose) { + print ($message . json_encode($context) . "\n"); + } + // 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 boolean $verbose + * @return void + */ + 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"); } - parent::warning($message, $context); + // 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 69% rename from src/Magento/FunctionalTestingFramework/Util/Manifest/ParallelTestManifest.php rename to src/Magento/FunctionalTestingFramework/Util/Manifest/BaseParallelTestManifest.php index 9b12cfd00..fdc4006a1 100644 --- a/src/Magento/FunctionalTestingFramework/Util/Manifest/ParallelTestManifest.php +++ b/src/Magento/FunctionalTestingFramework/Util/Manifest/BaseParallelTestManifest.php @@ -1,65 +1,64 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2021 Adobe + * All Rights Reserved. */ 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/BaseTestManifest.php b/src/Magento/FunctionalTestingFramework/Util/Manifest/BaseTestManifest.php index f7ad8aea2..534f7905d 100644 --- a/src/Magento/FunctionalTestingFramework/Util/Manifest/BaseTestManifest.php +++ b/src/Magento/FunctionalTestingFramework/Util/Manifest/BaseTestManifest.php @@ -1,7 +1,7 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2018 Adobe + * All Rights Reserved. */ namespace Magento\FunctionalTestingFramework\Util\Manifest; diff --git a/src/Magento/FunctionalTestingFramework/Util/Manifest/DefaultTestManifest.php b/src/Magento/FunctionalTestingFramework/Util/Manifest/DefaultTestManifest.php index eb4f79db2..36779c74a 100644 --- a/src/Magento/FunctionalTestingFramework/Util/Manifest/DefaultTestManifest.php +++ b/src/Magento/FunctionalTestingFramework/Util/Manifest/DefaultTestManifest.php @@ -1,7 +1,7 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2018 Adobe + * All Rights Reserved. */ namespace Magento\FunctionalTestingFramework\Util\Manifest; @@ -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..3c45b3c7c --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Util/Manifest/ParallelByGroupTestManifest.php @@ -0,0 +1,47 @@ +<?php +/** + * Copyright 2021 Adobe + * All Rights Reserved. + */ + +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..d71037211 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Util/Manifest/ParallelByTimeTestManifest.php @@ -0,0 +1,52 @@ +<?php +/** + * Copyright 2021 Adobe + * All Rights Reserved. + */ + +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/SingleRunTestManifest.php b/src/Magento/FunctionalTestingFramework/Util/Manifest/SingleRunTestManifest.php index 417fca686..263bf58f5 100644 --- a/src/Magento/FunctionalTestingFramework/Util/Manifest/SingleRunTestManifest.php +++ b/src/Magento/FunctionalTestingFramework/Util/Manifest/SingleRunTestManifest.php @@ -1,7 +1,7 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2018 Adobe + * All Rights Reserved. */ namespace Magento\FunctionalTestingFramework\Util\Manifest; diff --git a/src/Magento/FunctionalTestingFramework/Util/Manifest/TestManifestFactory.php b/src/Magento/FunctionalTestingFramework/Util/Manifest/TestManifestFactory.php index 1cfa6559e..1dffc38d9 100644 --- a/src/Magento/FunctionalTestingFramework/Util/Manifest/TestManifestFactory.php +++ b/src/Magento/FunctionalTestingFramework/Util/Manifest/TestManifestFactory.php @@ -1,12 +1,14 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2018 Adobe + * All Rights Reserved. */ 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..ed7c96120 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Util/MftfGlobals.php @@ -0,0 +1,163 @@ +<?php +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ + +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 33e559ea8..a6232fc7f 100644 --- a/src/Magento/FunctionalTestingFramework/Util/ModulePathExtractor.php +++ b/src/Magento/FunctionalTestingFramework/Util/ModulePathExtractor.php @@ -1,7 +1,7 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2018 Adobe + * All Rights Reserved. */ namespace Magento\FunctionalTestingFramework\Util; @@ -11,41 +11,89 @@ */ class ModulePathExtractor { - const MAGENTO = 'Magento'; + const SPLIT_DELIMITER = '_'; + + /** + * Test module paths + * + * @var array + */ + private $testModulePaths = []; + + /** + * ModulePathExtractor constructor + */ + public function __construct() + { + $verbosePath = true; + if (empty($this->testModulePaths)) { + $this->testModulePaths = ModuleResolver::getInstance()->getModulesPath($verbosePath); + } + } /** * Extracts module name from the path given + * * @param string $path * @return string */ public function extractModuleName($path) { - if (empty($path)) { - return "NO MODULE DETECTED"; - } - $paths = explode(DIRECTORY_SEPARATOR, $path); - if (count($paths) < 3) { + $key = $this->extractKeyByPath($path); + if (empty($key)) { return "NO MODULE DETECTED"; - } elseif ($paths[count($paths)-3] == "Mftf") { - // app/code/Magento/[Analytics]/Test/Mftf/Test/SomeText.xml - return $paths[count($paths)-5]; } - // dev/tests/acceptance/tests/functional/Magento/FunctionalTest/[Analytics]/Test/SomeText.xml - return $paths[count($paths)-3]; + $parts = $this->splitKeyForParts($key); + return isset($parts[1]) ? $parts[1] : "NO MODULE DETECTED"; } /** - * Extracts the extension form the path, Magento for dev/tests/acceptance, [name] before module otherwise + * Extracts vendor name for module from the path given + * * @param string $path * @return string */ public function getExtensionPath($path) { - $paths = explode(DIRECTORY_SEPARATOR, $path); - if ($paths[count($paths)-3] == "Mftf") { - // app/code/[Magento]/Analytics/Test/Mftf/Test/SomeText.xml - return $paths[count($paths)-6]; + $key = $this->extractKeyByPath($path); + if (empty($key)) { + return "NO VENDOR DETECTED"; + } + $parts = $this->splitKeyForParts($key); + return isset($parts[0]) ? $parts[0] : "NO VENDOR DETECTED"; + } + + /** + * Split key by SPLIT_DELIMITER and return parts array + * + * @param string $key + * @return array + */ + private function splitKeyForParts($key) + { + $parts = explode(self::SPLIT_DELIMITER, $key); + return count($parts) === 2 ? $parts : []; + } + + /** + * Extract module name key by path + * + * @param string $path + * @return string + */ + private function extractKeyByPath($path) + { + $shortenedPath = dirname(dirname($path)); + // Ignore this path if we cannot go to parent directory two levels up + if (empty($shortenedPath) || $shortenedPath === '.') { + return ''; + } + + foreach ($this->testModulePaths as $key => $value) { + if (substr($path, 0, strlen($value)) === $value) { + return $key; + } } - return self::MAGENTO; + return ''; } } diff --git a/src/Magento/FunctionalTestingFramework/Util/ModuleResolver.php b/src/Magento/FunctionalTestingFramework/Util/ModuleResolver.php index 0a5974387..dd45efbf0 100644 --- a/src/Magento/FunctionalTestingFramework/Util/ModuleResolver.php +++ b/src/Magento/FunctionalTestingFramework/Util/ModuleResolver.php @@ -1,33 +1,64 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ namespace Magento\FunctionalTestingFramework\Util; use Magento\FunctionalTestingFramework\Config\MftfApplicationConfig; +use Magento\FunctionalTestingFramework\DataGenerator\Handlers\CredentialStore; +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. * * @api + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + * @SuppressWarnings(PHPMD.ExcessiveClassComplexity) */ 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. */ const CUSTOM_MODULE_PATHS = 'CUSTOM_MODULE_PATHS'; + /** + * List of path types present in Magento Component Registrar + */ + const PATHS = ['module', 'library', 'theme', 'language']; + + /** + * Magento Registrar Class + */ + const REGISTRAR_CLASS = "\Magento\Framework\Component\ComponentRegistrar"; + + const TEST_MFTF_PATTERN = 'Test' . DIRECTORY_SEPARATOR . 'Mftf'; + const VENDOR = 'vendor'; + const APP_CODE = 'app' . DIRECTORY_SEPARATOR . "code"; + const DEV_TESTS = 'dev' + . DIRECTORY_SEPARATOR + . 'tests' + . DIRECTORY_SEPARATOR + . 'acceptance' + . DIRECTORY_SEPARATOR + . 'tests' + . DIRECTORY_SEPARATOR + . 'functional'; + /** * Enabled modules. * @@ -42,6 +73,13 @@ class ModuleResolver */ protected $enabledModulePaths = null; + /** + * Name and path for enabled modules + * + * @var array|null + */ + protected $enabledModuleNameAndPaths = null; + /** * Configuration instance. * @@ -96,7 +134,7 @@ class ModuleResolver * * @var array */ - protected $moduleBlacklist = [ + protected $moduleBlocklist = [ 'SampleTests', 'SampleTemplates' ]; @@ -119,15 +157,20 @@ 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); + } } /** * Return an array of enabled modules of target Magento instance. * * @return array + * @throws TestFrameworkException + * @throws FastFailException */ public function getEnabledModules() { @@ -135,13 +178,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, @@ -156,12 +199,14 @@ public function getEnabledModules() if (!$response) { $message = "Could not retrieve Modules from Magento Instance."; + $encryptedSecret = CredentialStore::getInstance()->getSecret('magento/MAGENTO_ADMIN_PASSWORD'); + $secret = CredentialStore::getInstance()->decryptSecretValue($encryptedSecret); $context = [ "Admin Module List Url" => $url, "MAGENTO_ADMIN_USERNAME" => getenv("MAGENTO_ADMIN_USERNAME"), - "MAGENTO_ADMIN_PASSWORD" => getenv("MAGENTO_ADMIN_PASSWORD"), + "MAGENTO_ADMIN_PASSWORD" => $secret, ]; - throw new TestFrameworkException($message, $context); + throw new FastFailException($message, $context); } $this->enabledModules = json_decode($response); @@ -169,126 +214,276 @@ public function getEnabledModules() return $this->enabledModules; } - /** - * Return an array of module whitelist that not exist in target Magento instance. - * - * @return array - */ - protected function getModuleWhitelist() - { - $moduleWhitelist = getenv(self::MODULE_WHITELIST); - - if (empty($moduleWhitelist)) { - return []; - } - return array_map('trim', explode(',', $moduleWhitelist)); - } - /** * Return the modules path based on which modules are enabled in the target Magento instance. * + * @param boolean $verbosePath * @return array + * @throws TestFrameworkException + * @throws FastFailException */ - public function getModulesPath() + public function getModulesPath($verbosePath = false) { - if (isset($this->enabledModulePaths)) { + if (isset($this->enabledModulePaths) && !$verbosePath) { return $this->enabledModulePaths; } - $allModulePaths = $this->aggregateTestModulePaths(); + if (isset($this->enabledModuleNameAndPaths) && $verbosePath) { + return $this->enabledModuleNameAndPaths; + } + + // Find test modules paths by searching patterns (Test/Mftf, etc) + $allModulePaths = ModuleResolverService::getInstance()->aggregateTestModulePaths(); + + // Find test modules paths by searching test composer.json files + $composerBasedModulePaths = $this->aggregateTestModulePathsFromComposerJson(); + + // Find test modules paths by querying composer installed packages + $composerBasedModulePaths = array_merge( + $composerBasedModulePaths, + $this->aggregateTestModulePathsFromComposerInstaller() + ); + + // Merge test module paths altogether + $allModulePaths = $this->mergeModulePaths($allModulePaths, $composerBasedModulePaths); + + // Normalize module names if we get registered module names from Magento system + $allModulePaths = $this->normalizeModuleNames($allModulePaths); if (MftfApplicationConfig::getConfig()->forceGenerateEnabled()) { + $allModulePaths = $this->flipAndSortModulePathsArray($allModulePaths, true); $this->enabledModulePaths = $this->applyCustomModuleMethods($allModulePaths); return $this->enabledModulePaths; } - $enabledModules = array_merge($this->getEnabledModules(), $this->getModuleWhitelist()); - $enabledDirectoryPaths = $this->getEnabledDirectoryPaths($enabledModules, $allModulePaths); - + $enabledModules = array_merge($this->getEnabledModules(), $this->getModuleAllowlist()); + $enabledDirectoryPaths = $this->flipAndFilterModulePathsArray($allModulePaths, $enabledModules); $this->enabledModulePaths = $this->applyCustomModuleMethods($enabledDirectoryPaths); + return $this->enabledModulePaths; } /** - * Retrieves all module directories which might contain pertinent test code. + * Sort files according module sequence. + * + * @param array $files + * @return array + */ + public function sortFilesByModuleSequence(array $files) + { + return $this->sequenceSorter->sort($files); + } + + /** + * Return an array of module allowlist that not exist in target Magento instance. * * @return array */ - private function aggregateTestModulePaths() + protected function getModuleAllowlist() { - $allModulePaths = []; + $moduleAllowlist = getenv(self::MODULE_ALLOWLIST); - // Define the Module paths from app/code - $appCodePath = MAGENTO_BP - . DIRECTORY_SEPARATOR - . 'app' . DIRECTORY_SEPARATOR - . 'code' . DIRECTORY_SEPARATOR; + if (empty($moduleAllowlist)) { + return []; + } + return array_map('trim', explode(',', $moduleAllowlist)); + } - // Define the Module paths from default TESTS_MODULE_PATH - $modulePath = defined('TESTS_MODULE_PATH') ? TESTS_MODULE_PATH : TESTS_BP; - $modulePath = rtrim($modulePath, DIRECTORY_SEPARATOR); + /** + * Aggregate all code paths with test module composer json files + * + * @return array + * @throws TestFrameworkException + */ + private function aggregateTestModulePathsFromComposerJson() + { + // Define the module paths + $magentoBaseCodePath = FilePathFormatter::format(MAGENTO_BP, false); - // Define the Module paths from vendor modules - $vendorCodePath = PROJECT_ROOT - . DIRECTORY_SEPARATOR - . 'vendor' . DIRECTORY_SEPARATOR; + // Define the module paths from default TESTS_MODULE_PATH + $modulePath = defined('TESTS_MODULE_PATH') ? TESTS_MODULE_PATH : TESTS_BP; + $modulePath = FilePathFormatter::format($modulePath, false); - $codePathsToPattern = [ - $modulePath => '', - $appCodePath => DIRECTORY_SEPARATOR . 'Test' . DIRECTORY_SEPARATOR . 'Mftf', - $vendorCodePath => DIRECTORY_SEPARATOR . 'Test' . DIRECTORY_SEPARATOR . 'Mftf' + $searchCodePaths = [ + $magentoBaseCodePath . DIRECTORY_SEPARATOR . self::DEV_TESTS, ]; - foreach ($codePathsToPattern as $codePath => $pattern) { - $allModulePaths = array_merge_recursive($allModulePaths, $this->globRelevantPaths($codePath, $pattern)); + // Add TESTS_MODULE_PATH if it's not included + if (array_search($modulePath, $searchCodePaths) === false) { + $searchCodePaths[] = $modulePath; } - return $allModulePaths; + return ModuleResolverService::getInstance()->getComposerJsonTestModulePaths($searchCodePaths); + } + + /** + * Aggregate all code paths with composer installed test modules + * + * @return array + */ + private function aggregateTestModulePathsFromComposerInstaller() + { + // Define the module paths + $magentoBaseCodePath = MAGENTO_BP; + $composerFile = $magentoBaseCodePath . DIRECTORY_SEPARATOR . 'composer.json'; + + return ModuleResolverService::getInstance()->getComposerInstalledTestModulePaths($composerFile); } /** - * 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. + * Flip and filter module code paths * - * @param string $testPath - * @param string $pattern + * @param array $objectArray + * @param array $filterArray * @return array */ - private function globRelevantPaths($testPath, $pattern) + private function flipAndFilterModulePathsArray($objectArray, $filterArray) { - $modulePaths = []; - $relevantPaths = []; + $oneToOneArray = []; + $oneToManyArray = []; + // 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) { + $oneToOneArray[$path] = $modules[0]; + } else { + $oneToManyArray[$path] = $modules; + } + } + } - if (file_exists($testPath)) { - $relevantPaths = $this->globRelevantWrapper($testPath, $pattern); + $flippedArray = []; + // Set flipped array for "one path => one module" case first to maintain module sequencing + foreach ($filterArray as $moduleName) { + $path = array_search($moduleName, $oneToOneArray); + if ($path !== false) { + if (strpos($moduleName, '_') === false) { + $moduleName = $this->findVendorNameFromPath($path) . '_' . $moduleName; + } + $flippedArray = $this->setArrayValueWithLogging($flippedArray, $moduleName, $path); + unset($oneToOneArray[$path]); + } } - foreach ($relevantPaths as $codePath) { - $mainModName = basename(str_replace($pattern, '', $codePath)); - $modulePaths[$mainModName][] = $codePath; + // Set flipped array for everything else + return $this->flipAndSortModulePathsArray( + array_merge($oneToOneArray, $oneToManyArray), + false, + $flippedArray + ); + } - if (MftfApplicationConfig::getConfig()->verboseEnabled()) { - LoggingUtil::getInstance()->getLogger(ModuleResolver::class)->debug( - "including module", - ['module' => $mainModName, 'path' => $codePath] - ); + /** + * Flip module code paths and optionally sort in alphabetical order + * + * @param array $objectArray + * @param boolean $sort + * @param array $inFlippedArray + * @return array + */ + private function flipAndSortModulePathsArray($objectArray, $sort, $inFlippedArray = []) + { + $flippedArray = $inFlippedArray; + + // Set flipped array from object array + 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. + // 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]; + } else { + $module = $modules[0]; + } + } else { + if (strpos($modules, '_') === false) { + $module = $this->findVendorNameFromPath($path) . '_' . $modules; + } else { + $module = $modules; + } } + $flippedArray = $this->setArrayValueWithLogging($flippedArray, $module, $path); } - return $modulePaths; + // Sort array in alphabetical order + if ($sort) { + ksort($flippedArray); + } + + return $flippedArray; } /** - * Glob wrapper for globRelevantPaths function + * Set array value at index only if array value at index is not yet set, skip otherwise and log warning message + * + * @param array $inArray + * @param string $index + * @param string $value * - * @param string $testPath - * @param string $pattern * @return array */ - private static function globRelevantWrapper($testPath, $pattern) + private function setArrayValueWithLogging($inArray, $index, $value) { - return glob($testPath . '*' . DIRECTORY_SEPARATOR . '*' . $pattern); + $outArray = $inArray; + if (!isset($inArray[$index])) { + $outArray[$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)->warning($warnMsg); + } + return $outArray; + } + + /** + * Merge code paths + * + * @param array $oneToOneArray + * @param array $oneToManyArray + * @return array + */ + private function mergeModulePaths($oneToOneArray, $oneToManyArray) + { + $mergedArray = $oneToOneArray; + foreach ($oneToManyArray as $path => $modules) { + // Do nothing when array_key_exists + if (!array_key_exists($path, $oneToOneArray)) { + $mergedArray[$path] = $modules; + } + } + return $mergedArray; + } + + /** + * Normalize module name if registered module list is available + * + * @param array $codePaths + * + * @return array + */ + private function normalizeModuleNames($codePaths) + { + $allComponents = ModuleResolverService::getInstance()->getRegisteredModuleList(); + if (empty($allComponents)) { + return $codePaths; + } + + $normalizedCodePaths = []; + foreach ($codePaths as $path => $moduleNames) { + $mainModName = array_search($path, $allComponents); + if ($mainModName) { + $normalizedCodePaths[$path] = [$mainModName]; + } else { + $normalizedCodePaths[$path] = $moduleNames; + } + } + + return $normalizedCodePaths; } /** @@ -309,32 +504,6 @@ private function flattenAllModulePaths($modulePaths) return $resultArray; } - /** - * Runs through enabled modules and maps them known module paths by name. - * @param array $enabledModules - * @param array $allModulePaths - * @return array - */ - private function getEnabledDirectoryPaths($enabledModules, $allModulePaths) - { - $enabledDirectoryPaths = []; - foreach ($enabledModules as $magentoModuleName) { - // Magento_Backend -> Backend or DevDocs -> DevDocs (if whitelisted has no underscore) - $moduleShortName = explode('_', $magentoModuleName)[1] ?? $magentoModuleName; - if (!isset($this->knownDirectories[$moduleShortName]) && !isset($allModulePaths[$moduleShortName])) { - continue; - } elseif (isset($this->knownDirectories[$moduleShortName]) && !isset($allModulePaths[$moduleShortName])) { - LoggingUtil::getInstance()->getLogger(ModuleResolver::class)->warn( - "Known directory could not match to an existing path.", - ['knownDirectory' => $moduleShortName] - ); - } else { - $enabledDirectoryPaths[$moduleShortName] = $allModulePaths[$moduleShortName]; - } - } - return $enabledDirectoryPaths; - } - /** * Executes a REST call to the supplied Magento Base Url for version information to display during generation * @@ -345,7 +514,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] @@ -366,76 +535,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 || !isset($_ENV['MAGENTO_BASE_URL'])) { - $message = "Cannot retrieve API token without credentials and base url, please fill out .env."; - $context = [ - "MAGENTO_BASE_URL" => getenv("MAGENTO_BASE_URL"), - "MAGENTO_ADMIN_USERNAME" => getenv("MAGENTO_ADMIN_USERNAME"), - "MAGENTO_ADMIN_PASSWORD" => getenv("MAGENTO_ADMIN_PASSWORD"), - ]; - throw new TestFrameworkException($message, $context); - } - - $url = ConfigSanitizerUtil::sanitizeUrl($_ENV['MAGENTO_BASE_URL']) . $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 Instance at given 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); - } - - /** - * Sort files according module sequence. - * - * @param array $files - * @return array - */ - public function sortFilesByModuleSequence(array $files) - { - return $this->sequenceSorter->sort($files); - } - /** * A wrapping method for any custom logic which needs to be applied to the module list * @@ -444,30 +543,34 @@ public function sortFilesByModuleSequence(array $files) */ protected function applyCustomModuleMethods($modulesPath) { - $modulePathsResult = $this->removeBlacklistModules($modulesPath); - $customModulePaths = $this->getCustomModulePaths(); + $modulePathsResult = $this->removeBlocklistModules($modulesPath); + $customModulePaths = ModuleResolverService::getInstance()->getCustomModulePaths(); - array_map(function ($value) { + array_map(function ($key, $value) { LoggingUtil::getInstance()->getLogger(ModuleResolver::class)->info( "including custom module", - ['module' => $value] + [$key => $value] ); - }, $customModulePaths); + }, array_keys($customModulePaths), $customModulePaths); + if (!isset($this->enabledModuleNameAndPaths)) { + $this->enabledModuleNameAndPaths = array_merge($modulePathsResult, $customModulePaths); + } return $this->flattenAllModulePaths(array_merge($modulePathsResult, $customModulePaths)); } /** - * 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) { - 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", @@ -480,28 +583,51 @@ private function removeBlacklistModules($modulePaths) } /** - * Returns an array of custom module paths defined by the user + * Getter for moduleBlocklist. * * @return string[] */ - private function getCustomModulePaths() + private function getModuleBlocklist() { - $customModulePaths = getenv(self::CUSTOM_MODULE_PATHS); - - if (!$customModulePaths) { - return []; - } + return $this->moduleBlocklist; + } - return array_map('trim', explode(',', $customModulePaths)); + /** + * Find vendor and module name from path + * + * @param string $path + * @return string + */ + private function findVendorAndModuleNameFromPath($path) + { + $path = str_replace(DIRECTORY_SEPARATOR . self::TEST_MFTF_PATTERN, '', $path); + return $this->findVendorNameFromPath($path) . '_' . basename($path); } /** - * Getter for moduleBlacklist. + * Find vendor name from path * - * @return string[] + * @param string $path + * @return string */ - private function getModuleBlacklist() + private function findVendorNameFromPath($path) { - return $this->moduleBlacklist; + $possibleVendorName = 'UnknownVendor'; + $dirPaths = [ + self::VENDOR, + self::APP_CODE, + self::DEV_TESTS + ]; + + foreach ($dirPaths as $dirPath) { + $regex = "~.+\\/" . $dirPath . "\/(?<" . self::VENDOR . ">[^\/]+)\/.+~"; + $match = []; + preg_match($regex, $path, $match); + if (isset($match[self::VENDOR])) { + $possibleVendorName = ucfirst($match[self::VENDOR]); + return $possibleVendorName; + } + } + return $possibleVendorName; } } diff --git a/src/Magento/FunctionalTestingFramework/Util/ModuleResolver/AlphabeticSequenceSorter.php b/src/Magento/FunctionalTestingFramework/Util/ModuleResolver/AlphabeticSequenceSorter.php new file mode 100644 index 000000000..201aaa915 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Util/ModuleResolver/AlphabeticSequenceSorter.php @@ -0,0 +1,25 @@ +<?php +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ + +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..634f9beb7 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Util/ModuleResolver/ModuleResolverService.php @@ -0,0 +1,336 @@ +<?php +/** + * Copyright 2021 Adobe + * All Rights Reserved. + */ + +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/ModuleResolver/SequenceSorter.php b/src/Magento/FunctionalTestingFramework/Util/ModuleResolver/SequenceSorter.php index 10c285923..67ced4b6f 100644 --- a/src/Magento/FunctionalTestingFramework/Util/ModuleResolver/SequenceSorter.php +++ b/src/Magento/FunctionalTestingFramework/Util/ModuleResolver/SequenceSorter.php @@ -1,7 +1,7 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ namespace Magento\FunctionalTestingFramework\Util\ModuleResolver; diff --git a/src/Magento/FunctionalTestingFramework/Util/ModuleResolver/SequenceSorterInterface.php b/src/Magento/FunctionalTestingFramework/Util/ModuleResolver/SequenceSorterInterface.php index fa251c00f..9f473017f 100644 --- a/src/Magento/FunctionalTestingFramework/Util/ModuleResolver/SequenceSorterInterface.php +++ b/src/Magento/FunctionalTestingFramework/Util/ModuleResolver/SequenceSorterInterface.php @@ -1,9 +1,9 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ - + namespace Magento\FunctionalTestingFramework\Util\ModuleResolver; /** diff --git a/src/Magento/FunctionalTestingFramework/Util/Path/FilePathFormatter.php b/src/Magento/FunctionalTestingFramework/Util/Path/FilePathFormatter.php new file mode 100644 index 000000000..997b99824 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Util/Path/FilePathFormatter.php @@ -0,0 +1,34 @@ +<?php +/** + * Copyright 2019 Adobe + * All Rights Reserved. + */ + +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..5e5077816 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Util/Path/FormatterInterface.php @@ -0,0 +1,25 @@ +<?php +/** + * Copyright 2019 Adobe + * All Rights Reserved. + */ + +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..3cf8ba471 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Util/Path/UrlFormatter.php @@ -0,0 +1,121 @@ +<?php +/** + * Copyright 2019 Adobe + * All Rights Reserved. + */ + +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..d8fda01ff --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Util/Script/ScriptUtil.php @@ -0,0 +1,342 @@ +<?php +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ + +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."; + } + + $this->printTofile($errors, $filePath); + + $errorCount = count($errors); + + return $message . ": Errors found across {$errorCount} file(s). Error details output to {$filePath}"; + } + + /** + * Prints out given warnings to file, and returns summary result string + * @param array $warnings + * @param string $filePath + * @param string $message + * @return string + */ + public function printWarningsToFile(array $warnings, string $filePath, string $message): string + { + if (empty($warnings)) { + return $message . ": No warnings found."; + } + $this->printTofile($warnings, $filePath); + $errorCount = count($warnings); + + return $message . ": Warnings found across {$errorCount} file(s). Warning details output to {$filePath}"; + } + + /** + * Writes contents to filePath + * @param array $contents + * @param string $filePath + * @return void + */ + private function printTofile(array $contents, string $filePath) + { + $dirname = dirname($filePath); + if (!file_exists($dirname)) { + mkdir($dirname, 0777, true); + } + + $fileResource = fopen($filePath, 'w'); + + foreach ($contents as $test => $error) { + fwrite($fileResource, $error[0] . PHP_EOL); + } + + fclose($fileResource); + } + + /** + * 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..83d7d995e --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Util/Script/TestDependencyUtil.php @@ -0,0 +1,218 @@ +<?php +/** + * Copyright 2022 Adobe + * All Rights Reserved. + */ + +namespace Magento\FunctionalTestingFramework\Util\Script; + +use Magento\FunctionalTestingFramework\Test\Handlers\TestObjectHandler; +use Magento\FunctionalTestingFramework\Filter\FilterList; +use Magento\FunctionalTestingFramework\Config\MftfApplicationConfig; + +/** + * 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 $filterList + * @param array $extendedTestMapping + * @return array + */ + public function mergeDependenciesForExtendingTests( + array $testDependencies, + array $filterList, + array $extendedTestMapping = [] + ): array { + $testObjects = TestObjectHandler::getInstance()->getAllObjects(); + $filters = MftfApplicationConfig::getConfig()->getFilterList()->getFilters(); + $filteredTestNames = (count($filterList)>0)?$this->getFilteredTestNames($testObjects, $filters):[]; + $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) { + if (( + empty($filterList)) || + isset($filteredTestNames[$testDependencyArray[0]["test_name"]]) + ) { + $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; + } + + /** + * Return array of merge test modules and file path with same test name. + * @param array $testObjects + * @param array $filters + * @return array + */ + public function getFilteredTestNames(array $testObjects, array $filters) : array + { + foreach ($filters as $filter) { + $filter->filter($testObjects); + } + $testValues = array_map(function ($testObjects) { + return $testObjects->getName(); + }, $testObjects); + return $testValues; + } +} diff --git a/src/Magento/FunctionalTestingFramework/Util/Sorter/ParallelGroupSorter.php b/src/Magento/FunctionalTestingFramework/Util/Sorter/ParallelGroupSorter.php index 8654d64a7..c0507e18a 100644 --- a/src/Magento/FunctionalTestingFramework/Util/Sorter/ParallelGroupSorter.php +++ b/src/Magento/FunctionalTestingFramework/Util/Sorter/ParallelGroupSorter.php @@ -1,11 +1,12 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2018 Adobe + * All Rights Reserved. */ + 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 +27,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( + throw new FastFailException( "Please provide the argument '--time' to the robo command in order to". " generate grouped tests manifests for a parallel execution" ); @@ -47,12 +48,18 @@ public function getTestsGroupedBySize($suiteConfiguration, $testNameToSize, $tim $testGroups = []; $splitSuiteNamesToTests = $this->createGroupsWithinSuites($suiteConfiguration, $time); $splitSuiteNamesToSize = $this->getSuiteToSize($splitSuiteNamesToTests); - $entriesForGeneration = array_merge($testNameToSize, $splitSuiteNamesToSize); - arsort($entriesForGeneration); + arsort($testNameToSize); + arsort($splitSuiteNamesToSize); - $testNameToSizeForUse = $entriesForGeneration; + $testNameToSizeForUse = $testNameToSize; $nodeNumber = 1; - foreach ($entriesForGeneration as $testName => $testSize) { + + foreach ($splitSuiteNamesToSize as $testName => $testSize) { + $testGroups[$nodeNumber] = [$testName => $testSize]; + $nodeNumber++; + } + + foreach ($testNameToSize as $testName => $testSize) { if (!array_key_exists($testName, $testNameToSizeForUse)) { // skip tests which have already been added to a group continue; @@ -69,6 +76,241 @@ 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) + { + if (empty($suiteConfiguration)) { + return $this->convertArrayIndexStartingAtOne($this->splitTestsIntoGroups($testNameToSize, $groupTotal)); + } + + $suiteNameToTestSize = $this->getSuiteNameToTestSize($suiteConfiguration); + + // Calculate suite group totals + $suiteNameToGroupCount = $this->getSuiteGroupCounts($suiteNameToTestSize, $testNameToSize, $groupTotal); + + $suitesGroupTotal = array_sum($suiteNameToGroupCount); + + // Calculate minimum required groups + $minSuiteGroupTotal = count($suiteNameToTestSize); + $minTestGroupTotal = empty($testNameToSize) ? 0 : 1; + $minRequiredGroupTotal = $minSuiteGroupTotal + $minTestGroupTotal; + + if ($groupTotal < $minRequiredGroupTotal) { + throw new FastFailException( + "Invalid parameter 'groupTotal': must be equal or greater than {$minRequiredGroupTotal}" + ); + } elseif ($groupTotal < $suitesGroupTotal + $minTestGroupTotal) { + // Split in savvy mode when $groupTotal requested is very small + $testGroupTotal = $minTestGroupTotal; + // Reduce suite group total + $suiteNameToGroupCount = $this->reduceSuiteGroupTotal( + $suiteNameToGroupCount, + $groupTotal - $minTestGroupTotal + ); + } else { + // Calculate test group total + $testGroupTotal = $groupTotal - $suitesGroupTotal; + } + + // 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) + { + if (empty($suiteNameToTestSize)) { + return []; + } + + // 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 = (int)ceil($maxSuiteTime / $minGroupTime); + $ceilSuiteGroupTime = max(ceil($maxSuiteTime / $ceilSuiteGroupNumber), $minGroupTime); + $floorSuiteGroupNumber = (int)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 = $groupTotal - $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; + } + + /** + * Reduce total suite groups to a given $total. + * This method will reduce 1 from a suite that's greater than 1 repeatedly until sum of all groups reaches $total. + * + * @param array $suiteNameToGroupCount + * @param integer $total + * @return array + * @throws FastFailException + */ + private function reduceSuiteGroupTotal($suiteNameToGroupCount, $total) + { + if (count($suiteNameToGroupCount) > $total) { + throw new FastFailException( + "Invalid parameter 'total': must be equal or greater than {count($suiteNameToGroupCount)}" + ); + } + + $done = false; + while (!$done) { + foreach ($suiteNameToGroupCount as $suite => $count) { + if (array_sum($suiteNameToGroupCount) == $total) { + $done = true; + break; + } + if ($count > 1) { + $suiteNameToGroupCount[$suite] -= 1; + } + } + } + + 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((int)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) + { + if (empty($tests)) { + return []; + } + + // 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 * @@ -226,7 +468,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 @@ -246,8 +488,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++; @@ -268,11 +510,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 7afbfb5e6..0225f9ad8 100644 --- a/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php +++ b/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php @@ -1,29 +1,37 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ namespace Magento\FunctionalTestingFramework\Util; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\CredentialStore; +use Magento\FunctionalTestingFramework\Config\MftfApplicationConfig; 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 @@ -31,13 +39,47 @@ */ class TestGenerator { + const ACTION_GROUP_STEP_KEY_REGEX = "/\[(?<actionGroupStepKey>.*)\]/"; + const ACTION_STEP_KEY_REGEX = "/\/\/ stepKey: (?<stepKey>.*)/"; 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', + '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. @@ -79,25 +121,30 @@ class TestGenerator * * @var string */ - private $currentGenerationScope; + private $currentGenerationScope = TestGenerator::TEST_SCOPE; /** - * TestGenerator constructor. + * Test deprecation messages. + * + * @var array + */ + private $deprecationMessages = []; + + /** + * 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; @@ -111,7 +158,7 @@ private function __construct($exportDir, $tests, $debug = false) * @param boolean $debug * @return TestGenerator */ - public static function getInstance($dir = null, $tests = [], $debug = false) + public static function getInstance(?string $dir = null, $tests = [], $debug = false) { return new TestGenerator($dir, $tests, $debug); } @@ -132,6 +179,9 @@ public function getExportDir() * * @param array $testsToIgnore * @return array + * @throws TestReferenceException + * @throws TestFrameworkException + * @throws FastFailException */ private function loadAllTestObjects($testsToIgnore) { @@ -159,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); } /** @@ -182,10 +225,12 @@ 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) + public function createAllTestFiles(?BaseTestManifest $testManifest = null, ?array $testsToIgnore = null) { if ($this->tests === null) { // no-op if the test configuration is null @@ -202,6 +247,73 @@ public function createAllTestFiles($testManifest = null, $testsToIgnore = null) $this->createCestFile($testPhpFile[1], $testPhpFile[0]); } } + + /** + * Throw exception if duplicate arguments found + * @param TestObject $testObject + * @return void + * @throws TestFrameworkException + */ + public function throwExceptionIfDuplicateArgumentsFound($testObject): void + { + if (!($testObject instanceof TestObject)) { + return; + } + $fileName = $testObject->getFilename(); + if (!empty($fileName) && file_exists($fileName)) { + return; + } + $fileContents = file_get_contents($fileName); + $parsedSteps = $testObject->getUnresolvedSteps(); + foreach ($parsedSteps as $parsedStep) { + if ($parsedStep->getType() !== 'actionGroup' && $parsedStep->getType() !== 'helper') { + continue; + } + $attributesActions = $parsedStep->getCustomActionAttributes(); + if (!key_exists('arguments', $attributesActions)) { + continue; + } + $arguments = $attributesActions['arguments']; + $stepKey = $parsedStep->getStepKey(); + + $fileToArr = explode("\n", $fileContents); + $actionGroupStart = false; + $argumentArray = []; + foreach ($fileToArr as $fileVal) { + $fileVal = trim($fileVal); + if ((str_contains($fileVal, '<actionGroup') || str_contains($fileVal, '<helper')) && + str_contains($fileVal, $stepKey)) { + $actionGroupStart = true; + continue; + } + if (str_contains($fileVal, '</actionGroup') || str_contains($fileVal, '</helper')) { + foreach ($arguments as $argumentName => $argumentValue) { + $argumentCounter = 0; + foreach ($argumentArray as $rawArgument) { + if (str_contains($rawArgument, '<argument') && + str_contains($rawArgument, 'name="'.$argumentName.'"')) { + $argumentCounter++; + } + if ($argumentCounter > 1) { + $err[] = sprintf( + 'Duplicate argument(%s) for stepKey: %s in test file: %s', + $argumentName, + $stepKey, + $testObject->getFileName() + ); + throw new TestFrameworkException(implode(PHP_EOL, $err)); + } + } + $actionGroupStart = false; + $argumentArray = []; + } + } + if ($actionGroupStart) { + $argumentArray[] = $fileVal; + } + } + } + } /** * Assemble the entire PHP string for a single Test based on a Test Object. @@ -212,14 +324,18 @@ public function createAllTestFiles($testManifest = null, $testsToIgnore = null) * @throws TestReferenceException * @throws \Exception */ - private function assembleTestPhp($testObject) + public function assembleTestPhp($testObject) { + if (!empty($testObject->getFilename()) && file_exists($testObject->getFilename())) { + $fileContents = file_get_contents($testObject->getFilename()); + $this->throwExceptionIfDuplicateArgumentsFound($fileContents, $testObject->getFilename()); + } + $this->customHelpers = []; $usePhp = $this->generateUseStatementsPhp(); - $classAnnotationsPhp = $this->generateAnnotationsPhp($testObject->getAnnotations()); $className = $testObject->getCodeceptionName(); try { - if (!$testObject->isSkipped()) { + if (!$testObject->isSkipped() || MftfApplicationConfig::getConfig()->allowSkipped()) { $hookPhp = $this->generateHooksPhp($testObject->getHooks()); } else { $hookPhp = null; @@ -228,6 +344,7 @@ private 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"; @@ -235,6 +352,11 @@ private 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"; @@ -242,41 +364,107 @@ private 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()]); } } @@ -308,8 +496,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 = [ @@ -333,12 +519,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"; @@ -350,11 +537,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); } @@ -376,7 +563,7 @@ private function generateAnnotationsPhp($annotationsObject, $isMethod = false) * @param string|null $annotationName * @return null|string */ - private function generateMethodAnnotations($annotationType = null, $annotationName = null) + private function generateMethodAnnotations(?string $annotationType = null, mixed $annotationName = null) { $annotationToAppend = null; $indent = "\t"; @@ -411,11 +598,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"; @@ -424,26 +607,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": @@ -464,6 +670,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 @@ -481,10 +717,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; @@ -501,6 +740,7 @@ public function generateStepsPhp($actionObjects, $generationScope = TestGenerato $function = null; $time = null; $locale = null; + $currency = null; $username = null; $password = null; $width = null; @@ -512,6 +752,7 @@ public function generateStepsPhp($actionObjects, $generationScope = TestGenerato $dependentSelector = null; $visible = null; $command = null; + $cronGroups = ''; $arguments = null; $sortOrder = null; $storeCode = null; @@ -529,6 +770,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']); } @@ -541,7 +785,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'])) { @@ -549,9 +797,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']); } @@ -596,13 +841,19 @@ public function generateStepsPhp($actionObjects, $generationScope = TestGenerato $time = $customActionAttributes['timeout']; } - if (isset($customActionAttributes['parameterArray']) && $actionObject->getType() != 'pressKey') { + if (in_array($actionObject->getType(), ActionObject::COMMAND_ACTION_ATTRIBUTES)) { + $time = $time ?? ActionObject::getDefaultMagentoCLIWaitTimeout(); + } 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'])) { @@ -615,6 +866,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']; @@ -643,19 +899,23 @@ public function generateStepsPhp($actionObjects, $generationScope = TestGenerato $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); } } if (isset($customActionAttributes['html'])) { - $html = $customActionAttributes['html']; + $html = $this->addUniquenessFunctionCall($customActionAttributes['html']); } if (isset($customActionAttributes['locale'])) { $locale = $this->wrapWithDoubleQuotes($customActionAttributes['locale']); } + if (isset($customActionAttributes['currency'])) { + $currency = $this->wrapWithDoubleQuotes($customActionAttributes['currency']); + } + if (isset($customActionAttributes['username'])) { $username = $this->wrapWithDoubleQuotes($customActionAttributes['username']); } @@ -695,23 +955,62 @@ public function generateStepsPhp($actionObjects, $generationScope = TestGenerato if (isset($customActionAttributes['storeCode'])) { $storeCode = $customActionAttributes['storeCode']; } + 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->amGoingTo(\"create entity that has the stepKey: %s\");\n", + "\t\t$%s->comment('[%s] %s()');" . PHP_EOL, $actor, - $stepKey + $stepKey, + $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; @@ -722,28 +1021,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; - } - - $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}]"; + $scope = $this->getObjectScope($generationScope); + + $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\tnull"; + $createEntityFunctionCall .= " []"; } if ($storeCode !== null) { - $createEntityFunctionCall .= ",\n\t\t\t\"{$storeCode}\""; + $createEntityFunctionCall .= ", \"{$storeCode}\""; } - $createEntityFunctionCall .= "\n\t\t);\n"; + $createEntityFunctionCall .= ");"; $testSteps .= $createEntityFunctionCall; break; case "deleteData": @@ -755,33 +1048,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->amGoingTo(\"delete entity that has the createDataKey: %s\");\n", - $actor, - $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 ); @@ -798,17 +1078,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->amGoingTo(\"update entity that has the createdDataKey: %s\");\n", - $actor, - $key - ); - // 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; @@ -819,22 +1092,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; @@ -844,17 +1112,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->amGoingTo(\"get entity that has the stepKey: %s\");\n", - $actor, - $stepKey - ); // 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; } @@ -864,29 +1126,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; @@ -936,6 +1192,7 @@ public function generateStepsPhp($actionObjects, $generationScope = TestGenerato $parameterArray ); break; + case "grabCookieAttributes": case "grabCookie": $testSteps .= $this->wrapFunctionCallWithReturnValue( $stepKey, @@ -973,6 +1230,7 @@ public function generateStepsPhp($actionObjects, $generationScope = TestGenerato break; case "selectOption": case "unselectOption": + case "seeNumberOfElements": $testSteps .= $this->wrapFunctionCall( $actor, $actionObject, @@ -1000,6 +1258,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, @@ -1010,9 +1276,6 @@ public function generateStepsPhp($actionObjects, $generationScope = TestGenerato $parameterArray ); break; - case "executeInSelenium": - $testSteps .= $this->wrapFunctionCall($actor, $actionObject, $function); - break; case "executeJS": $testSteps .= $this->wrapFunctionCallWithReturnValue( $stepKey, @@ -1021,7 +1284,6 @@ public function generateStepsPhp($actionObjects, $generationScope = TestGenerato $function ); break; - case "performOn": case "waitForElementChange": $testSteps .= $this->wrapFunctionCall( $actor, @@ -1044,6 +1306,8 @@ public function generateStepsPhp($actionObjects, $generationScope = TestGenerato case "waitForElement": case "waitForElementVisible": case "waitForElementNotVisible": + case "waitForPwaElementVisible": + case "waitForPwaElementNotVisible": $testSteps .= $this->wrapFunctionCall($actor, $actionObject, $selector, $time); break; case "waitForPageLoad": @@ -1056,13 +1320,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": @@ -1089,10 +1364,12 @@ public function generateStepsPhp($actionObjects, $generationScope = TestGenerato ); break; case "grabPageSource": + case "getOTP": $testSteps .= $this->wrapFunctionCallWithReturnValue( $stepKey, $actor, - $actionObject + $actionObject, + $input ); break; case "resizeWindow": @@ -1131,27 +1408,22 @@ public function generateStepsPhp($actionObjects, $generationScope = TestGenerato case "dontSeeInField": case "dontSeeInCurrentUrl": case "dontSeeInTitle": - case "dontSeeInPageSource": case "dontSeeOptionIsSelected": case "fillField": case "loadSessionSnapshot": case "seeInField": case "seeOptionIsSelected": + case "seeInSecretField": $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": case "dontSeeInSource": - // TODO: Need to fix xml parser to allow parsing html. + //TODO: Deprecate allowed usage of userInput in dontSeeInPageSource + if ($html === null && $input !== null) { + $html = $input; + } $testSteps .= $this->wrapFunctionCall($actor, $actionObject, $html); break; case "conditionalClick": @@ -1163,15 +1435,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": @@ -1185,6 +1454,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, @@ -1195,6 +1468,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)) { @@ -1225,45 +1523,60 @@ public function generateStepsPhp($actionObjects, $generationScope = TestGenerato $assertMessage ); break; - case "assertArraySubset": + case "fail": $testSteps .= $this->wrapFunctionCall( $actor, $actionObject, - $assertExpected, - $assertActual, - $assertIsStrict, $assertMessage ); break; - case "fail": - $testSteps .= $this->wrapFunctionCall( + case "magentoCLI": + case "magentoCLISecret": + $testSteps .= $this->wrapFunctionCallWithReturnValue( + $stepKey, $actor, $actionObject, - $assertMessage + $command, + $time, + $arguments + ); + $testSteps .= sprintf(self::STEP_KEY_ANNOTATION, $stepKey) . PHP_EOL; + $testSteps .= sprintf( + "\t\t$%s->comment(\$%s);", + $actor, + $stepKey ); break; - case "magentoCLI": + case 'magentoCron': $testSteps .= $this->wrapFunctionCallWithReturnValue( $stepKey, $actor, $actionObject, - $command, + $cronGroups, + self::CRON_INTERVAL + $time, $arguments ); + $testSteps .= sprintf(self::STEP_KEY_ANNOTATION, $stepKey) . PHP_EOL; $testSteps .= sprintf( - "\t\t$%s->comment(\$%s);\n", + "\t\t$%s->comment(\$%s);", $actor, $stepKey ); break; case "field": $fieldKey = $actionObject->getCustomActionAttributes()['key']; + $input = $this->resolveStepKeyReferences($input, $actionObject->getActionOrigin()); $input = $this->resolveTestVariable( [$input], $actionObject->getActionOrigin() )[0]; $argRef = "\t\t\$"; - $argRef .= str_replace(ucfirst($fieldKey), "", $stepKey) . "Fields['{$fieldKey}'] = ${input};\n"; + $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": @@ -1279,9 +1592,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 default: $testSteps .= $this->wrapFunctionCall( $actor, @@ -1291,11 +1614,29 @@ public function generateStepsPhp($actionObjects, $generationScope = TestGenerato $parameter ); } + if (!in_array($actionObject->getType(), self::NO_STEPKEY_ACTIONS)) { + $testSteps .= sprintf(self::STEP_KEY_ANNOTATION, $stepKey); + } + $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. * @@ -1329,7 +1670,7 @@ private function resolveTestVariable($args, $actionOrigin) } $outputArg = $arg; // Math on $data.key$ and $$data.key$$ - preg_match_all('/\${1,2}[\w.\[\]]+\${1,2}/', $outputArg, $matches); + preg_match_all(self::PERSISTED_OBJECT_NOTATION_REGEX, $outputArg, $matches); $this->replaceMatchesIntoArg($matches[0], $outputArg); //trim "{$variable}" into $variable @@ -1354,9 +1695,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; } /** @@ -1375,14 +1716,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 @@ -1423,7 +1768,7 @@ private function processQuoteBreaks($match, $argument, $replacement) */ private function resolveStepKeyReferences($input, $actionGroupOrigin, $matchAll = false) { - if ($actionGroupOrigin == null) { + if ($actionGroupOrigin === null) { return $input; } $output = $input; @@ -1437,13 +1782,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 = str_replace($stepKeyVarRef, $stepKeyVarRef . $testInvocationKey, $output); + $output = preg_replace('/\B\\' . $stepKeyVarRef . '\b/', $stepKeyVarRef . $testInvocationKey, $output); } if (strpos($output, $persistedVarRef) !== false) { @@ -1478,8 +1830,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 { @@ -1520,6 +1872,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'; @@ -1538,9 +1894,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"; } @@ -1562,9 +1949,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()) { + 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)) { @@ -1572,14 +1965,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 (TestReferenceException $e) { - throw new TestReferenceException($e->getMessage() . " in Test \"" . $test->getName() . "\""); - } } $testPhp .= $testAnnotations; @@ -1588,6 +1976,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; } @@ -1648,8 +2044,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++; } @@ -1693,12 +2090,17 @@ private function processPressKey($input) /** * Add uniqueness function call to input string based on regex pattern. * - * @param string $input + * @param string $input + * @param boolean $wrapWithDoubleQuotes * @return string */ - private function addUniquenessFunctionCall($input) + private function addUniquenessFunctionCall($input, $wrapWithDoubleQuotes = true) { - $output = $this->wrapWithDoubleQuotes($input); + if ($wrapWithDoubleQuotes) { + $output = $this->wrapWithDoubleQuotes($input); + } else { + $output = $input; + } //Match on msq(\"entityName\") preg_match_all('/' . EntityDataObject::CEST_UNIQUE_FUNCTION . '\(\\\\"[\w]+\\\\"\)/', $output, $matches); @@ -1719,7 +2121,7 @@ private function addUniquenessFunctionCall($input) */ private function wrapWithDoubleQuotes($input) { - if ($input == null) { + if ($input === null || $input === '') { return ''; } //Only replace " with \" so that it doesn't break outer string. @@ -1738,13 +2140,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, '"'); } /** @@ -1758,28 +2155,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)) { @@ -1787,31 +2198,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; })) . ");\n"; + $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)) { @@ -1819,13 +2234,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; })) . ");\n"; + $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 @@ -1836,18 +2263,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. @@ -1865,7 +2294,7 @@ private function resolveAllRuntimeReferences($args) { $runtimeReferenceRegex = [ "/{{_ENV\.([\w]+)}}/" => 'getenv', - "/{{_CREDS\.([\w]+)}}/" => 'CredentialStore::getInstance()->getSecret' + ActionMergeUtil::CREDS_REGEX => "\${$this->actor}->getSecret" ]; $argResult = $args; @@ -1885,43 +2314,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(?string $value = null, ?string $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; } /** @@ -1944,11 +2409,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); } /** @@ -2000,6 +2465,7 @@ private function validateXmlAttributesMutuallyExclusive($key, $tagName, $attribu 'excludes' => [ 'dontSeeCookie', 'grabCookie', + 'grabCookieAttributes', 'resetCookie', 'seeCookie', 'setCookie', @@ -2035,9 +2501,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 964e68137..601d580c7 100644 --- a/src/Magento/FunctionalTestingFramework/Util/Validation/DuplicateNodeValidationUtil.php +++ b/src/Magento/FunctionalTestingFramework/Util/Validation/DuplicateNodeValidationUtil.php @@ -1,7 +1,7 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2018 Adobe + * All Rights Reserved. */ namespace Magento\FunctionalTestingFramework\Util\Validation; @@ -65,11 +65,15 @@ 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) { - $keyError .= "\t{$this->uniqueKey}: {$duplicateValue} is used more than once. (Parent: {$parentKey})\n"; + foreach ($duplicates as $duplicateValue) { + $keyError .= "\t{$this->uniqueKey}: {$duplicateValue} is used more than once."; + if ($parentKey !== null) { + $keyError .=" (Parent: {$parentKey})"; + } + $keyError .= "\n"; } $errorMsg = "{$type} cannot use {$this->uniqueKey}s more than once.\t\n{$keyError}\tin file: {$filename}"; diff --git a/src/Magento/FunctionalTestingFramework/Util/Validation/NameValidationUtil.php b/src/Magento/FunctionalTestingFramework/Util/Validation/NameValidationUtil.php index 60b4660fb..9c94aea6b 100644 --- a/src/Magento/FunctionalTestingFramework/Util/Validation/NameValidationUtil.php +++ b/src/Magento/FunctionalTestingFramework/Util/Validation/NameValidationUtil.php @@ -1,19 +1,46 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2018 Adobe + * All Rights Reserved. */ 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 @@ -41,7 +68,7 @@ public static function validateName($name, $type) } if (!empty($illegalCharArray)) { - $errorMessage = "{$type} name \"${name}\" contains illegal characters, please fix and re-run."; + $errorMessage = "{$type} name \"{$name}\" contains illegal characters, please fix and re-run."; foreach ($illegalCharArray as $diffChar) { $errorMessage .= "\nTest names cannot contain '{$diffChar}'"; @@ -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..f82c433a4 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Util/Validation/SingleNodePerFileValidationUtil.php @@ -0,0 +1,53 @@ +<?php +/** + * Copyright 2020 Adobe + * All Rights Reserved. + */ + +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/Util/msq.php b/src/Magento/FunctionalTestingFramework/Util/msq.php index 3968e3c82..d2a83df6a 100644 --- a/src/Magento/FunctionalTestingFramework/Util/msq.php +++ b/src/Magento/FunctionalTestingFramework/Util/msq.php @@ -1,9 +1,9 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ - + use Magento\FunctionalTestingFramework\Module\MagentoSequence; if (!function_exists('msq')) { @@ -13,7 +13,7 @@ * @param null $id * @return string */ - function msq($id = null) + function msq(?string $id = null) { if ($id and isset(MagentoSequence::$hash[$id])) { return MagentoSequence::$hash[$id]; @@ -34,7 +34,7 @@ function msq($id = null) * @param null $id * @return string */ - function msqs($id = null) + function msqs(?string $id = null) { if ($id and isset(MagentoSequence::$suiteHash[$id])) { return MagentoSequence::$suiteHash[$id]; diff --git a/src/Magento/FunctionalTestingFramework/XmlParser/PageParser.php b/src/Magento/FunctionalTestingFramework/XmlParser/PageParser.php index 643370504..4b4ed822e 100644 --- a/src/Magento/FunctionalTestingFramework/XmlParser/PageParser.php +++ b/src/Magento/FunctionalTestingFramework/XmlParser/PageParser.php @@ -1,8 +1,9 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ + namespace Magento\FunctionalTestingFramework\XmlParser; use Magento\FunctionalTestingFramework\Config\DataInterface; diff --git a/src/Magento/FunctionalTestingFramework/XmlParser/ParserInterface.php b/src/Magento/FunctionalTestingFramework/XmlParser/ParserInterface.php index b48f3531c..102e8d373 100644 --- a/src/Magento/FunctionalTestingFramework/XmlParser/ParserInterface.php +++ b/src/Magento/FunctionalTestingFramework/XmlParser/ParserInterface.php @@ -1,9 +1,9 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ - + namespace Magento\FunctionalTestingFramework\XmlParser; /** diff --git a/src/Magento/FunctionalTestingFramework/XmlParser/SectionParser.php b/src/Magento/FunctionalTestingFramework/XmlParser/SectionParser.php index ff8f92c42..796516f17 100644 --- a/src/Magento/FunctionalTestingFramework/XmlParser/SectionParser.php +++ b/src/Magento/FunctionalTestingFramework/XmlParser/SectionParser.php @@ -1,9 +1,9 @@ <?php /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2017 Adobe + * All Rights Reserved. */ - + namespace Magento\FunctionalTestingFramework\XmlParser; use Magento\FunctionalTestingFramework\Config\DataInterface; diff --git a/src/Magento/FunctionalTestingFramework/_bootstrap.php b/src/Magento/FunctionalTestingFramework/_bootstrap.php index 34807d2b9..10761fac0 100644 --- a/src/Magento/FunctionalTestingFramework/_bootstrap.php +++ b/src/Magento/FunctionalTestingFramework/_bootstrap.php @@ -1,11 +1,14 @@ <?php // @codingStandardsIgnoreFile /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. + * Copyright 2018 Adobe + * All Rights Reserved. */ + // 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 +18,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 +48,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 +74,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(); -}