diff --git a/.gitignore b/.gitignore index 8e2330435..a405c41cb 100755 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,5 @@ vendor/* .env _generated AcceptanceTester.php +cghooks.lock +src/Magento/FunctionalTestingFramework/Group/*.php diff --git a/COPYING.txt b/COPYING.txt new file mode 100644 index 000000000..d2cbcd015 --- /dev/null +++ b/COPYING.txt @@ -0,0 +1,9 @@ +Copyright © 2013-2017 Magento, Inc. + +Each Magento source file included in this distribution is licensed under OSL 3.0 or the Magento Enterprise Edition (MEE) license + +http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) +Please see LICENSE.txt for the full text of the OSL 3.0 license or contact license@magentocommerce.com for a copy. + +Subject to Licensee's payment of fees and compliance with the terms and conditions of the MEE License, the MEE License supersedes the OSL 3.0 license for each source file. +Please see LICENSE_EE.txt for the full text of the MEE License or visit http://magento.com/legal/terms/enterprise. diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 000000000..36b2459f6 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,48 @@ + +Open Software License ("OSL") v. 3.0 + +This Open Software License (the "License") applies to any original work of authorship (the "Original Work") whose owner (the "Licensor") has placed the following licensing notice adjacent to the copyright notice for the Original Work: + +Licensed under the Open Software License version 3.0 + + 1. Grant of Copyright License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, for the duration of the copyright, to do the following: + + 1. to reproduce the Original Work in copies, either alone or as part of a collective work; + + 2. to translate, adapt, alter, transform, modify, or arrange the Original Work, thereby creating derivative works ("Derivative Works") based upon the Original Work; + + 3. to distribute or communicate copies of the Original Work and Derivative Works to the public, with the proviso that copies of Original Work or Derivative Works that You distribute or communicate shall be licensed under this Open Software License; + + 4. to perform the Original Work publicly; and + + 5. to display the Original Work publicly. + + 2. Grant of Patent License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, under patent claims owned or controlled by the Licensor that are embodied in the Original Work as furnished by the Licensor, for the duration of the patents, to make, use, sell, offer for sale, have made, and import the Original Work and Derivative Works. + + 3. Grant of Source Code License. The term "Source Code" means the preferred form of the Original Work for making modifications to it and all available documentation describing how to modify the Original Work. Licensor agrees to provide a machine-readable copy of the Source Code of the Original Work along with each copy of the Original Work that Licensor distributes. Licensor reserves the right to satisfy this obligation by placing a machine-readable copy of the Source Code in an information repository reasonably calculated to permit inexpensive and convenient access by You for as long as Licensor continues to distribute the Original Work. + + 4. Exclusions From License Grant. Neither the names of Licensor, nor the names of any contributors to the Original Work, nor any of their trademarks or service marks, may be used to endorse or promote products derived from this Original Work without express prior permission of the Licensor. Except as expressly stated herein, nothing in this License grants any license to Licensor's trademarks, copyrights, patents, trade secrets or any other intellectual property. No patent license is granted to make, use, sell, offer for sale, have made, or import embodiments of any patent claims other than the licensed claims defined in Section 2. No license is granted to the trademarks of Licensor even if such marks are included in the Original Work. Nothing in this License shall be interpreted to prohibit Licensor from licensing under terms different from this License any Original Work that Licensor otherwise would have a right to license. + + 5. External Deployment. The term "External Deployment" means the use, distribution, or communication of the Original Work or Derivative Works in any way such that the Original Work or Derivative Works may be used by anyone other than You, whether those works are distributed or communicated to those persons or made available as an application intended for use over a network. As an express condition for the grants of license hereunder, You must treat any External Deployment by You of the Original Work or a Derivative Work as a distribution under section 1(c). + + 6. Attribution Rights. You must retain, in the Source Code of any Derivative Works that You create, all copyright, patent, or trademark notices from the Source Code of the Original Work, as well as any notices of licensing and any descriptive text identified therein as an "Attribution Notice." You must cause the Source Code for any Derivative Works that You create to carry a prominent Attribution Notice reasonably calculated to inform recipients that You have modified the Original Work. + + 7. Warranty of Provenance and Disclaimer of Warranty. Licensor warrants that the copyright in and to the Original Work and the patent rights granted herein by Licensor are owned by the Licensor or are sublicensed to You under the terms of this License with the permission of the contributor(s) of those copyrights and patent rights. Except as expressly stated in the immediately preceding sentence, the Original Work is provided under this License on an "AS IS" BASIS and WITHOUT WARRANTY, either express or implied, including, without limitation, the warranties of non-infringement, merchantability or fitness for a particular purpose. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL WORK IS WITH YOU. This DISCLAIMER OF WARRANTY constitutes an essential part of this License. No license to the Original Work is granted by this License except under this disclaimer. + + 8. Limitation of Liability. Under no circumstances and under no legal theory, whether in tort (including negligence), contract, or otherwise, shall the Licensor be liable to anyone for any indirect, special, incidental, or consequential damages of any character arising as a result of this License or the use of the Original Work including, without limitation, damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses. This limitation of liability shall not apply to the extent applicable law prohibits such limitation. + + 9. Acceptance and Termination. If, at any time, You expressly assented to this License, that assent indicates your clear and irrevocable acceptance of this License and all of its terms and conditions. If You distribute or communicate copies of the Original Work or a Derivative Work, You must make a reasonable effort under the circumstances to obtain the express assent of recipients to the terms of this License. This License conditions your rights to undertake the activities listed in Section 1, including your right to create Derivative Works based upon the Original Work, and doing so without honoring these terms and conditions is prohibited by copyright law and international treaty. Nothing in this License is intended to affect copyright exceptions and limitations (including 'fair use' or 'fair dealing'). This License shall terminate immediately and You may no longer exercise any of the rights granted to You by this License upon your failure to honor the conditions in Section 1(c). + + 10. Termination for Patent Action. This License shall terminate automatically and You may no longer exercise any of the rights granted to You by this License as of the date You commence an action, including a cross-claim or counterclaim, against Licensor or any licensee alleging that the Original Work infringes a patent. This termination provision shall not apply for an action alleging patent infringement by combinations of the Original Work with other software or hardware. + + 11. Jurisdiction, Venue and Governing Law. Any action or suit relating to this License may be brought only in the courts of a jurisdiction wherein the Licensor resides or in which Licensor conducts its primary business, and under the laws of that jurisdiction excluding its conflict-of-law provisions. The application of the United Nations Convention on Contracts for the International Sale of Goods is expressly excluded. Any use of the Original Work outside the scope of this License or after its termination shall be subject to the requirements and penalties of copyright or patent law in the appropriate jurisdiction. This section shall survive the termination of this License. + + 12. Attorneys' Fees. In any action to enforce the terms of this License or seeking damages relating thereto, the prevailing party shall be entitled to recover its costs and expenses, including, without limitation, reasonable attorneys' fees and costs incurred in connection with such action, including any appeal of such action. This section shall survive the termination of this License. + + 13. Miscellaneous. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. + + 14. Definition of "You" in This License. "You" throughout this License, whether in upper or lower case, means an individual or a legal entity exercising rights under, and complying with all of the terms of, this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with you. For purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + + 15. Right to Use. You may use the Original Work in all ways not otherwise restricted or conditioned by this License or by law, and Licensor promises not to interfere with or be responsible for such uses by You. + + 16. Modification of This License. This License is Copyright (C) 2005 Lawrence Rosen. Permission is granted to copy, distribute, or communicate this License without modification. Nothing in this License permits You to modify this License as applied to the Original Work or to Derivative Works. However, You may modify the text of this License and copy, distribute or communicate your modified version (the "Modified License") and apply it to other original works of authorship subject to the following conditions: (i) You may not indicate in any way that your Modified License is the "Open Software License" or "OSL" and you may not use those names in the name of your Modified License; (ii) You must replace the notice specified in the first paragraph above with the notice "Licensed under " or with a notice of your own that is not confusingly similar to the notice in this License; and (iii) You may not claim that your original works are open source software unless your Modified License has been approved by Open Source Initiative (OSI) and You comply with its license review and certification process. diff --git a/bin/blacklist.txt b/bin/blacklist.txt new file mode 100644 index 000000000..c85d25b5c --- /dev/null +++ b/bin/blacklist.txt @@ -0,0 +1,9 @@ +################################################################### +# Specify directories or files for the copyright-check to ignore. # +# # +# THIS FILE CANNOT CONTAIN BLANK LINES # +################################################################### +bin/blacklist.txt +dev/tests/static/Magento/Sniffs/Annotations/Helper.php +dev/tests/static/Magento/Sniffs/Annotations/RequireAnnotatedAttributesSniff.php +dev/tests/static/Magento/Sniffs/Annotations/RequireAnnotatedMethodsSniff.php \ No newline at end of file diff --git a/bin/copyright-check b/bin/copyright-check new file mode 100755 index 000000000..79fe8df06 --- /dev/null +++ b/bin/copyright-check @@ -0,0 +1,31 @@ +#!/bin/bash + +# Copyright © Magento, Inc. All rights reserved. +# See COPYING.txt for license details. + + +FILE_EXTENSIONS='.php\|.xml\|.xsd' +BLACKLIST='bin/blacklist.txt' +RESULT='' + +# Iterate through the list of tracked files +# that have the expected extensions +# that are not ignored +for i in `git ls-tree --full-tree -r --name-only HEAD | grep $FILE_EXTENSIONS | grep -v -f $BLACKLIST` +do + if echo `cat $i` | grep -q -v "Copyright © Magento, Inc. All rights reserved."; then + # Copyright is missing + RESULT+="$i\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 "$RESULT\n" + exit 1 +fi + +# Success! +exit 0 diff --git a/composer.json b/composer.json index 2bd7d5aea..427d08cbe 100755 --- a/composer.json +++ b/composer.json @@ -7,15 +7,22 @@ "php": "~7.0", "codeception/codeception": "2.2|2.3", "flow/jsonpath": ">0.2", - "fzaninotto/faker": "^1.6" + "fzaninotto/faker": "^1.6", + "mustache/mustache": "~2.5" }, "require-dev": { "squizlabs/php_codesniffer": "1.5.3", - "sebastian/phpcpd": "~3.0" + "sebastian/phpcpd": "~3.0", + "brainmaestro/composer-git-hooks": "^2.3" }, "autoload": { "psr-4": { "Magento\\FunctionalTestingFramework\\": ["src/Magento/FunctionalTestingFramework"] } + }, + "extra": { + "hooks": { + "pre-push": "bin/copyright-check" + } } } diff --git a/composer.lock b/composer.lock index 88f87805a..60d4dc14f 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "hash": "4ccd9ed4f8c2fde43e5784bdc02b4064", - "content-hash": "d2c73e722ab7776b4cf486b17e2abfa7", + "content-hash": "6a845d91aa6c99cce577f96862648392", "packages": [ { "name": "behat/gherkin", @@ -64,7 +63,7 @@ "gherkin", "parser" ], - "time": "2016-10-30 11:50:56" + "time": "2016-10-30T11:50:56+00:00" }, { "name": "codeception/codeception", @@ -158,36 +157,36 @@ "functional testing", "unit testing" ], - "time": "2017-05-22 23:47:35" + "time": "2017-05-22T23:47:35+00:00" }, { "name": "doctrine/instantiator", - "version": "1.0.5", + "version": "1.1.0", "source": { "type": "git", "url": "https://github.com/doctrine/instantiator.git", - "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d" + "reference": "185b8868aa9bf7159f5f953ed5afb2d7fcdc3bda" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/8e884e78f9f0eb1329e445619e04456e64d8051d", - "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/185b8868aa9bf7159f5f953ed5afb2d7fcdc3bda", + "reference": "185b8868aa9bf7159f5f953ed5afb2d7fcdc3bda", "shasum": "" }, "require": { - "php": ">=5.3,<8.0-DEV" + "php": "^7.1" }, "require-dev": { "athletic/athletic": "~0.1.8", "ext-pdo": "*", "ext-phar": "*", - "phpunit/phpunit": "~4.0", - "squizlabs/php_codesniffer": "~2.0" + "phpunit/phpunit": "^6.2.3", + "squizlabs/php_codesniffer": "^3.0.2" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "1.2.x-dev" } }, "autoload": { @@ -212,7 +211,7 @@ "constructor", "instantiate" ], - "time": "2015-06-14 21:17:01" + "time": "2017-07-22T11:58:36+00:00" }, { "name": "facebook/webdriver", @@ -264,7 +263,7 @@ "selenium", "webdriver" ], - "time": "2017-04-28 14:54:49" + "time": "2017-04-28T14:54:49+00:00" }, { "name": "flow/jsonpath", @@ -305,7 +304,7 @@ } ], "description": "JSONPath implementation for parsing, searching and flattening arrays", - "time": "2016-09-06 17:43:18" + "time": "2016-09-06T17:43:18+00:00" }, { "name": "fzaninotto/faker", @@ -355,7 +354,7 @@ "faker", "fixtures" ], - "time": "2017-08-15 16:48:10" + "time": "2017-08-15T16:48:10+00:00" }, { "name": "guzzlehttp/guzzle", @@ -420,7 +419,7 @@ "rest", "web service" ], - "time": "2017-06-22 18:50:49" + "time": "2017-06-22T18:50:49+00:00" }, { "name": "guzzlehttp/promises", @@ -471,7 +470,7 @@ "keywords": [ "promise" ], - "time": "2016-12-20 10:07:11" + "time": "2016-12-20T10:07:11+00:00" }, { "name": "guzzlehttp/psr7", @@ -536,7 +535,53 @@ "uri", "url" ], - "time": "2017-03-20 17:10:46" + "time": "2017-03-20T17:10:46+00:00" + }, + { + "name": "mustache/mustache", + "version": "v2.12.0", + "source": { + "type": "git", + "url": "https://github.com/bobthecow/mustache.php.git", + "reference": "fe8fe72e9d580591854de404cc59a1b83ca4d19e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/bobthecow/mustache.php/zipball/fe8fe72e9d580591854de404cc59a1b83ca4d19e", + "reference": "fe8fe72e9d580591854de404cc59a1b83ca4d19e", + "shasum": "" + }, + "require": { + "php": ">=5.2.4" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "~1.11", + "phpunit/phpunit": "~3.7|~4.0|~5.0" + }, + "type": "library", + "autoload": { + "psr-0": { + "Mustache": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Justin Hileman", + "email": "justin@justinhileman.info", + "homepage": "http://justinhileman.com" + } + ], + "description": "A Mustache implementation in PHP.", + "homepage": "https://github.com/bobthecow/mustache.php", + "keywords": [ + "mustache", + "templating" + ], + "time": "2017-07-11T12:54:05+00:00" }, { "name": "myclabs/deep-copy", @@ -578,20 +623,20 @@ "object", "object graph" ], - "time": "2017-04-12 18:52:22" + "time": "2017-04-12T18:52:22+00:00" }, { "name": "phpdocumentor/reflection-common", - "version": "1.0", + "version": "1.0.1", "source": { "type": "git", "url": "https://github.com/phpDocumentor/ReflectionCommon.git", - "reference": "144c307535e82c8fdcaacbcfc1d6d8eeb896687c" + "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/144c307535e82c8fdcaacbcfc1d6d8eeb896687c", - "reference": "144c307535e82c8fdcaacbcfc1d6d8eeb896687c", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6", + "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6", "shasum": "" }, "require": { @@ -632,7 +677,7 @@ "reflection", "static analysis" ], - "time": "2015-12-27 11:43:31" + "time": "2017-09-11T18:02:19+00:00" }, { "name": "phpdocumentor/reflection-docblock", @@ -677,7 +722,7 @@ } ], "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-08-30 18:51:59" + "time": "2017-08-30T18:51:59+00:00" }, { "name": "phpdocumentor/type-resolver", @@ -724,7 +769,7 @@ "email": "me@mikevanriel.com" } ], - "time": "2017-07-14 14:27:02" + "time": "2017-07-14T14:27:02+00:00" }, { "name": "phpspec/prophecy", @@ -787,7 +832,7 @@ "spy", "stub" ], - "time": "2017-09-04 11:05:03" + "time": "2017-09-04T11:05:03+00:00" }, { "name": "phpunit/php-code-coverage", @@ -850,7 +895,7 @@ "testing", "xunit" ], - "time": "2017-04-02 07:44:40" + "time": "2017-04-02T07:44:40+00:00" }, { "name": "phpunit/php-file-iterator", @@ -897,7 +942,7 @@ "filesystem", "iterator" ], - "time": "2016-10-03 07:40:28" + "time": "2016-10-03T07:40:28+00:00" }, { "name": "phpunit/php-text-template", @@ -938,7 +983,7 @@ "keywords": [ "template" ], - "time": "2015-06-21 13:50:34" + "time": "2015-06-21T13:50:34+00:00" }, { "name": "phpunit/php-timer", @@ -987,7 +1032,7 @@ "keywords": [ "timer" ], - "time": "2017-02-26 11:10:40" + "time": "2017-02-26T11:10:40+00:00" }, { "name": "phpunit/php-token-stream", @@ -1036,20 +1081,20 @@ "keywords": [ "tokenizer" ], - "time": "2017-08-20 05:47:52" + "time": "2017-08-20T05:47:52+00:00" }, { "name": "phpunit/phpunit", - "version": "5.7.21", + "version": "5.7.23", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "3b91adfb64264ddec5a2dee9851f354aa66327db" + "reference": "78532d5269d984660080d8e0f4c99c5c2ea65ffe" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/3b91adfb64264ddec5a2dee9851f354aa66327db", - "reference": "3b91adfb64264ddec5a2dee9851f354aa66327db", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/78532d5269d984660080d8e0f4c99c5c2ea65ffe", + "reference": "78532d5269d984660080d8e0f4c99c5c2ea65ffe", "shasum": "" }, "require": { @@ -1118,7 +1163,7 @@ "testing", "xunit" ], - "time": "2017-06-21 08:11:54" + "time": "2017-10-15T06:13:55+00:00" }, { "name": "phpunit/phpunit-mock-objects", @@ -1177,7 +1222,7 @@ "mock", "xunit" ], - "time": "2017-06-30 09:13:00" + "time": "2017-06-30T09:13:00+00:00" }, { "name": "psr/http-message", @@ -1227,7 +1272,7 @@ "request", "response" ], - "time": "2016-08-06 14:39:51" + "time": "2016-08-06T14:39:51+00:00" }, { "name": "psr/log", @@ -1274,7 +1319,7 @@ "psr", "psr-3" ], - "time": "2016-10-10 12:19:37" + "time": "2016-10-10T12:19:37+00:00" }, { "name": "sebastian/code-unit-reverse-lookup", @@ -1319,7 +1364,7 @@ ], "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-04 06:30:41" + "time": "2017-03-04T06:30:41+00:00" }, { "name": "sebastian/comparator", @@ -1383,7 +1428,7 @@ "compare", "equality" ], - "time": "2017-01-29 09:50:25" + "time": "2017-01-29T09:50:25+00:00" }, { "name": "sebastian/diff", @@ -1435,7 +1480,7 @@ "keywords": [ "diff" ], - "time": "2017-05-22 07:24:03" + "time": "2017-05-22T07:24:03+00:00" }, { "name": "sebastian/environment", @@ -1485,7 +1530,7 @@ "environment", "hhvm" ], - "time": "2016-11-26 07:53:53" + "time": "2016-11-26T07:53:53+00:00" }, { "name": "sebastian/exporter", @@ -1552,7 +1597,7 @@ "export", "exporter" ], - "time": "2016-11-19 08:54:04" + "time": "2016-11-19T08:54:04+00:00" }, { "name": "sebastian/global-state", @@ -1603,7 +1648,7 @@ "keywords": [ "global state" ], - "time": "2015-10-12 03:26:01" + "time": "2015-10-12T03:26:01+00:00" }, { "name": "sebastian/object-enumerator", @@ -1649,7 +1694,7 @@ ], "description": "Traverses array structures and object graphs to enumerate all referenced objects", "homepage": "https://github.com/sebastianbergmann/object-enumerator/", - "time": "2017-02-18 15:18:39" + "time": "2017-02-18T15:18:39+00:00" }, { "name": "sebastian/recursion-context", @@ -1702,7 +1747,7 @@ ], "description": "Provides functionality to recursively process PHP variables", "homepage": "http://www.github.com/sebastianbergmann/recursion-context", - "time": "2016-11-19 07:33:16" + "time": "2016-11-19T07:33:16+00:00" }, { "name": "sebastian/resource-operations", @@ -1744,7 +1789,7 @@ ], "description": "Provides a list of PHP built-in functions that operate on resources", "homepage": "https://www.github.com/sebastianbergmann/resource-operations", - "time": "2015-07-28 20:34:47" + "time": "2015-07-28T20:34:47+00:00" }, { "name": "sebastian/version", @@ -1787,7 +1832,7 @@ ], "description": "Library that helps with managing the version number of Git-hosted PHP projects", "homepage": "https://github.com/sebastianbergmann/version", - "time": "2016-10-03 07:35:21" + "time": "2016-10-03T07:35:21+00:00" }, { "name": "stecman/symfony-console-completion", @@ -1832,20 +1877,20 @@ } ], "description": "Automatic BASH completion for Symfony Console Component based applications.", - "time": "2016-02-24 05:08:54" + "time": "2016-02-24T05:08:54+00:00" }, { "name": "symfony/browser-kit", - "version": "v3.3.8", + "version": "v3.3.10", "source": { "type": "git", "url": "https://github.com/symfony/browser-kit.git", - "reference": "aee7120b058c268363e606ff5fe8271da849a1b5" + "reference": "317d5bdf0127f06db7ea294186132b4f5b036839" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/browser-kit/zipball/aee7120b058c268363e606ff5fe8271da849a1b5", - "reference": "aee7120b058c268363e606ff5fe8271da849a1b5", + "url": "https://api.github.com/repos/symfony/browser-kit/zipball/317d5bdf0127f06db7ea294186132b4f5b036839", + "reference": "317d5bdf0127f06db7ea294186132b4f5b036839", "shasum": "" }, "require": { @@ -1889,20 +1934,20 @@ ], "description": "Symfony BrowserKit Component", "homepage": "https://symfony.com", - "time": "2017-07-29 21:54:42" + "time": "2017-10-02T06:42:24+00:00" }, { "name": "symfony/console", - "version": "v3.3.8", + "version": "v3.3.10", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "d6596cb5022b6a0bd940eae54a1de78646a5fda6" + "reference": "116bc56e45a8e5572e51eb43ab58c769a352366c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/d6596cb5022b6a0bd940eae54a1de78646a5fda6", - "reference": "d6596cb5022b6a0bd940eae54a1de78646a5fda6", + "url": "https://api.github.com/repos/symfony/console/zipball/116bc56e45a8e5572e51eb43ab58c769a352366c", + "reference": "116bc56e45a8e5572e51eb43ab58c769a352366c", "shasum": "" }, "require": { @@ -1957,20 +2002,20 @@ ], "description": "Symfony Console Component", "homepage": "https://symfony.com", - "time": "2017-08-27 14:52:21" + "time": "2017-10-02T06:42:24+00:00" }, { "name": "symfony/css-selector", - "version": "v3.3.8", + "version": "v3.3.10", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", - "reference": "c5f5263ed231f164c58368efbce959137c7d9488" + "reference": "07447650225ca9223bd5c97180fe7c8267f7d332" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/c5f5263ed231f164c58368efbce959137c7d9488", - "reference": "c5f5263ed231f164c58368efbce959137c7d9488", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/07447650225ca9223bd5c97180fe7c8267f7d332", + "reference": "07447650225ca9223bd5c97180fe7c8267f7d332", "shasum": "" }, "require": { @@ -2010,20 +2055,20 @@ ], "description": "Symfony CssSelector Component", "homepage": "https://symfony.com", - "time": "2017-07-29 21:54:42" + "time": "2017-10-02T06:42:24+00:00" }, { "name": "symfony/debug", - "version": "v3.3.8", + "version": "v3.3.10", "source": { "type": "git", "url": "https://github.com/symfony/debug.git", - "reference": "084d804fe35808eb2ef596ec83d85d9768aa6c9d" + "reference": "eb95d9ce8f18dcc1b3dfff00cb624c402be78ffd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/debug/zipball/084d804fe35808eb2ef596ec83d85d9768aa6c9d", - "reference": "084d804fe35808eb2ef596ec83d85d9768aa6c9d", + "url": "https://api.github.com/repos/symfony/debug/zipball/eb95d9ce8f18dcc1b3dfff00cb624c402be78ffd", + "reference": "eb95d9ce8f18dcc1b3dfff00cb624c402be78ffd", "shasum": "" }, "require": { @@ -2066,20 +2111,20 @@ ], "description": "Symfony Debug Component", "homepage": "https://symfony.com", - "time": "2017-08-27 14:52:21" + "time": "2017-10-02T06:42:24+00:00" }, { "name": "symfony/dom-crawler", - "version": "v3.3.8", + "version": "v3.3.10", "source": { "type": "git", "url": "https://github.com/symfony/dom-crawler.git", - "reference": "d15dfaf71b65bf3affb80900470caf4451a8217e" + "reference": "40dafd42d5dad7fe5ad4e958413d92a207522ac1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/d15dfaf71b65bf3affb80900470caf4451a8217e", - "reference": "d15dfaf71b65bf3affb80900470caf4451a8217e", + "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/40dafd42d5dad7fe5ad4e958413d92a207522ac1", + "reference": "40dafd42d5dad7fe5ad4e958413d92a207522ac1", "shasum": "" }, "require": { @@ -2122,20 +2167,20 @@ ], "description": "Symfony DomCrawler Component", "homepage": "https://symfony.com", - "time": "2017-08-15 13:31:09" + "time": "2017-10-02T06:42:24+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v3.3.8", + "version": "v3.3.10", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "54ca9520a00386f83bca145819ad3b619aaa2485" + "reference": "d7ba037e4b8221956ab1e221c73c9e27e05dd423" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/54ca9520a00386f83bca145819ad3b619aaa2485", - "reference": "54ca9520a00386f83bca145819ad3b619aaa2485", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/d7ba037e4b8221956ab1e221c73c9e27e05dd423", + "reference": "d7ba037e4b8221956ab1e221c73c9e27e05dd423", "shasum": "" }, "require": { @@ -2185,20 +2230,20 @@ ], "description": "Symfony EventDispatcher Component", "homepage": "https://symfony.com", - "time": "2017-07-29 21:54:42" + "time": "2017-10-02T06:42:24+00:00" }, { "name": "symfony/finder", - "version": "v3.3.8", + "version": "v3.3.10", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "b2260dbc80f3c4198f903215f91a1ac7fe9fe09e" + "reference": "773e19a491d97926f236942484cb541560ce862d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/b2260dbc80f3c4198f903215f91a1ac7fe9fe09e", - "reference": "b2260dbc80f3c4198f903215f91a1ac7fe9fe09e", + "url": "https://api.github.com/repos/symfony/finder/zipball/773e19a491d97926f236942484cb541560ce862d", + "reference": "773e19a491d97926f236942484cb541560ce862d", "shasum": "" }, "require": { @@ -2234,20 +2279,20 @@ ], "description": "Symfony Finder Component", "homepage": "https://symfony.com", - "time": "2017-07-29 21:54:42" + "time": "2017-10-02T06:42:24+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.5.0", + "version": "v1.6.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "7c8fae0ac1d216eb54349e6a8baa57d515fe8803" + "reference": "2ec8b39c38cb16674bbf3fea2b6ce5bf117e1296" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/7c8fae0ac1d216eb54349e6a8baa57d515fe8803", - "reference": "7c8fae0ac1d216eb54349e6a8baa57d515fe8803", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/2ec8b39c38cb16674bbf3fea2b6ce5bf117e1296", + "reference": "2ec8b39c38cb16674bbf3fea2b6ce5bf117e1296", "shasum": "" }, "require": { @@ -2259,7 +2304,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.5-dev" + "dev-master": "1.6-dev" } }, "autoload": { @@ -2293,20 +2338,20 @@ "portable", "shim" ], - "time": "2017-06-14 15:44:48" + "time": "2017-10-11T12:05:26+00:00" }, { "name": "symfony/process", - "version": "v3.3.8", + "version": "v3.3.10", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "b7666e9b438027a1ea0e1ee813ec5042d5d7f6f0" + "reference": "fdf89e57a723a29baf536e288d6e232c059697b1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/b7666e9b438027a1ea0e1ee813ec5042d5d7f6f0", - "reference": "b7666e9b438027a1ea0e1ee813ec5042d5d7f6f0", + "url": "https://api.github.com/repos/symfony/process/zipball/fdf89e57a723a29baf536e288d6e232c059697b1", + "reference": "fdf89e57a723a29baf536e288d6e232c059697b1", "shasum": "" }, "require": { @@ -2342,20 +2387,20 @@ ], "description": "Symfony Process Component", "homepage": "https://symfony.com", - "time": "2017-07-29 21:54:42" + "time": "2017-10-02T06:42:24+00:00" }, { "name": "symfony/yaml", - "version": "v3.3.8", + "version": "v3.3.10", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "1d8c2a99c80862bdc3af94c1781bf70f86bccac0" + "reference": "8c7bf1e7d5d6b05a690b715729cb4cd0c0a99c46" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/1d8c2a99c80862bdc3af94c1781bf70f86bccac0", - "reference": "1d8c2a99c80862bdc3af94c1781bf70f86bccac0", + "url": "https://api.github.com/repos/symfony/yaml/zipball/8c7bf1e7d5d6b05a690b715729cb4cd0c0a99c46", + "reference": "8c7bf1e7d5d6b05a690b715729cb4cd0c0a99c46", "shasum": "" }, "require": { @@ -2397,7 +2442,7 @@ ], "description": "Symfony Yaml Component", "homepage": "https://symfony.com", - "time": "2017-07-29 21:54:42" + "time": "2017-10-05T14:43:42+00:00" }, { "name": "webmozart/assert", @@ -2447,7 +2492,7 @@ "check", "validate" ], - "time": "2016-11-23 20:04:58" + "time": "2016-11-23T20:04:58+00:00" } ], "packages-dev": [ @@ -2488,7 +2533,7 @@ ], "description": "FinderFacade is a convenience wrapper for Symfony's Finder component.", "homepage": "https://github.com/sebastianbergmann/finder-facade", - "time": "2016-02-17 07:02:23" + "time": "2016-02-17T07:02:23+00:00" }, { "name": "sebastian/phpcpd", @@ -2538,7 +2583,7 @@ ], "description": "Copy/Paste Detector (CPD) for PHP code.", "homepage": "https://github.com/sebastianbergmann/phpcpd", - "time": "2017-02-05 07:48:01" + "time": "2017-02-05T07:48:01+00:00" }, { "name": "squizlabs/php_codesniffer", @@ -2613,7 +2658,7 @@ "phpcs", "standards" ], - "time": "2014-05-01 03:07:07" + "time": "2014-05-01T03:07:07+00:00" }, { "name": "theseer/fdomdocument", @@ -2653,7 +2698,7 @@ ], "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-30 11:53:12" + "time": "2017-06-30T11:53:12+00:00" } ], "aliases": [], diff --git a/dev/_suite/functionalSuite.xml b/dev/_suite/functionalSuite.xml new file mode 100644 index 000000000..62f97fdc7 --- /dev/null +++ b/dev/_suite/functionalSuite.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/dev/tests/_bootstrap.php b/dev/tests/_bootstrap.php new file mode 100644 index 000000000..57721a44e --- /dev/null +++ b/dev/tests/_bootstrap.php @@ -0,0 +1,63 @@ + 'http://baseurl:8080', + 'MAGENTO_BACKEND_NAME' => 'admin', + 'MAGENTO_ADMIN_USERNAME' => 'admin', + 'MAGENTO_ADMIN_PASSWORD' => 'admin123' +]; + +foreach ($TEST_ENVS as $key => $value) { + $_ENV[$key] = $value; +} + +// Add our test module to the whitelist +putenv('MODULE_WHITELIST=Magento_TestModule'); + + +// Define our own set of paths for the tests +defined('FW_BP') || define('FW_BP', PROJECT_ROOT); + +$RELATIVE_TESTS_MODULE_PATH = DIRECTORY_SEPARATOR . 'verification'; + +defined('TESTS_BP') || define('TESTS_BP', __DIR__); +defined('TESTS_MODULE_PATH') || define('TESTS_MODULE_PATH', TESTS_BP . $RELATIVE_TESTS_MODULE_PATH); + +$utilDir = DIRECTORY_SEPARATOR . 'Util'. DIRECTORY_SEPARATOR . '*.php'; + +//Load required util files from functional dir +$functionalUtilFiles = glob(TESTS_BP . DIRECTORY_SEPARATOR . 'verification' . $utilDir); +foreach (sortInterfaces($functionalUtilFiles) as $functionalUtilFile) { + require($functionalUtilFile); +} + +//Load required util files from unit dir +$unitUtilFiles = glob(TESTS_BP . DIRECTORY_SEPARATOR . 'unit' . $utilDir); +foreach (sortInterfaces($unitUtilFiles) as $unitUtilFile) { + require($unitUtilFile); +} + +function sortInterfaces($files) +{ + $bottom = []; + $top = []; + foreach ($files as $file) { + if (strstr(strtolower($file), 'interface')) { + $top[] = $file; + continue; + } + + $bottom[] = $file; + } + + return array_merge($top, $bottom); +} diff --git a/dev/tests/phpunit.xml b/dev/tests/phpunit.xml new file mode 100644 index 000000000..4751e1765 --- /dev/null +++ b/dev/tests/phpunit.xml @@ -0,0 +1,30 @@ + + + + + + verification + + + unit + + + + + ../../src/Magento/FunctionalTestingFramework/DataGenerator + ../../src/Magento/FunctionalTestingFramework/Page + ../../src/Magento/FunctionalTestingFramework/Suite + ../../src/Magento/FunctionalTestingFramework/Test + ../../src/Magento/FunctionalTestingFramework/Util + + + \ No newline at end of file diff --git a/dev/tests/unit/Magento/FunctionalTestFramework/Test/Util/ActionMergeUtilTest.php b/dev/tests/unit/Magento/FunctionalTestFramework/Test/Util/ActionMergeUtilTest.php new file mode 100644 index 000000000..1c5d68724 --- /dev/null +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Test/Util/ActionMergeUtilTest.php @@ -0,0 +1,159 @@ +resolveActionSteps($actions); + $orderedActionKeys = array_keys($orderedActions); + + $this->assertEquals($testObjNamePosBeforeFirst, $orderedActionKeys[0]); + $this->assertEquals($testObjNamePosFirst, $orderedActionKeys[1]); + $this->assertEquals($testObjNamePosEnd, $orderedActionKeys[$actionsLength + 1]); + $this->assertEquals($testObjNamePosAfterEnd, $orderedActionKeys[$actionsLength + 2]); + } + + /** + * Test to validate action steps properly resolve section element references. + * + * @return void + */ + public function testResolveActionStepSectionData() + { + //TODO implement section object mocker and test + } + + /** + * Test to validate action steps properly resolve page references. + * + * @return void + */ + public function resolveActionStepPageData() + { + //TODO implement page object mocker and test + } + + /** + * Test to validate action steps properly resolve entity data references. + * + * @return void + */ + public function testResolveActionStepEntityData() + { + $dataObjectName = 'myObject'; + $dataObjectType = 'testObject'; + $dataFieldName = 'myfield'; + $dataFieldValue = 'myValue'; + $userInputKey = "userInput"; + $userinputValue = "{{" . "${dataObjectName}.${dataFieldName}}}"; + $actionName = "myAction"; + $actionType = "myCustomType"; + + + // Set up mock data object + $mockData = [$dataFieldName => $dataFieldValue]; + $mockDataObject = new EntityDataObject($dataObjectName, $dataObjectType, $mockData, null, null, null); + + // Set up mock DataObject Handler + $mockDataHandler = $this->createMock(DataObjectHandler::class); + $mockDataHandler->expects($this->any()) + ->method('getObject') + ->with($this->matches($dataObjectName)) + ->willReturn($mockDataObject); + DataObjectHandlerReflectionUtil::setupMock($mockDataHandler); + + // Create test object and action object + $actionAttributes = [$userInputKey => $userinputValue]; + $actions[$actionName] = new ActionObject($actionName, $actionType, $actionAttributes); + + $this->assertEquals($userinputValue, $actions[$actionName]->getCustomActionAttributes()[$userInputKey]); + + $mergeUtil = new ActionMergeUtil(); + $resolvedActions = $mergeUtil->resolveActionSteps($actions); + + $this->assertEquals($dataFieldValue, $resolvedActions[$actionName]->getCustomActionAttributes()[$userInputKey]); + + DataObjectHandlerReflectionUtil::tearDown(); + } +} diff --git a/dev/tests/unit/Util/DataObjectHandlerReflectionUtil.php b/dev/tests/unit/Util/DataObjectHandlerReflectionUtil.php new file mode 100644 index 000000000..557ab5772 --- /dev/null +++ b/dev/tests/unit/Util/DataObjectHandlerReflectionUtil.php @@ -0,0 +1,47 @@ +bindTo(null, DataObjectHandler::class); + $setMockStatic($mockObject); + } + + /** + * Sets the Data Object Handler Instance to a null value for re-initialization. + */ + public static function tearDown() + { + $resetStatic = function () { + static::$DATA_OBJECT_HANDLER = null; + }; + + $resetMockStatic = $resetStatic->bindTo(null, DataObjectHandler::class); + $resetMockStatic(); + } +} \ No newline at end of file diff --git a/dev/tests/unit/Util/ObjectHandlerReflectionUtilInterface.php b/dev/tests/unit/Util/ObjectHandlerReflectionUtilInterface.php new file mode 100644 index 000000000..145c1a0e3 --- /dev/null +++ b/dev/tests/unit/Util/ObjectHandlerReflectionUtilInterface.php @@ -0,0 +1,24 @@ +amOnPage("/beforeUrl"); + } + + public function _after(AcceptanceTester $I) + { + $I->amOnPage("/afterUrl"); + } + + /** + * @Severity(level = SeverityLevel::SEVERE) + * @Title("Basic Functional Test") + * @Features({"Hardcoded Functional Test"}) + * @Stories({"MQE-425"}) + * @Parameter(name = "AcceptanceTester", value="$I") + * @group functionalTest + * @param AcceptanceTester $I + * @return void + */ + public function BasicFunctionalTest(AcceptanceTester $I) + { + $someVar = $I->grabValueFrom(); + $I->acceptPopup(); + $I->amOnPage("/test/url"); + $I->appendField(".functionalTestSelector", $someVar); + $I->attachFile(".functionalTestSelector", "testFileAttachment"); + $I->cancelPopup(); + $I->checkOption(".functionalTestSelector"); + $I->click(".functionalTestSelector"); + $I->clickWithLeftButton(".functionalTestSelector"); + $I->clickWithRightButton(".functionalTestSelector"); + $I->closeTab(); + $I->conditionalClick(".functionalTestSelector", ".functionalDependentTestSelector"); + $I->dontSee("someInput", ".functionalTestSelector"); + $I->dontSeeCheckboxIsChecked(".functionalTestSelector"); + $I->dontSeeCookie("someInput"); + $I->dontSeeCurrentUrlEquals("/functionalUrl"); + $I->dontSeeCurrentUrlMatches("/functionalUrl"); + $I->dontSeeElement(".functionalTestSelector"); + $I->dontSeeElementInDOM(".functionalTestSelector"); + $I->dontSeeInCurrentUrl("/functionalUrl"); + $I->dontSeeInField(".functionalTestSelector", $someVar); + $I->dontSeeInPageSource("someInput"); + $I->dontSeeInSource(""); + $I->dontSeeInTitle("someInput"); + $I->dontSeeLink("someInput", "/functionalUrl"); + $I->dontSeeOptionIsSelected(".functionalTestSelector", "someInput"); + $I->doubleClick(".functionalTestSelector"); + $I->dragAndDrop(".functionalTestSelector", ".functionalTestSelector2"); + $I->executeJS("someJSFunction"); + $I->fillField(".functionalTestSelector", "someInput"); + $grabAttributeVar = $I->grabAttributeFrom(".functionalTestSelector", "someInput"); + $grabCookieVar = $I->grabCookie("grabCookieInput", ['domain' => 'www.google.com']); + $grabUrlVar = $I->grabFromCurrentUrl("/grabCurrentUrl"); + $grabMultipleVar = $I->grabMultiple(".functionalTestSelector"); + $I->grabTextFrom(".functionalTestSelector"); + $grabValueVar = $I->grabValueFrom(".functionalTestSelector"); + $I->makeScreenshot("screenShotInput"); + $I->maximizeWindow(); + $I->moveBack(); + $I->moveForward(); + $I->moveMouseOver(".functionalTestSelector"); + $I->openNewTab(); + $I->pauseExecution(); + $I->pressKey(".functionalTestSelector"); + $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("/functionalUrl"); + $I->seeElement(".functionalTestSelector"); + $I->seeElementInDOM(".functionalTestSelector"); + $I->seeInCurrentUrl("/functionalUrl"); + $I->seeInField(".functionalTestSelector", "someInput"); + $I->seeInPageSource(""); + $I->seeInPopup("someInput"); + $I->seeInSource(""); + $I->seeInTitle("someInput"); + $I->seeLink("someInput", "/functionalUrl"); + $I->seeNumberOfElements(".functionalTestSelector", $someVar); + $I->seeOptionIsSelected(".functionalTestSelector", "someInput"); + $I->selectOption(".functionalTestSelector"); + $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->waitForJS("someJsFunction", 30); + $I->waitForText("someInput", 30, ".functionalTestSelector"); + } +} diff --git a/dev/tests/verification/Resources/testSuiteGeneration1.txt b/dev/tests/verification/Resources/testSuiteGeneration1.txt new file mode 100644 index 000000000..797b3c2a1 --- /dev/null +++ b/dev/tests/verification/Resources/testSuiteGeneration1.txt @@ -0,0 +1,4 @@ +dev/tests/verification/_generated/functionalSuite1/sampleSuite3Cest.php:includeTest --env chrome +dev/tests/verification/_generated/functionalSuite1/sampleSuite5Cest.php:additionalTest --env chrome +dev/tests/verification/_generated/functionalSuite1/sampleSuiteCest.php:includeTest --env chrome +dev/tests/verification/_generated/functionalSuite1/sampleSuite4Cest.php:includeTest --env chrome diff --git a/dev/tests/verification/TestModule/ActionObject/placeholder.txt b/dev/tests/verification/TestModule/ActionObject/placeholder.txt new file mode 100644 index 000000000..e69de29bb diff --git a/dev/tests/verification/TestModule/Cest/basicFunctionalCest.xml b/dev/tests/verification/TestModule/Cest/basicFunctionalCest.xml new file mode 100644 index 000000000..6d2fa6919 --- /dev/null +++ b/dev/tests/verification/TestModule/Cest/basicFunctionalCest.xml @@ -0,0 +1,114 @@ + + + + + + + + + <group value="functional"/> + <features value="Basic Functional Cest"/> + <stories value="MQE-305"/> + </annotations> + <before> + <amOnPage url="/beforeUrl" mergeKey="beforeAmOnPageKey"/> + </before> + <after> + <amOnPage url="/afterUrl" mergeKey="afterAmOnPageKey"/> + </after> + <test name="BasicFunctionalTest"> + <annotations> + <severity value="SEVERE"/> + <title value="Basic Functional Test"/> + <group value="functionalTest"/> + <features value="Hardcoded Functional Test"/> + <stories value="MQE-425"/> + </annotations> + <grabValueFrom returnVariable="someVar" mergeKey="someVarDefinition"/> + <acceptPopup mergeKey="acceptPopupKey1"/> + <amOnPage mergeKey="amOnPageKey1" url="/test/url"/> + <appendField variable="someVar" selector=".functionalTestSelector" userInput="someInput" mergeKey="appendFieldKey1" /> + <attachFile userInput="testFileAttachment" selector=".functionalTestSelector" mergeKey="attachFileKey1" /> + <cancelPopup mergeKey="cancelPopupKey1"/> + <checkOption selector=".functionalTestSelector" mergeKey="checkOptionKey1"/> + <click selector=".functionalTestSelector" mergeKey="clickKey1"/> + <clickWithLeftButton selector=".functionalTestSelector" mergeKey="clickWithLeftButtonKey1"/> + <clickWithRightButton selector=".functionalTestSelector" mergeKey="clickWithRightButtonKey1"/> + <closeTab mergeKey="closeTabKey1"/> + <conditionalClick selector=".functionalTestSelector" dependentSelector=".functionalDependentTestSelector" mergeKey="conditionalClickKey1"/> + <dontSee userInput="someInput" selector=".functionalTestSelector" mergeKey="dontSeeKey1" /> + <dontSeeCheckboxIsChecked selector=".functionalTestSelector" mergeKey="dontSeeCheckboxIsCheckedKey1"/> + <dontSeeCookie userInput="someInput" mergeKey="dontSeeCookieKey1"/> + <dontSeeCurrentUrlEquals url="/functionalUrl" mergeKey="dontSeeCurrentUrlEqualsKey1"/> + <dontSeeCurrentUrlMatches url="/functionalUrl" mergeKey="dontSeeCurrentUrlMatchesKey1"/> + <dontSeeElement selector=".functionalTestSelector" mergeKey="dontSeeElementKey1"/> + <dontSeeElementInDOM selector=".functionalTestSelector" mergeKey="dontSeeElementInDOMKey1"/> + <dontSeeInCurrentUrl url="/functionalUrl" mergeKey="dontSeeInCurrentUrlKey1"/> + <dontSeeInField variable="someVar" selector=".functionalTestSelector" userInput="someInput" mergeKey="dontSeeInFieldKey1" /> + <dontSeeInPageSource userInput="someInput" mergeKey="dontSeeInPageSourceKey1"/> + <dontSeeInSource html=""<myHtmlHere>"" mergeKey="dontSeeInSourceKey1"/> + <dontSeeInTitle userInput="someInput" mergeKey="dontSeeInTitleKey1"/> + <dontSeeLink userInput="someInput" url="/functionalUrl" mergeKey="dontSeeLinkKey1" /> + <dontSeeOptionIsSelected selector=".functionalTestSelector" userInput="someInput" mergeKey="dontSeeOptionIsSelectedKey1" /> + <doubleClick selector=".functionalTestSelector" mergeKey="doubleClickKey1"/> + <dragAndDrop selector1=".functionalTestSelector" selector2=".functionalTestSelector2" mergeKey="dragAndDropKey1" /> + <executeJS function="someJSFunction" mergeKey="executeJSKey1"/> + <fillField selector=".functionalTestSelector" userInput="someInput" mergeKey="fillFieldKey1" /> + <grabAttributeFrom returnVariable="grabAttributeVar" selector=".functionalTestSelector" userInput="someInput" mergeKey="grabAttributeFromKey1" /> + <grabCookie returnVariable="grabCookieVar" userInput="grabCookieInput" parameterArray="['domain' => 'www.google.com']" mergeKey="grabCookieKey1" /> + <grabFromCurrentUrl returnVariable="grabUrlVar" url="/grabCurrentUrl" mergeKey="grabFromCurrentUrlKey1" /> + <grabMultiple selector=".functionalTestSelector" returnVariable="grabMultipleVar" mergeKey="grabMultipleKey1" /> + <grabTextFrom returnVariable="" selector=".functionalTestSelector" mergeKey="grabTextFromKey1" /> + <grabValueFrom selector=".functionalTestSelector" returnVariable="grabValueVar" mergeKey="grabValueFromKey1" /> + <makeScreenshot userInput="screenShotInput" mergeKey="makeScreenshotKey1"/> + <maximizeWindow mergeKey="maximizeWindowKey1"/> + <moveBack mergeKey="moveBackKey1"/> + <moveForward mergeKey="moveForwardKey1"/> + <moveMouseOver selector=".functionalTestSelector" mergeKey="moveMouseOverKey1"/> + <openNewTab mergeKey="openNewTabKey1"/> + <pauseExecution mergeKey="pauseExecutionKey1"/> + <pressKey selector=".functionalTestSelector" mergeKey="pressKeyKey1"/> + <reloadPage mergeKey="reloadPageKey1"/> + <resetCookie userInput="cookieInput" mergeKey="resetCookieKey1"/> + <resizeWindow width="0" height="0" mergeKey="resizeWindowKey1" /> + <scrollTo selector=".functionalTestSelector" mergeKey="scrollToKey1"/> + <see userInput="someInput" selector=".functionalTestSelector" mergeKey="seeKey1" /> + <seeCheckboxIsChecked selector=".functionalTestSelector" mergeKey="seeCheckboxIsCheckedKey1"/> + <seeCookie userInput="someInput" mergeKey="seeCookieKey1"/> + <seeCurrentUrlEquals url="/functionalUrl" mergeKey="seeCurrentUrlEqualsKey1"/> + <seeCurrentUrlMatches url="/functionalUrl" mergeKey="seeCurrentUrlMatchesKey1"/> + <seeElement selector=".functionalTestSelector" mergeKey="seeElementKey1"/> + <seeElementInDOM selector=".functionalTestSelector" mergeKey="seeElementInDOMKey1"/> + <seeInCurrentUrl url="/functionalUrl" mergeKey="seeInCurrentUrlKey1"/> + <seeInField selector=".functionalTestSelector" userInput="someInput" mergeKey="seeInFieldKey1" /> + <seeInPageSource html=""<myHtmlHere>"" mergeKey="seeInPageSourceKey1"/> + <seeInPopup userInput="someInput" mergeKey="seeInPopupKey1"/> + <seeInSource html=""<myHtmlHere>"" mergeKey="seeInSourceKey1"/> + <seeInTitle userInput="someInput" mergeKey="seeInTitleKey1"/> + <seeLink userInput="someInput" url="/functionalUrl" mergeKey="seeLinkKey1" /> + <seeNumberOfElements variable="someVar" selector=".functionalTestSelector" mergeKey="seeNumberOfElementsKey1"/> + <seeOptionIsSelected selector=".functionalTestSelector" userInput="someInput" mergeKey="seeOptionIsSelectedKey1" /> + <selectOption selector=".functionalTestSelector" mergeKey="selectOptionKey1"/> + <setCookie value="someCookieValue" userInput="someInput" mergeKey="setCookieKey1" /> + <switchToIFrame userInput="someInput" mergeKey="switchToIFrameKey1"/> + <switchToNextTab mergeKey="switchToNextTabKey1"/> + <switchToPreviousTab mergeKey="switchToPreviousTabKey1"/> + <switchToWindow mergeKey="switchToWindowKey1"/> + <typeInPopup userInput="someInput" mergeKey="typeInPopupKey1"/> + <uncheckOption selector=".functionalTestSelector" mergeKey="uncheckOptionKey1"/> + <unselectOption selector=".functionalTestSelector" userInput="someInput" mergeKey="unselectOptionKey1" /> + <wait time="30" mergeKey="waitKey1"/> + <waitForElement time="30" selector=".functionalTestSelector" mergeKey="waitForElementKey1" /> + <waitForElementNotVisible selector=".functionalTestSelector" time="30" mergeKey="waitForElementNotVisibleKey1" /> + <waitForElementVisible selector=".functionalTestSelector" time="30" mergeKey="waitForElementVisibleKey1" /> + <waitForJS function="someJsFunction" time="30" mergeKey="waitForJSKey1" /> + <waitForText selector=".functionalTestSelector" userInput="someInput" time="30" mergeKey=""/> + </test> + </cest> +</config> \ No newline at end of file diff --git a/dev/tests/verification/TestModule/Cest/sampleSuiteCest.xml b/dev/tests/verification/TestModule/Cest/sampleSuiteCest.xml new file mode 100644 index 000000000..9f09a9eee --- /dev/null +++ b/dev/tests/verification/TestModule/Cest/sampleSuiteCest.xml @@ -0,0 +1,87 @@ +<?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="../../../../../src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + <cest name="sampleSuiteCest"> + <test name="includeTest"> + <amOnPage mergeKey="testOnPage" url="/someUrl"/> + <see mergeKey="seeThePage" selector=".someSelector"/> + <click mergeKey="clickOnSomething" selector=".clickable"/> + <fillField mergeKey="fillAField" selector=".fillable"/> + </test> + <test name="excludeTest"> + <amOnPage mergeKey="testOnPage" url="/someUrl"/> + <see mergeKey="seeThePage" selector=".someSelector"/> + <click mergeKey="clickOnSomething" selector=".clickable"/> + <fillField mergeKey="fillAField" selector=".fillable"/> + </test> + </cest> + <cest name="sampleSuite2Cest"> + <annotations> + <group value="exclude"/> + </annotations> + <test name="additionalTest"> + <amOnPage mergeKey="testOnPage" url="/someUrl"/> + <see mergeKey="seeThePage" selector=".someSelector"/> + <click mergeKey="clickOnSomething" selector=".clickable"/> + <fillField mergeKey="fillAField" selector=".fillable"/> + </test> + </cest> + <cest name="sampleSuite3Cest"> + <test name="includeTest"> + <annotations> + <group value="include"/> + </annotations> + <amOnPage mergeKey="testOnPage" url="/someUrl"/> + <see mergeKey="seeThePage" selector=".someSelector"/> + <click mergeKey="clickOnSomething" selector=".clickable"/> + <fillField mergeKey="fillAField" selector=".fillable"/> + </test> + <test name="additionalTest"> + <amOnPage mergeKey="testOnPage" url="/someUrl"/> + <see mergeKey="seeThePage" selector=".someSelector"/> + <click mergeKey="clickOnSomething" selector=".clickable"/> + <fillField mergeKey="fillAField" selector=".fillable"/> + </test> + </cest> + <cest name="sampleSuite4Cest"> + <test name="additionalTest"> + <annotations> + <group value="exclude"/> + </annotations> + <amOnPage mergeKey="testOnPage" url="/someUrl"/> + <see mergeKey="seeThePage" selector=".someSelector"/> + <click mergeKey="clickOnSomething" selector=".clickable"/> + <fillField mergeKey="fillAField" selector=".fillable"/> + </test> + <test name="includeTest"> + <amOnPage mergeKey="testOnPage" url="/someUrl"/> + <see mergeKey="seeThePage" selector=".someSelector"/> + <click mergeKey="clickOnSomething" selector=".clickable"/> + <fillField mergeKey="fillAField" selector=".fillable"/> + </test> + </cest> + <cest name="sampleSuite5Cest"> + <annotations> + <group value="include"/> + </annotations> + <test name="additionalTest"> + <amOnPage mergeKey="testOnPage" url="/someUrl"/> + <see mergeKey="seeThePage" selector=".someSelector"/> + <click mergeKey="clickOnSomething" selector=".clickable"/> + <fillField mergeKey="fillAField" selector=".fillable"/> + </test> + <test name="excludeTest"> + <amOnPage mergeKey="testOnPage" url="/someUrl"/> + <see mergeKey="seeThePage" selector=".someSelector"/> + <click mergeKey="clickOnSomething" selector=".clickable"/> + <fillField mergeKey="fillAField" selector=".fillable"/> + </test> + </cest> +</config> \ No newline at end of file diff --git a/dev/tests/verification/TestModule/Data/placeholder.txt b/dev/tests/verification/TestModule/Data/placeholder.txt new file mode 100644 index 000000000..e69de29bb diff --git a/dev/tests/verification/TestModule/Metadata/placeholder.txt b/dev/tests/verification/TestModule/Metadata/placeholder.txt new file mode 100644 index 000000000..e69de29bb diff --git a/dev/tests/verification/TestModule/Page/placeholder.txt b/dev/tests/verification/TestModule/Page/placeholder.txt new file mode 100644 index 000000000..e69de29bb diff --git a/dev/tests/verification/TestModule/Section/placeholder.txt b/dev/tests/verification/TestModule/Section/placeholder.txt new file mode 100644 index 000000000..e69de29bb diff --git a/dev/tests/verification/Tests/BasicCestGenerationTest.php b/dev/tests/verification/Tests/BasicCestGenerationTest.php new file mode 100644 index 000000000..b53223fce --- /dev/null +++ b/dev/tests/verification/Tests/BasicCestGenerationTest.php @@ -0,0 +1,42 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace tests\verification\Tests; + +use Magento\FunctionalTestingFramework\Test\Handlers\CestObjectHandler; +use Magento\FunctionalTestingFramework\Util\TestGenerator; +use PHPUnit\Framework\TestCase; +use tests\verification\Util\FileDiffUtil; + +class BasicCestGenerationTest extends TestCase +{ + const BASIC_FUNCTIONAL_CEST = 'BasicFunctionalCest'; + const RESOURCES_PATH = __DIR__ . '/../Resources'; + + /** + * Tests flat generation of a hardcoded cest file with no external references. + */ + public function testBasicGeneration() + { + $cest = CestObjectHandler::getInstance()->getObject(self::BASIC_FUNCTIONAL_CEST); + $test = TestGenerator::getInstance(null, [$cest]); + $test->createAllCestFiles(); + + $cestFile = $test->getExportDir() . + DIRECTORY_SEPARATOR . + self::BASIC_FUNCTIONAL_CEST . + ".php"; + + $this->assertTrue(file_exists($cestFile)); + + $fileDiffUtil = new FileDiffUtil( + self::RESOURCES_PATH . DIRECTORY_SEPARATOR . self::BASIC_FUNCTIONAL_CEST . ".txt", + $cestFile + ); + + $diffResult = $fileDiffUtil->diffContents(); + $this->assertNull($diffResult, $diffResult); + } +} diff --git a/dev/tests/verification/Tests/SuiteGenerationTest.php b/dev/tests/verification/Tests/SuiteGenerationTest.php new file mode 100644 index 000000000..ecba7d5cc --- /dev/null +++ b/dev/tests/verification/Tests/SuiteGenerationTest.php @@ -0,0 +1,80 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace tests\verification\Tests; + +use Magento\FunctionalTestingFramework\Suite\SuiteGenerator; +use Magento\FunctionalTestingFramework\Util\TestManifest; +use PHPUnit\Framework\TestCase; +use Symfony\Component\Yaml\Yaml; +use tests\verification\Util\FileDiffUtil; + +class SuiteGenerationTest extends TestCase +{ + const RESOURCES_DIR = TESTS_BP . DIRECTORY_SEPARATOR . 'verification' . DIRECTORY_SEPARATOR . 'Resources'; + const CONFIG_YML_FILE = FW_BP . DIRECTORY_SEPARATOR . SuiteGenerator::YAML_CODECEPTION_CONFIG_FILENAME; + + private static $YML_EXISTS_FLAG = false; + private static $TEST_GROUPS = []; + + + public static function setUpBeforeClass() + { + if (file_exists(self::CONFIG_YML_FILE)) { + self::$YML_EXISTS_FLAG = true; + return; + } + + $configYml = fopen(self::CONFIG_YML_FILE, "w"); + fclose($configYml); + } + + public function testSuiteGeneration1() + { + $groupName = 'functionalSuite1'; + + // Generate the Suite + SuiteGenerator::getInstance()->generateSuite($groupName); + + // Validate console message and add group name for later deletion + $this->expectOutputRegex('/Suite .* generated to .*/'); + self::$TEST_GROUPS[] = $groupName; + + // Validate Yaml file updated + $yml = Yaml::parse(file_get_contents(self::CONFIG_YML_FILE)); + $this->assertArrayHasKey($groupName, $yml['groups']); + + // Validate test manifest contents + $actualManifest = TESTS_BP . + DIRECTORY_SEPARATOR . + "verification" . + DIRECTORY_SEPARATOR . + "_generated" . + DIRECTORY_SEPARATOR . + $groupName . + DIRECTORY_SEPARATOR . + TestManifest::TEST_MANIFEST_FILENAME; + $expectedManifest = self::RESOURCES_DIR . DIRECTORY_SEPARATOR . __FUNCTION__ . ".txt"; + $fileDiffUtil = new FileDiffUtil($expectedManifest, $actualManifest); + $this->assertNull($fileDiffUtil->diffContents()); + } + + public static function tearDownAfterClass() + { + // restore config if we see there was an original codeception.yml file + if (self::$YML_EXISTS_FLAG) { + $yml = Yaml::parse(file_get_contents(self::CONFIG_YML_FILE)); + foreach (self::$TEST_GROUPS as $testGroup) { + unset($yml['group'][$testGroup]); + } + + file_put_contents(self::CONFIG_YML_FILE, Yaml::dump($yml, 10)); + return; + } + + unlink(self::CONFIG_YML_FILE); + } +} diff --git a/dev/tests/verification/Util/FileDiffUtil.php b/dev/tests/verification/Util/FileDiffUtil.php new file mode 100644 index 000000000..0937472a1 --- /dev/null +++ b/dev/tests/verification/Util/FileDiffUtil.php @@ -0,0 +1,53 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace tests\verification\Util; + +class FileDiffUtil +{ + /** + * Object which represents the expected file + * + * @var array|bool + */ + private $expectedFile; + + + /** + * Object which represents the actual file + * + * @var array|bool + */ + private $actualFile; + + /** + * FileDiffUtil constructor. + * + * @param string $expectedFilePath + * @param string $actualFilePath + */ + public function __construct($expectedFilePath, $actualFilePath) + { + $this->expectedFile = file($expectedFilePath); + $this->actualFile = file($actualFilePath); + } + + /** + * Function which does a line by line comparison between the contents of the two files fed to the constructor. + * + * @return null|string + */ + public function diffContents() + { + $differingContent = null; + foreach ($this->actualFile as $line_num => $line) { + if ($line != $this->expectedFile[$line_num]) { + return $this->expectedFile[$line_num] . "was expected, but found: ${line} on line ${line_num}."; + } + } + + return $differingContent; + } +} diff --git a/etc/di.xml b/etc/di.xml index b3be5638d..aa24e6e1c 100644 --- a/etc/di.xml +++ b/etc/di.xml @@ -206,7 +206,9 @@ <item name="/config/cest/test" xsi:type="string">name</item> <item name="/config/cest/test/actionGroup/argument" xsi:type="string">name</item> <item name="/config/cest/test/createData/required-entity" xsi:type="string">createDataKey</item> - <item name="/config/cest/test/(acceptPopup|actionGroup|amOnPage|amOnUrl|appendField|assertArraySubset|attachFile|cancelPopup|checkOption|click|clickWithLeftButton|clickWithRightButton|closeTab|createData|deleteData|dontSee|dontSeeCheckboxIsChecked|dontSeeCookie|dontSeeCurrentUrlEquals|dontSeeCurrentUrlMatches|dontSeeElement|dontSeeElementInDOM|dontSeeInCurrentUrl|dontSeeInField|dontSeeInPageSource|dontSeeInSource|dontSeeInTitle|dontSeeLink|dontSeeOptionIsSelected|doubleClick|dragAndDrop|entity|executeJS|fillField|formatMoney|grabAttributeFrom|grabCookie|grabFromCurrentUrl|grabMultiple|grabPageSource|grabTextFrom|grabValueFrom|loadSessionSnapshot|loginAsAdmin|makeScreenshot|maximizeWindow|moveBack|moveForward|moveMouseOver|openNewTab|pauseExecution|performOn|pressKey|reloadPage|resetCookie|resizeWindow|scrollTo|searchAndMultiSelectOption|see|seeCheckboxIsChecked|seeCookie|seeCurrentUrlEquals|seeCurrentUrlMatches|seeElement|seeElementInDOM|seeInCurrentUrl|seeInField|seeInFormFields|seeInPageSource|seeInPopup|seeInSource|seeInTitle|seeLink|seeNumberOfElements|seeOptionIsSelected|selectOption|setCookie|switchToIFrame|switchToNextTab|switchToPreviousTab|switchToWindow|typeInPopup|uncheckOption|unselectOption|wait|waitForAjaxLoad|waitForElement|waitForElementChange|waitForElementNotVisible|waitForElementVisible|waitForJS|waitForLoadingMaskToDisappear|waitForPageLoad|waitForText)" xsi:type="string">mergeKey</item> + <item name="/config/cest/test/updateData/required-entity" xsi:type="string">createDataKey</item> + <item name="/config/cest/test/getData/required-entity" xsi:type="string">createDataKey</item> + <item name="/config/cest/test/(acceptPopup|actionGroup|amOnPage|amOnUrl|appendField|assertArraySubset|attachFile|cancelPopup|checkOption|click|clickWithLeftButton|clickWithRightButton|closeTab|createData|deleteData|updateData|getData|dontSee|dontSeeCheckboxIsChecked|dontSeeCookie|dontSeeCurrentUrlEquals|dontSeeCurrentUrlMatches|dontSeeElement|dontSeeElementInDOM|dontSeeInCurrentUrl|dontSeeInField|dontSeeInPageSource|dontSeeInSource|dontSeeInTitle|dontSeeLink|dontSeeOptionIsSelected|doubleClick|dragAndDrop|entity|executeJS|fillField|formatMoney|grabAttributeFrom|grabCookie|grabFromCurrentUrl|grabMultiple|grabPageSource|grabTextFrom|grabValueFrom|loadSessionSnapshot|loginAsAdmin|makeScreenshot|maximizeWindow|moveBack|moveForward|moveMouseOver|openNewTab|pauseExecution|performOn|pressKey|reloadPage|resetCookie|resizeWindow|scrollTo|searchAndMultiSelectOption|see|seeCheckboxIsChecked|seeCookie|seeCurrentUrlEquals|seeCurrentUrlMatches|seeElement|seeElementInDOM|seeInCurrentUrl|seeInField|seeInFormFields|seeInPageSource|seeInPopup|seeInSource|seeInTitle|seeLink|seeNumberOfElements|seeOptionIsSelected|selectOption|setCookie|switchToIFrame|switchToNextTab|switchToPreviousTab|switchToWindow|typeInPopup|uncheckOption|unselectOption|wait|waitForAjaxLoad|waitForElement|waitForElementChange|waitForElementNotVisible|waitForElementVisible|waitForJS|waitForLoadingMaskToDisappear|waitForPageLoad|waitForText)" xsi:type="string">mergeKey</item> </argument> <argument name="fileName" xsi:type="string">*Cest.xml</argument> <argument name="defaultScope" xsi:type="string">Cest</argument> @@ -216,14 +218,20 @@ <virtualType name="Magento\FunctionalTestingFramework\Test\Config\Dom\ArrayNodeConfig" type="Magento\FunctionalTestingFramework\Config\Dom\ArrayNodeConfig"> <arguments> <argument name="assocArrayAttributes" xsi:type="array"> - <item name="/config/cest/test/(acceptPopup|actionGroup|amOnPage|amOnUrl|appendField|assertArraySubset|attachFile|cancelPopup|checkOption|click|clickWithLeftButton|clickWithRightButton|closeTab|createData|deleteData|dontSee|dontSeeCheckboxIsChecked|dontSeeCookie|dontSeeCurrentUrlEquals|dontSeeCurrentUrlMatches|dontSeeElement|dontSeeElementInDOM|dontSeeInCurrentUrl|dontSeeInField|dontSeeInPageSource|dontSeeInSource|dontSeeInTitle|dontSeeLink|dontSeeOptionIsSelected|doubleClick|dragAndDrop|entity|executeJS|fillField|formatMoney|grabAttributeFrom|grabCookie|grabFromCurrentUrl|grabMultiple|grabPageSource|grabTextFrom|grabValueFrom|loadSessionSnapshot|loginAsAdmin|makeScreenshot|maximizeWindow|moveBack|moveForward|moveMouseOver|openNewTab|pauseExecution|performOn|pressKey|reloadPage|resetCookie|resizeWindow|scrollTo|searchAndMultiSelectOption|see|seeCheckboxIsChecked|seeCookie|seeCurrentUrlEquals|seeCurrentUrlMatches|seeElement|seeElementInDOM|seeInCurrentUrl|seeInField|seeInFormFields|seeInPageSource|seeInPopup|seeInSource|seeInTitle|seeLink|seeNumberOfElements|seeOptionIsSelected|selectOption|setCookie|switchToIFrame|switchToNextTab|switchToPreviousTab|switchToWindow|typeInPopup|uncheckOption|unselectOption|wait|waitForAjaxLoad|waitForElement|waitForElementChange|waitForElementNotVisible|waitForElementVisible|waitForJS|waitForLoadingMaskToDisappear|waitForPageLoad|waitForText)" xsi:type="string">mergeKey</item> - <item name="/config/cest/before/(acceptPopup|actionGroup|amOnPage|amOnUrl|appendField|assertArraySubset|attachFile|cancelPopup|checkOption|click|clickWithLeftButton|clickWithRightButton|closeTab|createData|deleteData|dontSee|dontSeeCheckboxIsChecked|dontSeeCookie|dontSeeCurrentUrlEquals|dontSeeCurrentUrlMatches|dontSeeElement|dontSeeElementInDOM|dontSeeInCurrentUrl|dontSeeInField|dontSeeInPageSource|dontSeeInSource|dontSeeInTitle|dontSeeLink|dontSeeOptionIsSelected|doubleClick|dragAndDrop|entity|executeJS|fillField|formatMoney|grabAttributeFrom|grabCookie|grabFromCurrentUrl|grabMultiple|grabPageSource|grabTextFrom|grabValueFrom|loadSessionSnapshot|loginAsAdmin|makeScreenshot|maximizeWindow|moveBack|moveForward|moveMouseOver|openNewTab|pauseExecution|performOn|pressKey|reloadPage|resetCookie|resizeWindow|scrollTo|searchAndMultiSelectOption|see|seeCheckboxIsChecked|seeCookie|seeCurrentUrlEquals|seeCurrentUrlMatches|seeElement|seeElementInDOM|seeInCurrentUrl|seeInField|seeInFormFields|seeInPageSource|seeInPopup|seeInSource|seeInTitle|seeLink|seeNumberOfElements|seeOptionIsSelected|selectOption|setCookie|switchToIFrame|switchToNextTab|switchToPreviousTab|switchToWindow|typeInPopup|uncheckOption|unselectOption|wait|waitForAjaxLoad|waitForElement|waitForElementChange|waitForElementNotVisible|waitForElementVisible|waitForJS|waitForLoadingMaskToDisappear|waitForPageLoad|waitForText)" xsi:type="string">mergeKey</item> - <item name="/config/cest/after/(acceptPopup|actionGroup|amOnPage|amOnUrl|appendField|assertArraySubset|attachFile|cancelPopup|checkOption|click|clickWithLeftButton|clickWithRightButton|closeTab|createData|deleteData|dontSee|dontSeeCheckboxIsChecked|dontSeeCookie|dontSeeCurrentUrlEquals|dontSeeCurrentUrlMatches|dontSeeElement|dontSeeElementInDOM|dontSeeInCurrentUrl|dontSeeInField|dontSeeInPageSource|dontSeeInSource|dontSeeInTitle|dontSeeLink|dontSeeOptionIsSelected|doubleClick|dragAndDrop|entity|executeJS|fillField|formatMoney|grabAttributeFrom|grabCookie|grabFromCurrentUrl|grabMultiple|grabPageSource|grabTextFrom|grabValueFrom|loadSessionSnapshot|loginAsAdmin|makeScreenshot|maximizeWindow|moveBack|moveForward|moveMouseOver|openNewTab|pauseExecution|performOn|pressKey|reloadPage|resetCookie|resizeWindow|scrollTo|searchAndMultiSelectOption|see|seeCheckboxIsChecked|seeCookie|seeCurrentUrlEquals|seeCurrentUrlMatches|seeElement|seeElementInDOM|seeInCurrentUrl|seeInField|seeInFormFields|seeInPageSource|seeInPopup|seeInSource|seeInTitle|seeLink|seeNumberOfElements|seeOptionIsSelected|selectOption|setCookie|switchToIFrame|switchToNextTab|switchToPreviousTab|switchToWindow|typeInPopup|uncheckOption|unselectOption|wait|waitForAjaxLoad|waitForElement|waitForElementChange|waitForElementNotVisible|waitForElementVisible|waitForJS|waitForLoadingMaskToDisappear|waitForPageLoad|waitForText)" xsi:type="string">mergeKey</item> + <item name="/config/cest/test/(acceptPopup|actionGroup|amOnPage|amOnUrl|appendField|assertArraySubset|attachFile|cancelPopup|checkOption|click|clickWithLeftButton|clickWithRightButton|closeTab|createData|deleteData|updateData|getData|dontSee|dontSeeCheckboxIsChecked|dontSeeCookie|dontSeeCurrentUrlEquals|dontSeeCurrentUrlMatches|dontSeeElement|dontSeeElementInDOM|dontSeeInCurrentUrl|dontSeeInField|dontSeeInPageSource|dontSeeInSource|dontSeeInTitle|dontSeeLink|dontSeeOptionIsSelected|doubleClick|dragAndDrop|entity|executeJS|fillField|formatMoney|grabAttributeFrom|grabCookie|grabFromCurrentUrl|grabMultiple|grabPageSource|grabTextFrom|grabValueFrom|loadSessionSnapshot|loginAsAdmin|makeScreenshot|maximizeWindow|moveBack|moveForward|moveMouseOver|openNewTab|pauseExecution|performOn|pressKey|reloadPage|resetCookie|resizeWindow|scrollTo|searchAndMultiSelectOption|see|seeCheckboxIsChecked|seeCookie|seeCurrentUrlEquals|seeCurrentUrlMatches|seeElement|seeElementInDOM|seeInCurrentUrl|seeInField|seeInFormFields|seeInPageSource|seeInPopup|seeInSource|seeInTitle|seeLink|seeNumberOfElements|seeOptionIsSelected|selectOption|setCookie|switchToIFrame|switchToNextTab|switchToPreviousTab|switchToWindow|typeInPopup|uncheckOption|unselectOption|wait|waitForAjaxLoad|waitForElement|waitForElementChange|waitForElementNotVisible|waitForElementVisible|waitForJS|waitForLoadingMaskToDisappear|waitForPageLoad|waitForText)" xsi:type="string">mergeKey</item> + <item name="/config/cest/before/(acceptPopup|actionGroup|amOnPage|amOnUrl|appendField|assertArraySubset|attachFile|cancelPopup|checkOption|click|clickWithLeftButton|clickWithRightButton|closeTab|createData|deleteData|updateData|getData|dontSee|dontSeeCheckboxIsChecked|dontSeeCookie|dontSeeCurrentUrlEquals|dontSeeCurrentUrlMatches|dontSeeElement|dontSeeElementInDOM|dontSeeInCurrentUrl|dontSeeInField|dontSeeInPageSource|dontSeeInSource|dontSeeInTitle|dontSeeLink|dontSeeOptionIsSelected|doubleClick|dragAndDrop|entity|executeJS|fillField|formatMoney|grabAttributeFrom|grabCookie|grabFromCurrentUrl|grabMultiple|grabPageSource|grabTextFrom|grabValueFrom|loadSessionSnapshot|loginAsAdmin|makeScreenshot|maximizeWindow|moveBack|moveForward|moveMouseOver|openNewTab|pauseExecution|performOn|pressKey|reloadPage|resetCookie|resizeWindow|scrollTo|searchAndMultiSelectOption|see|seeCheckboxIsChecked|seeCookie|seeCurrentUrlEquals|seeCurrentUrlMatches|seeElement|seeElementInDOM|seeInCurrentUrl|seeInField|seeInFormFields|seeInPageSource|seeInPopup|seeInSource|seeInTitle|seeLink|seeNumberOfElements|seeOptionIsSelected|selectOption|setCookie|switchToIFrame|switchToNextTab|switchToPreviousTab|switchToWindow|typeInPopup|uncheckOption|unselectOption|wait|waitForAjaxLoad|waitForElement|waitForElementChange|waitForElementNotVisible|waitForElementVisible|waitForJS|waitForLoadingMaskToDisappear|waitForPageLoad|waitForText)" xsi:type="string">mergeKey</item> + <item name="/config/cest/after/(acceptPopup|actionGroup|amOnPage|amOnUrl|appendField|assertArraySubset|attachFile|cancelPopup|checkOption|click|clickWithLeftButton|clickWithRightButton|closeTab|createData|deleteData|updateData|getData|dontSee|dontSeeCheckboxIsChecked|dontSeeCookie|dontSeeCurrentUrlEquals|dontSeeCurrentUrlMatches|dontSeeElement|dontSeeElementInDOM|dontSeeInCurrentUrl|dontSeeInField|dontSeeInPageSource|dontSeeInSource|dontSeeInTitle|dontSeeLink|dontSeeOptionIsSelected|doubleClick|dragAndDrop|entity|executeJS|fillField|formatMoney|grabAttributeFrom|grabCookie|grabFromCurrentUrl|grabMultiple|grabPageSource|grabTextFrom|grabValueFrom|loadSessionSnapshot|loginAsAdmin|makeScreenshot|maximizeWindow|moveBack|moveForward|moveMouseOver|openNewTab|pauseExecution|performOn|pressKey|reloadPage|resetCookie|resizeWindow|scrollTo|searchAndMultiSelectOption|see|seeCheckboxIsChecked|seeCookie|seeCurrentUrlEquals|seeCurrentUrlMatches|seeElement|seeElementInDOM|seeInCurrentUrl|seeInField|seeInFormFields|seeInPageSource|seeInPopup|seeInSource|seeInTitle|seeLink|seeNumberOfElements|seeOptionIsSelected|selectOption|setCookie|switchToIFrame|switchToNextTab|switchToPreviousTab|switchToWindow|typeInPopup|uncheckOption|unselectOption|wait|waitForAjaxLoad|waitForElement|waitForElementChange|waitForElementNotVisible|waitForElementVisible|waitForJS|waitForLoadingMaskToDisappear|waitForPageLoad|waitForText)" xsi:type="string">mergeKey</item> <item name="/config/cest/test" xsi:type="string">name</item> <item name="/config/cest" xsi:type="string">name</item> <item name="/config/cest/test/createData/required-entity" xsi:type="string">createDataKey</item> <item name="/config/cest/before/createData/required-entity" xsi:type="string">createDataKey</item> <item name="/config/cest/after/createData/required-entity" xsi:type="string">createDataKey</item> + <item name="/config/cest/test/updateData/required-entity" xsi:type="string">createDataKey</item> + <item name="/config/cest/before/updateData/required-entity" xsi:type="string">createDataKey</item> + <item name="/config/cest/after/updateData/required-entity" xsi:type="string">createDataKey</item> + <item name="/config/cest/test/getData/required-entity" xsi:type="string">createDataKey</item> + <item name="/config/cest/before/getData/required-entity" xsi:type="string">createDataKey</item> + <item name="/config/cest/after/getData/required-entity" xsi:type="string">createDataKey</item> <item name="/config/cest/test/actionGroup/argument" xsi:type="string">name</item> </argument> <argument name="numericArrays" xsi:type="array"> @@ -309,4 +317,64 @@ <argument name="reader" xsi:type="object">Magento\FunctionalTestingFramework\Config\Reader\ActionGroupData</argument> </arguments> </virtualType> + + <!--Config for Suite Data --> + + <type name="Magento\FunctionalTestingFramework\Suite\Parsers\SuiteDataParser"> + <arguments> + <argument name="suiteData" xsi:type="object">Magento\FunctionalTestingFramework\Suite\Config\SuiteData</argument> + </arguments> + </type> + <virtualType name="Magento\FunctionalTestingFramework\Suite\Config\SuiteData" type="Magento\FunctionalTestingFramework\Config\Data"> + <arguments> + <argument name="reader" xsi:type="object">Magento\FunctionalTestingFramework\Config\Reader\SuiteData</argument> + </arguments> + </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> + </arguments> + </virtualType> + <virtualType name="Magento\FunctionalTestingFramework\Config\Reader\SuiteData" type="Magento\FunctionalTestingFramework\Config\Reader\Filesystem"> + <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="idAttributes" xsi:type="array"> + <item name="/suites/suite" xsi:type="string">name</item> + <item name="/suites/suite/include/group" xsi:type="string">name</item> + <item name="/suites/suite/include/cest" xsi:type="string">name</item> + <item name="/suites/suite/include/module" xsi:type="string">name</item> + <item name="/suites/suite/exclude/group" xsi:type="string">name</item> + <item name="/suites/suite/exclude/cest" xsi:type="string">name</item> + <item name="/suites/suite/exclude/module" xsi:type="string">name</item> + </argument> + <argument name="fileName" xsi:type="string">*.xml</argument> + <argument name="defaultScope" xsi:type="string">_suite</argument> + </arguments> + </virtualType> + + <virtualType name="Magento\FunctionalTestingFramework\Suite\Config\Dom\SuiteArrayNodeConfig" type="Magento\FunctionalTestingFramework\Config\Dom\ArrayNodeConfig"> + <arguments> + <argument name="assocArrayAttributes" xsi:type="array"> + <item name="/suites/suite" xsi:type="string">name</item> + <item name="/suites/suite/before/(createData|deleteData)" xsi:type="string">mergeKey</item> + <item name="/suites/suite/after/(createData|deleteData)" xsi:type="string">mergeKey</item> + <item name="/suites/suite/before/createData/required-entity" xsi:type="string">createDataKey</item> + <item name="/suites/suite/after/createData/required-entity" xsi:type="string">createDataKey</item> + <item name="/suites/suite/include/group" xsi:type="string">name</item> + <item name="/suites/suite/include/cest" xsi:type="string">name</item> + <item name="/suites/suite/include/module" xsi:type="string">name</item> + <item name="/suites/suite/exclude/group" xsi:type="string">name</item> + <item name="/suites/suite/exclude/cest" xsi:type="string">name</item> + <item name="/suites/suite/exclude/module" xsi:type="string">name</item> + </argument> + </arguments> + </virtualType> + + <virtualType name="Magento\FunctionalTestingFramework\Config\SuiteDataConverter" type="Magento\FunctionalTestingFramework\Test\Config\Converter\Dom\Flat"> + <arguments> + <argument name="arrayNodeConfig" xsi:type="object">Magento\FunctionalTestingFramework\Suite\Config\Dom\SuiteArrayNodeConfig</argument> + </arguments> + </virtualType> </config> diff --git a/src/Magento/FunctionalTestingFramework/Code/Reader/ClassReaderInterface.php b/src/Magento/FunctionalTestingFramework/Code/Reader/ClassReaderInterface.php index 21472847c..ff49f1d37 100644 --- a/src/Magento/FunctionalTestingFramework/Code/Reader/ClassReaderInterface.php +++ b/src/Magento/FunctionalTestingFramework/Code/Reader/ClassReaderInterface.php @@ -1,7 +1,6 @@ <?php /** - * - * Copyright © 2013-2017 Magento, Inc. All rights reserved. + * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ diff --git a/src/Magento/FunctionalTestingFramework/Config/Converter/Dom/Flat.php b/src/Magento/FunctionalTestingFramework/Config/Converter/Dom/Flat.php index b67f543e4..23f3b3e7c 100644 --- a/src/Magento/FunctionalTestingFramework/Config/Converter/Dom/Flat.php +++ b/src/Magento/FunctionalTestingFramework/Config/Converter/Dom/Flat.php @@ -1,6 +1,6 @@ <?php /** - * Copyright © 2013-2017 Magento, Inc. All rights reserved. + * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ namespace Magento\FunctionalTestingFramework\Config\Converter\Dom; diff --git a/src/Magento/FunctionalTestingFramework/Config/FileResolver/Mask.php b/src/Magento/FunctionalTestingFramework/Config/FileResolver/Mask.php index 71bf62cdc..b708ee052 100644 --- a/src/Magento/FunctionalTestingFramework/Config/FileResolver/Mask.php +++ b/src/Magento/FunctionalTestingFramework/Config/FileResolver/Mask.php @@ -1,6 +1,6 @@ <?php /** - * Copyright © 2017 Magento. All rights reserved. + * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ diff --git a/src/Magento/FunctionalTestingFramework/Config/FileResolver/Root.php b/src/Magento/FunctionalTestingFramework/Config/FileResolver/Root.php new file mode 100644 index 000000000..e728fa418 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Config/FileResolver/Root.php @@ -0,0 +1,28 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\FunctionalTestingFramework\Config\FileResolver; + +use Magento\FunctionalTestingFramework\Config\FileResolverInterface; +use Magento\FunctionalTestingFramework\Util\Iterator\File; + +class Root implements FileResolverInterface +{ + + /** + * Retrieve the list of configuration files with given name that relate to specified scope at the tests level + * + * @param string $filename + * @param string $scope + * @return array|\Iterator,\Countable + */ + public function get($filename, $scope) + { + $paths = glob(dirname(TESTS_BP) . DIRECTORY_SEPARATOR . $scope . DIRECTORY_SEPARATOR . $filename); + + return new File($paths); + } +} diff --git a/src/Magento/FunctionalTestingFramework/Data/Argument/Interpreter/ArrayType.php b/src/Magento/FunctionalTestingFramework/Data/Argument/Interpreter/ArrayType.php index 98fcf05e2..056a4f9d2 100644 --- a/src/Magento/FunctionalTestingFramework/Data/Argument/Interpreter/ArrayType.php +++ b/src/Magento/FunctionalTestingFramework/Data/Argument/Interpreter/ArrayType.php @@ -1,6 +1,6 @@ <?php /** - * Copyright © 2013-2017 Magento, Inc. All rights reserved. + * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ namespace Magento\FunctionalTestingFramework\Data\Argument\Interpreter; diff --git a/src/Magento/FunctionalTestingFramework/DataGenerator/Handlers/DataObjectHandler.php b/src/Magento/FunctionalTestingFramework/DataGenerator/Handlers/DataObjectHandler.php index f52659b34..ad60915d2 100644 --- a/src/Magento/FunctionalTestingFramework/DataGenerator/Handlers/DataObjectHandler.php +++ b/src/Magento/FunctionalTestingFramework/DataGenerator/Handlers/DataObjectHandler.php @@ -1,4 +1,8 @@ <?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ namespace Magento\FunctionalTestingFramework\DataGenerator\Handlers; @@ -62,14 +66,8 @@ class DataObjectHandler implements ObjectHandlerInterface public static function getInstance() { if (!self::$DATA_OBJECT_HANDLER) { - $entityParser = ObjectManagerFactory::getObjectManager()->create(DataProfileSchemaParser::class); - $entityParsedData = $entityParser->readDataProfiles(); - - if (!$entityParsedData) { - throw new \Exception("No entities could be parsed from xml definitions"); - } - - self::$DATA_OBJECT_HANDLER = new DataObjectHandler($entityParsedData); + self::$DATA_OBJECT_HANDLER = new DataObjectHandler(); + self::$DATA_OBJECT_HANDLER->initDataObjects(); } return self::$DATA_OBJECT_HANDLER; @@ -78,11 +76,10 @@ public static function getInstance() /** * DataArrayProcessor constructor. * @constructor - * @param array $arrayData */ - private function __construct($arrayData) + private function __construct() { - $this->arrayData = $arrayData; + // private constructor } /** @@ -105,12 +102,27 @@ public function getObject($entityName) */ public function getAllObjects() { - if (!$this->data) { - $this->parseEnvVariables(); - $this->parseDataEntities(); + return $this->data; + } + + /** + * Method to initialize parsing of data.xml and read into objects. + * + * @return void + */ + private function initDataObjects() + { + $entityParser = ObjectManagerFactory::getObjectManager()->create(DataProfileSchemaParser::class); + $entityParsedData = $entityParser->readDataProfiles(); + + if (!$entityParsedData) { + trigger_error("No entities could be parsed from xml definitions", E_USER_NOTICE); + return; } - return $this->data; + $this->arrayData = $entityParsedData; + $this->parseEnvVariables(); + $this->parseDataEntities(); } /** diff --git a/src/Magento/FunctionalTestingFramework/DataGenerator/Objects/EntityDataObject.php b/src/Magento/FunctionalTestingFramework/DataGenerator/Objects/EntityDataObject.php index 41730f3aa..479267a99 100644 --- a/src/Magento/FunctionalTestingFramework/DataGenerator/Objects/EntityDataObject.php +++ b/src/Magento/FunctionalTestingFramework/DataGenerator/Objects/EntityDataObject.php @@ -204,7 +204,7 @@ public function getDataByName($dataName, $uniDataFormat) * category->id) * * @param string $dataKey - * @return array|null + * @return string|null */ public function getVarReference($dataKey) { diff --git a/src/Magento/FunctionalTestingFramework/DataGenerator/Objects/OperationDefinitionObject.php b/src/Magento/FunctionalTestingFramework/DataGenerator/Objects/OperationDefinitionObject.php index 8f9ac19a4..b894592dd 100644 --- a/src/Magento/FunctionalTestingFramework/DataGenerator/Objects/OperationDefinitionObject.php +++ b/src/Magento/FunctionalTestingFramework/DataGenerator/Objects/OperationDefinitionObject.php @@ -195,10 +195,6 @@ public function getApiUrl() if (!$this->apiUrl) { $this->apiUrl = $this->apiUri; - if (array_key_exists('path', $this->params)) { - $this->addPathParam(); - } - if (array_key_exists('query', $this->params)) { $this->addQueryParams(); } @@ -267,18 +263,6 @@ public function getReturnRegex() return $this->returnRegex; } - /** - * Function to append path params where necessary - * - * @return void - */ - private function addPathParam() - { - foreach ($this->params['path'] as $paramName => $paramValue) { - $this->apiUrl = $this->apiUrl . "/" . $paramValue; - } - } - /** * Function to append or add query parameters * diff --git a/src/Magento/FunctionalTestingFramework/DataGenerator/Persist/Curl/AdminExecutor.php b/src/Magento/FunctionalTestingFramework/DataGenerator/Persist/Curl/AdminExecutor.php index 5e90cf7d8..3fe613771 100644 --- a/src/Magento/FunctionalTestingFramework/DataGenerator/Persist/Curl/AdminExecutor.php +++ b/src/Magento/FunctionalTestingFramework/DataGenerator/Persist/Curl/AdminExecutor.php @@ -59,12 +59,12 @@ public function __construct() } /** - * Authorize customer on backend. + * Authorize admin on backend. * * @return void * @throws TestFrameworkException */ - public function authorize() + private function authorize() { // Perform GET to backend url so form_key is set $this->transport->write(self::$adminUrl, [], CurlInterface::GET); @@ -85,7 +85,7 @@ public function authorize() } /** - * Init Form Key from response. + * Set Form Key from response. * * @return void */ @@ -114,7 +114,7 @@ public function write($url, $data = [], $method = CurlInterface::POST, $headers $data['form_key'] = $this->formKey; } else { throw new TestFrameworkException( - sprintf('Form key is absent! Url: "%s" Response: "%s"', $url, $this->response) + sprintf('Form key is absent! Url: "%s" Response: "%s"', $apiUrl, $this->response) ); } @@ -135,22 +135,14 @@ public function read($successRegex = null, $returnRegex = null) $this->setFormKey(); if (!empty($successRegex)) { - preg_match( - '/' . preg_quote($successRegex, '/') . '/', - $this->response, - $successMatches - ); + preg_match($successRegex, $this->response, $successMatches); if (empty($successMatches)) { throw new TestFrameworkException("Entity creation was not successful! Response: $this->response"); } } if (!empty($returnRegex)) { - preg_match( - '/' . preg_quote($returnRegex, '/') . '/', - $this->response, - $returnMatches - ); + preg_match($returnRegex, $this->response, $returnMatches); if (!empty($returnMatches)) { return $returnMatches; } diff --git a/src/Magento/FunctionalTestingFramework/DataGenerator/Persist/Curl/FrontendExecutor.php b/src/Magento/FunctionalTestingFramework/DataGenerator/Persist/Curl/FrontendExecutor.php new file mode 100644 index 000000000..b6f448dc0 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/DataGenerator/Persist/Curl/FrontendExecutor.php @@ -0,0 +1,210 @@ +<?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; +use Magento\FunctionalTestingFramework\Util\Protocol\CurlTransport; +use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; + +/** + * Curl executor for requests to Frontend. + */ +class FrontendExecutor extends AbstractExecutor implements CurlInterface +{ + /** + * Curl transport protocol. + * + * @var CurlTransport + */ + private $transport; + + /** + * Form key. + * + * @var string + */ + private $formKey = null; + + /** + * Response data. + * + * @var string + */ + private $response; + + /** + * Cookies data. + * + * @var string + */ + private $cookies = ''; + + /** + * Customer email used for authentication. + * + * @var string + */ + private $customerEmail; + + /** + * Customer password used for authentication. + * + * @var string + */ + private $customerPassword; + + /** + * FrontendExecutor constructor. + * + * @param string $customerEmail + * @param string $customerPassWord + */ + public function __construct($customerEmail, $customerPassWord) + { + if (!isset(parent::$baseUrl)) { + parent::resolveBaseUrl(); + } + $this->transport = new CurlTransport(); + $this->customerEmail = $customerEmail; + $this->customerPassword = $customerPassWord; + $this->authorize(); + } + + /** + * Authorize customer on frontend. + * + * @return void + * @throws TestFrameworkException + */ + private function authorize() + { + $url = parent::$baseUrl . 'customer/account/login/'; + $this->transport->write($url); + $this->read(); + + $url = parent::$baseUrl . 'customer/account/loginPost/'; + $data = [ + 'login[username]' => $this->customerEmail, + 'login[password]' => $this->customerPassword, + 'form_key' => $this->formKey, + ]; + $this->transport->write($url, $data, CurlInterface::POST, ['Set-Cookie:' . $this->cookies]); + $response = $this->read(); + if (strpos($response, 'customer/account/login')) { + throw new TestFrameworkException($this->customerEmail . ', cannot be logged in by curl handler!'); + } + } + + /** + * Set Form Key from response. + * + * @return void + */ + private function setFormKey() + { + $str = substr($this->response, strpos($this->response, 'form_key')); + preg_match('/value="(.*)" \/>/', $str, $matches); + if (!empty($matches[1])) { + $this->formKey = $matches[1]; + } + } + + /** + * Set Cookies from response. + * + * @return void + */ + protected function setCookies() + { + preg_match_all('|Set-Cookie: (.*);|U', $this->response, $matches); + if (!empty($matches[1])) { + $this->cookies = implode('; ', $matches[1]); + } + } + + /** + * Send request to the remote server. + * + * @param string $url + * @param array $data + * @param string $method + * @param array $headers + * @return void + * @throws TestFrameworkException + */ + public function write($url, $data = [], $method = CurlInterface::POST, $headers = []) + { + if (isset($data['customer_email'])) { + unset($data['customer_email']); + } + if (isset($data['customer_password'])) { + unset($data['customer_password']); + } + $apiUrl = parent::$baseUrl . $url; + if ($this->formKey) { + $data['form_key'] = $this->formKey; + } else { + throw new TestFrameworkException( + sprintf('Form key is absent! Url: "%s" Response: "%s"', $apiUrl, $this->response) + ); + } + $headers = ['Set-Cookie:' . $this->cookies]; + $this->transport->write($apiUrl, str_replace('null', '', http_build_query($data)), $method, $headers); + } + + /** + * Read response from server. + * + * @param string $successRegex + * @param string $returnRegex + * @return string|array + * @throws TestFrameworkException + */ + public function read($successRegex = null, $returnRegex = null) + { + $this->response = $this->transport->read(); + $this->setCookies(); + $this->setFormKey(); + + if (!empty($successRegex)) { + preg_match($successRegex, $this->response, $successMatches); + if (empty($successMatches)) { + throw new TestFrameworkException("Entity creation was not successful! Response: $this->response"); + } + } + + if (!empty($returnRegex)) { + preg_match($returnRegex, $this->response, $returnMatches); + if (!empty($returnMatches)) { + return $returnMatches; + } + } + return $this->response; + } + + /** + * Add additional option to cURL. + * + * @param int $option the CURLOPT_* constants + * @param int|string|bool|array $value + * @return void + */ + public function addOption($option, $value) + { + $this->transport->addOption($option, $value); + } + + /** + * Close the connection to the server. + * + * @return void + */ + public function close() + { + $this->transport->close(); + } +} diff --git a/src/Magento/FunctionalTestingFramework/DataGenerator/Persist/CurlHandler.php b/src/Magento/FunctionalTestingFramework/DataGenerator/Persist/CurlHandler.php index e7d73676e..a4a574dd2 100644 --- a/src/Magento/FunctionalTestingFramework/DataGenerator/Persist/CurlHandler.php +++ b/src/Magento/FunctionalTestingFramework/DataGenerator/Persist/CurlHandler.php @@ -6,6 +6,7 @@ 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\Handlers\OperationDefinitionObjectHandler; use Magento\FunctionalTestingFramework\DataGenerator\Objects\EntityDataObject; @@ -74,6 +75,7 @@ class CurlHandler /** * ApiSubObject constructor. + * * @param string $operation * @param EntityDataObject $entityObject * @param string $storeCode @@ -104,7 +106,14 @@ public function executeRequest($dependentEntities) $successRegex = null; $returnRegex = null; - $apiUrl = $this->resolveUrlReference($this->operationDefinition->getApiUrl()); + if ($this->operation == 'update') { + $entities = array_merge($dependentEntities, [$this->entityObject]); + } elseif ((null !== $dependentEntities) && is_array($dependentEntities)) { + $entities = array_merge([$this->entityObject], $dependentEntities); + } else { + $entities = [$this->entityObject]; + } + $apiUrl = $this->resolveUrlReference($this->operationDefinition->getApiUrl(), $entities); $headers = $this->operationDefinition->getHeaders(); $authorization = $this->operationDefinition->getAuth(); $contentType = $this->operationDefinition->getContentType(); @@ -123,8 +132,11 @@ public function executeRequest($dependentEntities) $executor = new WebapiExecutor($this->storeCode); } elseif ($authorization === 'adminFormKey') { $executor = new AdminExecutor(); - //TODO: add frontend request executor - //} elseif ($authorization === 'customFromKey') { + } elseif ($authorization === 'customerFormKey') { + $executor = new FrontendExecutor( + $this->requestData['customer_email'], + $this->requestData['customer_password'] + ); } if (!$executor) { @@ -171,12 +183,13 @@ public function isContentTypeJson() } /** - * Resolve rul reference from entity data. + * Resolve rul reference from entity objects. * * @param string $urlIn + * @param array $entityObjects * @return string */ - private function resolveUrlReference($urlIn) + private function resolveUrlReference($urlIn, $entityObjects) { $urlOut = $urlIn; $matchedParams = []; @@ -184,11 +197,16 @@ private function resolveUrlReference($urlIn) if (!empty($matchedParams)) { foreach ($matchedParams[0] as $paramKey => $paramValue) { - $param = $this->entityObject->getDataByName( - $matchedParams[1][$paramKey], - EntityDataObject::CEST_UNIQUE_VALUE - ); - $urlOut = str_replace($paramValue, $param, $urlIn); + foreach ($entityObjects as $entityObject) { + $param = $entityObject->getDataByName( + $matchedParams[1][$paramKey], + EntityDataObject::CEST_UNIQUE_VALUE + ); + if (null !== $param) { + $urlOut = str_replace($paramValue, $param, $urlIn); + continue; + } + } } } return $urlOut; diff --git a/src/Magento/FunctionalTestingFramework/DataGenerator/Persist/DataPersistenceHandler.php b/src/Magento/FunctionalTestingFramework/DataGenerator/Persist/DataPersistenceHandler.php index 7c80765b7..fe1b1e037 100644 --- a/src/Magento/FunctionalTestingFramework/DataGenerator/Persist/DataPersistenceHandler.php +++ b/src/Magento/FunctionalTestingFramework/DataGenerator/Persist/DataPersistenceHandler.php @@ -69,8 +69,54 @@ public function createEntity($storeCode = null) } $curlHandler = new CurlHandler('create', $this->entityObject, $this->storeCode); $result = $curlHandler->executeRequest($this->dependentObjects); - $this->setCreatedEntity( + $this->setCreatedObject( $result, + null, + $curlHandler->getRequestDataArray(), + $curlHandler->isContentTypeJson() + ); + } + + /** + * Function which executes a put request based on specific operation metadata. + * + * @param string $storeCode + * @return void + */ + + public function updateEntity($storeCode = null) + { + if (!empty($storeCode)) { + $this->storeCode = $storeCode; + } + $curlHandler = new CurlHandler('update', $this->entityObject, $this->storeCode); + $result = $curlHandler->executeRequest($this->dependentObjects); + $this->setCreatedObject( + $result, + null, + $curlHandler->getRequestDataArray(), + $curlHandler->isContentTypeJson() + ); + } + + /** + * Function which executes a get request on specific operation metadata. + * + * @param integer|null $index + * @param string $storeCode + * @return void + */ + + public function getEntity($index = null, $storeCode = null) + { + if (!empty($storeCode)) { + $this->storeCode = $storeCode; + } + $curlHandler = new CurlHandler('get', $this->entityObject, $this->storeCode); + $result = $curlHandler->executeRequest($this->dependentObjects); + $this->setCreatedObject( + $result, + $index, $curlHandler->getRequestDataArray(), $curlHandler->isContentTypeJson() ); @@ -92,7 +138,8 @@ public function deleteEntity($storeCode = null) } /** - * Returns the createdDataObject, instantiated when the entity is created via API. + * Returns the created data object, instantiated when the entity is created via API. + * * @return EntityDataObject */ public function getCreatedObject() @@ -110,24 +157,27 @@ public function getCreatedDataByName($dataName) return $this->createdObject->getDataByName($dataName, EntityDataObject::NO_UNIQUE_PROCESS); } - // TODO add update function - /* public function updateEntity() - { - - }*/ - /** - * Save created entity. + * Save the created data object. * * @param string|array $response + * @param integer|null $index * @param array $requestDataArray * @param bool $isJson * @return void */ - private function setCreatedEntity($response, $requestDataArray, $isJson) + private function setCreatedObject($response, $index, $requestDataArray, $isJson) { if ($isJson) { - $persistedData = array_merge($requestDataArray, json_decode($response, true)); + $responseData = json_decode($response, true); + if (is_array($responseData) && (null !== $index)) { + $responseData = $responseData[$index]; + } + if (is_array($responseData)) { + $persistedData = array_merge($requestDataArray, $responseData); + } else { + $persistedData = $requestDataArray; + } } else { $persistedData = array_merge( $this->convertToFlatArray($requestDataArray), diff --git a/src/Magento/FunctionalTestingFramework/DataGenerator/Persist/OperationDataArrayResolver.php b/src/Magento/FunctionalTestingFramework/DataGenerator/Persist/OperationDataArrayResolver.php index 49cb535a8..852862640 100644 --- a/src/Magento/FunctionalTestingFramework/DataGenerator/Persist/OperationDataArrayResolver.php +++ b/src/Magento/FunctionalTestingFramework/DataGenerator/Persist/OperationDataArrayResolver.php @@ -45,7 +45,6 @@ class OperationDataArrayResolver */ public function __construct($dependentEntities = null) { - //empty constructor if ($dependentEntities !== null) { foreach ($dependentEntities as $entity) { $this->dependentEntities[$entity->getName()] = $entity; @@ -73,7 +72,7 @@ public function resolveOperationDataArray($entityObject, $operationMetadata, $op foreach ($operationMetadata as $operationElement) { if ($operationElement->getType() == OperationElementExtractor::OPERATION_OBJECT_OBJ_NAME) { $entityObj = $this->resolveOperationObjectAndEntityData($entityObject, $operationElement->getValue()); - $operationDataArray[$operationElement->getValue()] = + $operationDataArray[$operationElement->getKey()] = $this->resolveOperationDataArray($entityObj, $operationElement->getNestedMetadata(), $operation); continue; } diff --git a/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataOperation.xsd b/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataOperation.xsd index eadc0e396..7126fa67e 100644 --- a/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataOperation.xsd +++ b/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataOperation.xsd @@ -1,4 +1,11 @@ <?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + <xs:schema attributeFormDefault="unqualified" elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema"> <xs:element name="config"> <xs:complexType> @@ -74,6 +81,7 @@ <xs:enumeration value="create" /> <xs:enumeration value="delete" /> <xs:enumeration value="update" /> + <xs:enumeration value="get" /> </xs:restriction> </xs:simpleType> <xs:simpleType name="authEnum" final="restriction" > diff --git a/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd b/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd index c0494c911..f42a11c40 100644 --- a/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd +++ b/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd @@ -1,4 +1,11 @@ <?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + <xs:schema attributeFormDefault="unqualified" elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema"> diff --git a/src/Magento/FunctionalTestingFramework/Group/placeholder.txt b/src/Magento/FunctionalTestingFramework/Group/placeholder.txt new file mode 100644 index 000000000..e55d26bb4 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Group/placeholder.txt @@ -0,0 +1,2 @@ +This directory is used for group object files. The framework will generate files to this directory for support of +before and after logic run once (before and after) a suite of tests. \ No newline at end of file diff --git a/src/Magento/FunctionalTestingFramework/Module/MagentoRestDriver.php b/src/Magento/FunctionalTestingFramework/Module/MagentoRestDriver.php index bae3c50f7..790626629 100644 --- a/src/Magento/FunctionalTestingFramework/Module/MagentoRestDriver.php +++ b/src/Magento/FunctionalTestingFramework/Module/MagentoRestDriver.php @@ -1,4 +1,9 @@ <?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + namespace Magento\FunctionalTestingFramework\Module; use Codeception\Module\REST; diff --git a/src/Magento/FunctionalTestingFramework/Module/MagentoSequence.php b/src/Magento/FunctionalTestingFramework/Module/MagentoSequence.php index 52a995be1..6689e6b45 100644 --- a/src/Magento/FunctionalTestingFramework/Module/MagentoSequence.php +++ b/src/Magento/FunctionalTestingFramework/Module/MagentoSequence.php @@ -1,4 +1,9 @@ <?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + namespace Magento\FunctionalTestingFramework\Module; use Codeception\Module\Sequence; diff --git a/src/Magento/FunctionalTestingFramework/Module/MagentoWebDriver.php b/src/Magento/FunctionalTestingFramework/Module/MagentoWebDriver.php index 2bca979ca..470db0c50 100644 --- a/src/Magento/FunctionalTestingFramework/Module/MagentoWebDriver.php +++ b/src/Magento/FunctionalTestingFramework/Module/MagentoWebDriver.php @@ -1,4 +1,9 @@ <?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + namespace Magento\FunctionalTestingFramework\Module; use Codeception\Module\WebDriver; diff --git a/src/Magento/FunctionalTestingFramework/ObjectManager/ObjectHandlerInterface.php b/src/Magento/FunctionalTestingFramework/ObjectManager/ObjectHandlerInterface.php index 1c2e76b2d..1bc79cbbb 100644 --- a/src/Magento/FunctionalTestingFramework/ObjectManager/ObjectHandlerInterface.php +++ b/src/Magento/FunctionalTestingFramework/ObjectManager/ObjectHandlerInterface.php @@ -21,10 +21,10 @@ public static function getInstance(); /** * Function to return a single object by name * - * @param string $jsonDefitionName + * @param string $objectName * @return object */ - public function getObject($jsonDefitionName); + public function getObject($objectName); /** * Function to return all objects the handler is responsible for diff --git a/src/Magento/FunctionalTestingFramework/Page/Handlers/PageObjectHandler.php b/src/Magento/FunctionalTestingFramework/Page/Handlers/PageObjectHandler.php index 13273a34a..b13ae5c7b 100644 --- a/src/Magento/FunctionalTestingFramework/Page/Handlers/PageObjectHandler.php +++ b/src/Magento/FunctionalTestingFramework/Page/Handlers/PageObjectHandler.php @@ -94,7 +94,14 @@ private function initPageObjects() $objectManager = ObjectManagerFactory::getObjectManager(); /** @var $parser \Magento\FunctionalTestingFramework\XmlParser\PageParser */ $parser = $objectManager->get(PageParser::class); - foreach ($parser->getData(self::TYPE) as $pageName => $pageData) { + $parsedObjs = $parser->getData(self::TYPE); + + if (!$parsedObjs) { + trigger_error("No " . self::TYPE . " objects defined", E_USER_NOTICE); + return; + } + + foreach ($parsedObjs as $pageName => $pageData) { $urlPath = $pageData[PageObjectHandler::URL_PATH_ATTR]; $module = $pageData[PageObjectHandler::MODULE_ATTR]; $sections = array_keys($pageData[PageObjectHandler::SUB_TYPE]); diff --git a/src/Magento/FunctionalTestingFramework/Page/Handlers/SectionObjectHandler.php b/src/Magento/FunctionalTestingFramework/Page/Handlers/SectionObjectHandler.php index 65edaeab2..8bd5863a4 100644 --- a/src/Magento/FunctionalTestingFramework/Page/Handlers/SectionObjectHandler.php +++ b/src/Magento/FunctionalTestingFramework/Page/Handlers/SectionObjectHandler.php @@ -97,7 +97,14 @@ private function initSectionObjects() $objectManager = ObjectManagerFactory::getObjectManager(); /** @var $parser \Magento\FunctionalTestingFramework\XmlParser\SectionParser */ $parser = $objectManager->get(SectionParser::class); - foreach ($parser->getData(self::TYPE) as $sectionName => $sectionData) { + $parsedObjs = $parser->getData(self::TYPE); + + if (!$parsedObjs) { + trigger_error("No " . self::TYPE . " objects defined", E_USER_NOTICE); + return; + } + + foreach ($parsedObjs as $sectionName => $sectionData) { // create elements $elements = []; foreach ($sectionData[SectionObjectHandler::SUB_TYPE] as $elementName => $elementData) { diff --git a/src/Magento/FunctionalTestingFramework/Page/etc/PageObject.xsd b/src/Magento/FunctionalTestingFramework/Page/etc/PageObject.xsd index aca2a5ee9..631b40531 100644 --- a/src/Magento/FunctionalTestingFramework/Page/etc/PageObject.xsd +++ b/src/Magento/FunctionalTestingFramework/Page/etc/PageObject.xsd @@ -1,4 +1,10 @@ <?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> <xs:annotation> diff --git a/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd b/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd index e0c421dcb..4fdeba173 100644 --- a/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd +++ b/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd @@ -1,4 +1,10 @@ <?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> <xs:element name="config"> diff --git a/src/Magento/FunctionalTestingFramework/Step/Backend/AdminStep.php b/src/Magento/FunctionalTestingFramework/Step/Backend/AdminStep.php index 8284de253..f68ad1074 100644 --- a/src/Magento/FunctionalTestingFramework/Step/Backend/AdminStep.php +++ b/src/Magento/FunctionalTestingFramework/Step/Backend/AdminStep.php @@ -1,4 +1,9 @@ <?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + namespace Magento\FunctionalTestingFramework\Step\Backend; require_once __DIR__ . '/../../Helper/AdminUrlList.php'; diff --git a/src/Magento/FunctionalTestingFramework/Suite/Generators/GroupClassGenerator.php b/src/Magento/FunctionalTestingFramework/Suite/Generators/GroupClassGenerator.php new file mode 100644 index 000000000..043c267c2 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Suite/Generators/GroupClassGenerator.php @@ -0,0 +1,150 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\FunctionalTestingFramework\Suite\Generators; + +use Magento\FunctionalTestingFramework\Suite\Objects\SuiteObject; +use Magento\FunctionalTestingFramework\Test\Objects\ActionObject; +use Magento\FunctionalTestingFramework\Test\Objects\CestHookObject; +use Magento\FunctionalTestingFramework\Test\Util\ActionObjectExtractor; +use Magento\FunctionalTestingFramework\Util\TestGenerator; +use Mustache_Engine; +use Mustache_Loader_FilesystemLoader; + +class GroupClassGenerator +{ + const MUSTACHE_TEMPLATE_NAME = 'SuiteClass'; + const SUITE_NAME_TAG = 'suiteName'; + const TEST_COUNT_TAG = 'testCount'; + const BEFORE_MUSTACHE_KEY = 'before'; + const AFTER_MUSTACHE_KEY = 'after'; + const ENTITY_NAME_TAG = 'entityName'; + const ENTITY_MERGE_KEY = 'mergeKey'; + const REQUIRED_ENTITY_KEY = 'requiredEntities'; + const LAST_REQUIRED_ENTITY_TAG = 'last'; + const MUSTACHE_VAR_TAG = 'var'; + + const GROUP_DIR_NAME = 'Group'; + + /** + * Mustache_Engine instance for template loading + * + * @var Mustache_Engine + */ + private $mustacheEngine; + + /** + * GroupClassGenerator constructor + */ + public function __construct() + { + $this->mustacheEngine = new Mustache_Engine([ + 'loader' => new Mustache_Loader_FilesystemLoader(dirname(__DIR__) . DIRECTORY_SEPARATOR . "views"), + 'partials_loader' => new Mustache_Loader_FilesystemLoader( + dirname(__DIR__) . DIRECTORY_SEPARATOR . "views" . DIRECTORY_SEPARATOR . "partials" + ) + ]); + } + + /** + * Method for adding preconditions and creating a corresponding group file for codeception. After generation, + * the method returns the config path for the group file. + * + * @param SuiteObject $suiteObject + * @return string + */ + public function generateGroupClass($suiteObject) + { + $classContent = $this->createClassContent($suiteObject); + $configEntry = self::GROUP_DIR_NAME . DIRECTORY_SEPARATOR . $suiteObject->getName(); + $filePath = dirname(dirname(__DIR__)) . + DIRECTORY_SEPARATOR . + $configEntry . + '.php'; + file_put_contents($filePath, $classContent); + + return str_replace(DIRECTORY_SEPARATOR, "\\", $configEntry); + } + + /** + * Function to create group class content based on suite object definition. + * + * @param SuiteObject $suiteObject + * @return string; + */ + private function createClassContent($suiteObject) + { + $mustacheData = []; + $mustacheData[self::SUITE_NAME_TAG] = $suiteObject->getName(); + $mustacheData[self::TEST_COUNT_TAG] = count($suiteObject->getCests()); + + $mustacheData[self::BEFORE_MUSTACHE_KEY] = $this->buildHookMustacheArray($suiteObject->getBeforeHook()); + $mustacheData[self::AFTER_MUSTACHE_KEY] = $this->buildHookMustacheArray($suiteObject->getAfterHook()); + $mustacheData[self::MUSTACHE_VAR_TAG] = array_merge( + $mustacheData[self::BEFORE_MUSTACHE_KEY]['createData'] ?? [], + $mustacheData[self::AFTER_MUSTACHE_KEY]['createData'] ?? [] + ); + + return $this->mustacheEngine->render(self::MUSTACHE_TEMPLATE_NAME, $mustacheData); + } + + /** + * Function which takes hook objects and transforms data into array for mustache template engine. + * + * @param CestHookObject $hookObj + * @return array + */ + private function buildHookMustacheArray($hookObj) + { + $mustacheHookArray = []; + foreach ($hookObj->getActions() as $action) { + /** @var ActionObject $action */ + $entityArray = []; + $entityArray[self::ENTITY_MERGE_KEY] = $action->getMergeKey(); + $entityArray[self::ENTITY_NAME_TAG] = + $action->getCustomActionAttributes()['entity'] ?? + $action->getCustomActionAttributes()[TestGenerator::REQUIRED_ENTITY_REFERENCE]; + + // if there is more than 1 custom attribute, we can assume there are required entities + if (count($action->getCustomActionAttributes()) > 1) { + $entityArray[self::REQUIRED_ENTITY_KEY] = + $this->buildReqEntitiesMustacheArray($action->getCustomActionAttributes()); + } + + $mustacheHookArray[$action->getType()][] = $entityArray; + } + + return $mustacheHookArray; + } + + /** + * Function which takes any required entities under a 'createData' tag and transforms data into array to be consumed + * by mustache template. + * (<createData entity="" mergeKey=""> + * <requiredEntity'...) + * + * @param array $customAttributes + * @return array + */ + private function buildReqEntitiesMustacheArray($customAttributes) + { + $requiredEntities = []; + foreach ($customAttributes as $attribute) { + if (!is_array($attribute)) { + continue; + } + + if ($attribute[ActionObjectExtractor::NODE_NAME] == 'required-entity') { + $requiredEntities[] = [self::ENTITY_NAME_TAG => $attribute[TestGenerator::REQUIRED_ENTITY_REFERENCE]]; + } + } + + //append "last" attribute to final entry for mustache template (omit trailing comma) + $requiredEntities[count($requiredEntities)-1][self::LAST_REQUIRED_ENTITY_TAG] = true; + + return $requiredEntities; + } +} diff --git a/src/Magento/FunctionalTestingFramework/Suite/Handlers/SuiteObjectHandler.php b/src/Magento/FunctionalTestingFramework/Suite/Handlers/SuiteObjectHandler.php new file mode 100644 index 000000000..806ef44fc --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Suite/Handlers/SuiteObjectHandler.php @@ -0,0 +1,97 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\FunctionalTestingFramework\Suite\Handlers; + +use Exception; +use Magento\FunctionalTestingFramework\ObjectManager\ObjectHandlerInterface; +use Magento\FunctionalTestingFramework\ObjectManagerFactory; +use Magento\FunctionalTestingFramework\Suite\Objects\SuiteObject; +use Magento\FunctionalTestingFramework\Suite\Parsers\SuiteDataParser; +use Magento\FunctionalTestingFramework\Suite\Util\SuiteObjectExtractor; +use Magento\FunctionalTestingFramework\Test\Handlers\CestObjectHandler; +use Magento\FunctionalTestingFramework\Test\Objects\CestObject; +use Magento\FunctionalTestingFramework\Test\Util\CestHookObjectExtractor; +use Magento\FunctionalTestingFramework\Test\Util\CestObjectExtractor; +use Magento\Ui\Test\Unit\Component\PagingTest; + +/** + * Class SuiteObjectHandler + */ +class SuiteObjectHandler implements ObjectHandlerInterface +{ + /** + * Singleton instance of suite object handler. + * + * @var SuiteObjectHandler + */ + private static $SUITE_OBJECT_HANLDER_INSTANCE; + + /** + * Array of suite objects keyed by suite name. + * + * @var array + */ + private $suiteObjects; + + /** + * SuiteObjectHandler constructor. + */ + private function __construct() + { + // empty constructor + } + + /** + * Function to enforce singleton design pattern + * + * @return ObjectHandlerInterface + */ + public static function getInstance() + { + if (self::$SUITE_OBJECT_HANLDER_INSTANCE == null) { + self::$SUITE_OBJECT_HANLDER_INSTANCE = new SuiteObjectHandler(); + self::$SUITE_OBJECT_HANLDER_INSTANCE->initSuiteData(); + } + + return self::$SUITE_OBJECT_HANLDER_INSTANCE; + } + + /** + * Function to return a single suite object by name + * + * @param string $objectName + * @return SuiteObject + */ + public function getObject($objectName) + { + if (!array_key_exists($objectName, $this->suiteObjects)) { + trigger_error("Suite ${objectName} is not defined.", E_USER_ERROR); + } + return $this->suiteObjects[$objectName]; + } + + /** + * Function to return all objects the handler is responsible for + * + * @return array + */ + public function getAllObjects() + { + return $this->suiteObjects; + } + + /** + * Method to parse all suite data xml into objects. + * + * @return void + */ + private function initSuiteData() + { + $suiteDataParser = ObjectManagerFactory::getObjectManager()->create(SuiteDataParser::class); + $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 new file mode 100644 index 000000000..44dd64937 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Suite/Objects/SuiteObject.php @@ -0,0 +1,172 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\FunctionalTestingFramework\Suite\Objects; + +use Magento\FunctionalTestingFramework\Test\Objects\CestHookObject; +use Magento\FunctionalTestingFramework\Test\Objects\CestObject; + +/** + * Class SuiteObject + */ +class SuiteObject +{ + /** + * Name of the Suite. + * + * @var string + */ + private $name; + + /** + * Array of Cests to include for the suite. + * + * @var array + */ + private $includeCests = []; + + /** + * Array of Cests to exclude for the suite. + * + * @var array + */ + private $excludeCests = []; + + /** + * Array of before/after hooks to be executed for a suite. + * + * @var array + */ + private $hooks; + + /** + * SuiteObject constructor. + * @param string $name + * @param array $includeCests + * @param array $excludeCests + * @param array $hooks + */ + public function __construct($name, $includeCests, $excludeCests, $hooks) + { + $this->name = $name; + $this->includeCests = $includeCests; + $this->excludeCests = $excludeCests; + $this->hooks = $hooks; + } + + /** + * Getter for suite name. + * + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * Returns an array of Cest Objects based on specifications in exclude and include arrays. + * + * @return array + */ + public function getCests() + { + return $this->resolveCests($this->includeCests, $this->excludeCests); + } + + /** + * Takes an array of Cest Objects to include and an array of Cest Objects to exlucde. Loops through each Cest + * and determines any overlapping tests. Returns a resulting array of Cest Objects based on this logic. Exclusion is + * preferred to exclusiong (i.e. a test is specified in both include and exclude, it will be excluded). + * + * @param array $includeCests + * @param array $excludeCests + * @return array + */ + private function resolveCests($includeCests, $excludeCests) + { + $finalCestList = $includeCests; + $matchingCests = array_intersect(array_keys($includeCests), array_keys($excludeCests)); + + // filter out tests within cests here and any overlap + foreach ($matchingCests as $cestName) { + $testNamesForExclusion = array_keys($excludeCests[$cestName]->getTests()); + $relevantTestNames = array_diff(array_keys($includeCests[$cestName]->getTests()), $testNamesForExclusion); + + if (empty($relevantTestNames)) { + unset($finalCestList[$cestName]); + continue; + } + + /** @var CestObject $tempCestObj */ + $tempCestObj = $includeCests[$cestName]; + $finalCestList[$tempCestObj->getName()] = new CestObject( + $tempCestObj->getName(), + $tempCestObj->getAnnotations(), + $this->resolveTests($relevantTestNames, $includeCests[$cestName]->getTests()), + $tempCestObj->getHooks() + ); + } + + if (empty($finalCestList)) { + trigger_error( + "Current suite configuration for " . + $this->name . " contains no cests.", + E_USER_WARNING + ); + } + + return $finalCestList; + } + + /** + * Takes an array of test names to be extracted from an array of test objects. Returns the resulting array of test + * objects contained within the applicable test names. + * + * @param array $relevantTestNames + * @param array $tests + * @return array + */ + private function resolveTests($relevantTestNames, $tests) + { + $relevantTests = []; + foreach ($relevantTestNames as $testName) { + $relevantTests[$testName] = $tests[$testName]; + } + + return $relevantTests; + } + + /** + * Convenience method for determining if a Suite will require group file generation. + * A group file will only be generated when the user specifies a before/after statement. + * + * @return bool + */ + public function requiresGroupFile() + { + return !empty($this->hooks); + } + + /** + * Getter for before hooks. + * + * @return CestHookObject + */ + public function getBeforeHook() + { + return $this->hooks['before']; + } + + /** + * Getter for after hooks. + * + * @return CestHookObject + */ + public function getAfterHook() + { + return $this->hooks['after']; + } +} diff --git a/src/Magento/FunctionalTestingFramework/Suite/Parsers/SuiteDataParser.php b/src/Magento/FunctionalTestingFramework/Suite/Parsers/SuiteDataParser.php new file mode 100644 index 000000000..2fb2031dc --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Suite/Parsers/SuiteDataParser.php @@ -0,0 +1,38 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\FunctionalTestingFramework\Suite\Parsers; + +use Magento\FunctionalTestingFramework\Config\DataInterface; + +class SuiteDataParser +{ + /** + * Suite data interface for parser. + * + * @var DataInterface + */ + private $suiteData; + + /** + * TestDataParser constructor. + * + * @param DataInterface $suiteData + */ + public function __construct(DataInterface $suiteData) + { + $this->suiteData = $suiteData; + } + + /** + * Returns an array of data based on *Cest.xml files + * + * @return array + */ + public function readSuiteData() + { + return $this->suiteData->get(); + } +} diff --git a/src/Magento/FunctionalTestingFramework/Suite/SuiteGenerator.php b/src/Magento/FunctionalTestingFramework/Suite/SuiteGenerator.php new file mode 100644 index 000000000..2b6af5c5b --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Suite/SuiteGenerator.php @@ -0,0 +1,144 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\FunctionalTestingFramework\Suite; + +use Magento\Framework\Phrase; +use Magento\Framework\Validator\Exception; +use Magento\FunctionalTestingFramework\Suite\Generators\GroupClassGenerator; +use Magento\FunctionalTestingFramework\Suite\Handlers\SuiteObjectHandler; +use Magento\FunctionalTestingFramework\Suite\Objects\SuiteObject; +use Magento\FunctionalTestingFramework\Util\Filesystem\DirSetupUtil; +use Magento\FunctionalTestingFramework\Util\TestGenerator; +use Symfony\Component\Yaml\Yaml; + +class SuiteGenerator +{ + const YAML_CODECEPTION_DIST_FILENAME = 'codeception.dist.yml'; + const YAML_CODECEPTION_CONFIG_FILENAME = 'codeception.yml'; + const YAML_GROUPS_TAG = 'groups'; + const YAML_EXTENSIONS_TAG = 'extensions'; + const YAML_ENABLED_TAG = 'enabled'; + const YAML_COPYRIGHT_TEXT = + "# Copyright © Magento, Inc. All rights reserved.\n# See COPYING.txt for license details.\n"; + + /** + * Singelton Variable Instance. + * + * @var SuiteGenerator + */ + private static $SUITE_GENERATOR_INSTANCE; + + /** + * Group Class Generator initialized in constructor. + * + * @var GroupClassGenerator + */ + private $groupClassGenerator; + + /** + * SuiteGenerator constructor. + */ + private function __construct() + { + $this->groupClassGenerator = new GroupClassGenerator(); + } + + /** + * Singleton method which is used to retrieve the instance of the suite generator. + * + * @return SuiteGenerator + */ + public static function getInstance() + { + if (!self::$SUITE_GENERATOR_INSTANCE) { + self::$SUITE_GENERATOR_INSTANCE = new SuiteGenerator(); + } + + return self::$SUITE_GENERATOR_INSTANCE; + } + + /** + * Function which takes a suite name and generates corresponding dir, cest files, group class, and updates + * yml configuration for group run. + * + * @param string $suiteName + * @return void + */ + public function generateSuite($suiteName) + { + /**@var SuiteObject $suite **/ + $suite = SuiteObjectHandler::getInstance()->getObject($suiteName); + $relativePath = TestGenerator::GENERATED_DIR . DIRECTORY_SEPARATOR . $suiteName; + $fullPath = TESTS_MODULE_PATH . DIRECTORY_SEPARATOR . $relativePath; + $groupNamespace = null; + + DirSetupUtil::createGroupDir($fullPath); + $this->generateRelevantGroupTests($suiteName, $suite->getCests()); + + if ($suite->requiresGroupFile()) { + // if the suite requires a group file, generate it and set the namespace + $groupNamespace = $this->groupClassGenerator->generateGroupClass($suite); + } + + $this->appendEntriesToConfig($suiteName, $fullPath, $groupNamespace); + print "Suite ${suiteName} generated to ${relativePath}.\n"; + } + + /** + * 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 $groupNamespace + * @return void + */ + private function appendEntriesToConfig($suiteName, $suitePath, $groupNamespace) + { + $configYmlPath = dirname(dirname(TESTS_BP)) . DIRECTORY_SEPARATOR; + $configYmlFile = $configYmlPath . self::YAML_CODECEPTION_CONFIG_FILENAME; + $defaultConfigYmlFile = $configYmlPath . self::YAML_CODECEPTION_DIST_FILENAME; + $relativeSuitePath = substr($suitePath, strlen(dirname(dirname(TESTS_BP))) + 1); + + $ymlContents = null; + if (file_exists($configYmlFile)) { + $ymlContents = file_get_contents($configYmlFile); + } else { + $ymlContents = file_get_contents($defaultConfigYmlFile); + } + + $ymlArray = Yaml::parse($ymlContents) ?? []; + if (!array_key_exists(self::YAML_GROUPS_TAG, $ymlArray)) { + $ymlArray[self::YAML_GROUPS_TAG]= []; + } + + $ymlArray[self::YAML_GROUPS_TAG][$suiteName] = [$relativeSuitePath]; + + if ($groupNamespace) { + $ymlArray[self::YAML_EXTENSIONS_TAG][self::YAML_ENABLED_TAG][] = $groupNamespace; + } + + $ymlText = self::YAML_COPYRIGHT_TEXT . Yaml::dump($ymlArray, 10); + file_put_contents($configYmlFile, $ymlText); + } + + /** + * Function which takes a string which is the desired output directory (under _generated) and an array of cests + * 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 cest files for the suite. + * + * @param string $path + * @param array $cests + * @return void + */ + private function generateRelevantGroupTests($path, $cests) + { + $testGenerator = TestGenerator::getInstance($path, $cests); + $testGenerator->createAllCestFiles(); + } +} diff --git a/src/Magento/FunctionalTestingFramework/Suite/Util/SuiteObjectExtractor.php b/src/Magento/FunctionalTestingFramework/Suite/Util/SuiteObjectExtractor.php new file mode 100644 index 000000000..04135c867 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Suite/Util/SuiteObjectExtractor.php @@ -0,0 +1,237 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\FunctionalTestingFramework\Suite\Util; + +use Exception; +use Magento\FunctionalTestingFramework\Suite\Objects\SuiteObject; +use Magento\FunctionalTestingFramework\Test\Handlers\CestObjectHandler; +use Magento\FunctionalTestingFramework\Test\Objects\CestObject; +use Magento\FunctionalTestingFramework\Test\Util\BaseCestObjectExtractor; +use Magento\FunctionalTestingFramework\Test\Util\CestHookObjectExtractor; +use Magento\FunctionalTestingFramework\Test\Util\CestObjectExtractor; + +class SuiteObjectExtractor extends BaseCestObjectExtractor +{ + const SUITE_ROOT_TAG = 'suites'; + const SUITE_TAG_NAME = 'suite'; + const INCLUDE_TAG_NAME = 'include'; + const EXCLUDE_TAG_NAME = 'exclude'; + const MODULE_TAG_NAME = 'module'; + const MODULE_TAG_FILE_ATTRIBUTE = 'file'; + const CEST_TAG_NAME = 'cest'; + const CEST_TAG_NAME_ATTRIBUTE = 'name'; + const CEST_TAG_TEST_ATTRIBUTE = 'test'; + const GROUP_TAG_NAME = 'group'; + + /** + * SuiteObjectExtractor constructor + */ + public function __construct() + { + // empty constructor + } + + /** + * Takes an array of parsed xml and converts into an array of suite objects. + * + * @param array $parsedSuiteData + * @return array + */ + public function parseSuiteDataIntoObjects($parsedSuiteData) + { + $suiteObjects = []; + $cestObjectHookExtractor = new CestHookObjectExtractor(); + foreach ($parsedSuiteData[self::SUITE_ROOT_TAG] as $parsedSuite) { + if (!is_array($parsedSuite)) { + // skip non array items parsed from suite (suite objects will always be arrays) + continue; + } + + $suiteHooks = []; + + //extract include and exclude references + $groupCestsToInclude = $parsedSuite[self::INCLUDE_TAG_NAME] ?? []; + $groupCestsToExclude = $parsedSuite[self::EXCLUDE_TAG_NAME] ?? []; + + // resolve references as cest objects + $includeCests = $this->extractCestObjectsFromSuiteRef($groupCestsToInclude); + $excludeCests = $this->extractCestObjectsFromSuiteRef($groupCestsToExclude); + + // add all cests if include cests is completely empty + if (empty($includeCests)) { + $includeCests = CestObjectHandler::getInstance()->getAllObjects(); + } + + // parse any object hooks + if (array_key_exists(CestObjectExtractor::CEST_BEFORE_HOOK, $parsedSuite)) { + $suiteHooks[CestObjectExtractor::CEST_BEFORE_HOOK] = $cestObjectHookExtractor->extractHook( + CestObjectExtractor::CEST_BEFORE_HOOK, + $parsedSuite[CestObjectExtractor::CEST_BEFORE_HOOK] + ); + } + if (array_key_exists(CestObjectExtractor::CEST_AFTER_HOOK, $parsedSuite)) { + $suiteHooks[CestObjectExtractor::CEST_AFTER_HOOK] = $cestObjectHookExtractor->extractHook( + CestObjectExtractor::CEST_AFTER_HOOK, + $parsedSuite[CestObjectExtractor::CEST_AFTER_HOOK] + ); + } + + // create the new suite object + $suiteObjects[$parsedSuite[self::NAME]] = new SuiteObject( + $parsedSuite[self::NAME], + $includeCests, + $excludeCests, + $suiteHooks + ); + } + + return $suiteObjects; + } + + /** + * 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 + */ + private function extractCestObjectsFromSuiteRef($suiteReferences) + { + $cestObjectList = []; + foreach ($suiteReferences as $suiteRefName => $suiteRefData) { + if (!is_array($suiteRefData)) { + continue; + } + + switch ($suiteRefData[self::NODE_NAME]) { + case self::CEST_TAG_NAME: + $extractedCest = $this->extractRelevantCestObject($suiteRefData); + $cestObjectList[$extractedCest->getName()] = $extractedCest; + break; + case self::GROUP_TAG_NAME: + $cestObjectList = $cestObjectList + + CestObjectHandler::getInstance()->getCestsByGroup($suiteRefData[self::NAME]); + break; + case self::MODULE_TAG_NAME: + $cestObjectList = array_merge($cestObjectList, $this->extractModuleAndFiles( + $suiteRefData[self::NAME], + $suiteRefData[self::MODULE_TAG_FILE_ATTRIBUTE ?? null] + )); + break; + } + } + + return $cestObjectList; + } + + /** + * Takes an array of Cests/Tests and resolves names into corresponding Cest/Test objects. + * + * @param array $suiteTestData + * @return CestObject|null + */ + private function extractRelevantCestObject($suiteTestData) + { + $relevantCest = CestObjectHandler::getInstance()->getObject($suiteTestData[self::CEST_TAG_NAME_ATTRIBUTE]); + + if (array_key_exists(self::CEST_TAG_TEST_ATTRIBUTE, $suiteTestData)) { + $relevantTest = $relevantCest->getTests()[$suiteTestData[self::CEST_TAG_TEST_ATTRIBUTE]] ?? null; + + if (!$relevantTest) { + trigger_error( + "Test " . + $suiteTestData[self::CEST_TAG_NAME_ATTRIBUTE] . + " does not exist.", + E_USER_NOTICE + ); + return null; + } + + return new CestObject( + $relevantCest->getName(), + $relevantCest->getAnnotations(), + [$relevantTest->getName() => $relevantTest], + $relevantCest->getHooks() + ); + } + + return $relevantCest; + } + + /** + * Takes an array of modules/files and resolves to an array of cest objects. + * + * @param string $moduleName + * @param string $moduleFilePath + * @return array + */ + private function extractModuleAndFiles($moduleName, $moduleFilePath) + { + if (empty($moduleFilePath)) { + return $this->resolveModulePathCestNames($moduleName); + } + + $cestObj = $this->resolveFilePathCestName($moduleFilePath, $moduleName); + return [$cestObj->getName() => $cestObj]; + } + + /** + * Takes a filepath (and optionally a module name) and resolves to a cest object. + * + * @param string $filename + * @param null $moduleName + * @return CestObject + * @throws Exception + */ + private function resolveFilePathCestName($filename, $moduleName = null) + { + $filepath = $filename; + if (!strstr($filepath, DIRECTORY_SEPARATOR)) { + $filepath = TESTS_MODULE_PATH . + DIRECTORY_SEPARATOR . + $moduleName . + DIRECTORY_SEPARATOR . + 'Cest' . + DIRECTORY_SEPARATOR . + $filename; + } + + if (!file_exists($filepath)) { + throw new Exception("Could not find file ${filename}"); + } + $xml = simplexml_load_file($filepath); + $cestName = (string)$xml->cest->attributes()->name; + + return CestObjectHandler::getInstance()->getObject($cestName); + } + + /** + * Takes a single module name and resolves to an array of cests contained within specified module. + * + * @param string $moduleName + * @return array + */ + private function resolveModulePathCestNames($moduleName) + { + $cestObjects = []; + $xmlFiles = glob( + TESTS_MODULE_PATH . + DIRECTORY_SEPARATOR . + $moduleName . + DIRECTORY_SEPARATOR . + 'Cest' . + DIRECTORY_SEPARATOR . + '*.xml' + ); + + foreach ($xmlFiles as $xmlFile) { + $cestObj = $this->resolveFilePathCestName($xmlFile); + $cestObjects[$cestObj->getName()] = $cestObj; + } + + return $cestObjects; + } +} diff --git a/src/Magento/FunctionalTestingFramework/Suite/etc/sampleSuite.xml b/src/Magento/FunctionalTestingFramework/Suite/etc/sampleSuite.xml new file mode 100644 index 000000000..7a120b3e2 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Suite/etc/sampleSuite.xml @@ -0,0 +1,23 @@ +<?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="suiteSchema.xsd"> + <suite name="mySuite"> + <include> + <group name="someGroup"/> + <cest name="cest" test=""/> + <module name="moduleName" file="myFile"/> + </include> + <exclude> + <group name="someGroup"/> + <cest name="excludeTest" test=""/> + <group name="excludeGroup"/> + <module name="moduleName" file="excludeFile"/> + </exclude> + </suite> +</suites> diff --git a/src/Magento/FunctionalTestingFramework/Suite/etc/suiteSchema.xsd b/src/Magento/FunctionalTestingFramework/Suite/etc/suiteSchema.xsd new file mode 100644 index 000000000..31ebed313 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Suite/etc/suiteSchema.xsd @@ -0,0 +1,71 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<xs:schema attributeFormDefault="unqualified" elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema"> + <xs:include schemaLocation="../../Test/etc/testSchema.xsd"/> + <xs:element name="suites" type="suiteConfigType"/> + <xs:complexType name="groupSuiteOptionType"> + <xs:simpleContent> + <xs:extension base="xs:string"> + <xs:attribute type="xs:string" name="name" use="required"/> + <xs:attribute type="xs:boolean" name="remove" use="optional"/> + </xs:extension> + </xs:simpleContent> + </xs:complexType> + <xs:complexType name="cestSuiteOptionType"> + <xs:simpleContent> + <xs:extension base="xs:string"> + <xs:attribute type="xs:string" name="name" use="required"/> + <xs:attribute type="xs:string" name="test" use="optional"/> + <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="cestSuiteOptionType" name="cest" 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="cestSuiteOptionType" name="cest" 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="suiteHookType" name="before" maxOccurs="1"/> + <xs:element type="suiteHookType" 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:schema> diff --git a/src/Magento/FunctionalTestingFramework/Suite/views/SuiteClass.mustache b/src/Magento/FunctionalTestingFramework/Suite/views/SuiteClass.mustache new file mode 100644 index 000000000..d55d6d0ee --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Suite/views/SuiteClass.mustache @@ -0,0 +1,54 @@ +<?php + +namespace Group; + +use Magento\FunctionalTestingFramework\DataGenerator\Handlers\DataObjectHandler; +use Magento\FunctionalTestingFramework\DataGenerator\Persist\DataPersistenceHandler; + +/** + * 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 {{suiteName}} extends \Codeception\GroupObject +{ + public static $group = '{{suiteName}}'; + private static $TEST_COUNT = {{testCount}}; + private static $CURRENT_TEST_RUN = 0; + + {{#var}} + private ${{mergeKey}}; + {{/var}} + + {{#before}} + public function _before(\Codeception\Event\TestEvent $e) + { + // increment test count per execution + self::$CURRENT_TEST_RUN++; + + if (self::$CURRENT_TEST_RUN == 1) { + {{> dataPersistence}} + } + } + {{/before}} + + {{#after}} + public function _after(\Codeception\Event\TestEvent $e) + { + if (self::$CURRENT_TEST_RUN == self::$TEST_COUNT) { + {{> dataPersistence}} + } + } + + public function _failed(\Codeception\Event\TestEvent $e) + { + if (self::$CURRENT_TEST_RUN == self::$TEST_COUNT) { + {{> dataPersistence}} + } + } + {{/after}} +} diff --git a/src/Magento/FunctionalTestingFramework/Suite/views/partials/dataPersistence.mustache b/src/Magento/FunctionalTestingFramework/Suite/views/partials/dataPersistence.mustache new file mode 100644 index 000000000..269204602 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Suite/views/partials/dataPersistence.mustache @@ -0,0 +1,8 @@ +{{#createData}} +${{entityName}} = DataObjectHandler::getInstance()->getObject("{{entityName}}"); +$this->{{mergeKey}} = new DataPersistenceHandler(${{entityName}}, [{{#requiredEntities}}$this->{{entityName}}{{^last}}, {{/last}}{{/requiredEntities}}]); +$this->{{mergeKey}}->createEntity(); +{{/createData}} +{{#deleteData}} +$this->{{entityName}}->deleteEntity(); +{{/deleteData}} diff --git a/src/Magento/FunctionalTestingFramework/Test/Config/Converter/Dom/Flat.php b/src/Magento/FunctionalTestingFramework/Test/Config/Converter/Dom/Flat.php index bd53b8b5f..e08174604 100644 --- a/src/Magento/FunctionalTestingFramework/Test/Config/Converter/Dom/Flat.php +++ b/src/Magento/FunctionalTestingFramework/Test/Config/Converter/Dom/Flat.php @@ -1,8 +1,9 @@ <?php /** - * Copyright © 2013-2017 Magento, Inc. All rights reserved. + * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\FunctionalTestingFramework\Test\Config\Converter\Dom; use Magento\FunctionalTestingFramework\Config\ConverterInterface; diff --git a/src/Magento/FunctionalTestingFramework/Test/Handlers/CestObjectHandler.php b/src/Magento/FunctionalTestingFramework/Test/Handlers/CestObjectHandler.php index 0bb21897a..ab8187881 100644 --- a/src/Magento/FunctionalTestingFramework/Test/Handlers/CestObjectHandler.php +++ b/src/Magento/FunctionalTestingFramework/Test/Handlers/CestObjectHandler.php @@ -63,6 +63,11 @@ private function __construct() */ public function getObject($cestName) { + if (!array_key_exists($cestName, $this->cests)) { + trigger_error("Cest ${cestName} not defined in xml.", E_USER_ERROR); + return null; + } + return $this->cests[$cestName]; } @@ -76,6 +81,44 @@ public function getAllObjects() return $this->cests; } + /** + * Returns tests and cests tagged with the group name passed to the method. + * + * @param string $groupName + * @return array + */ + public function getCestsByGroup($groupName) + { + $relevantCests = []; + foreach ($this->cests as $cest) { + /** @var CestObject $cest */ + if (in_array($groupName, $cest->getAnnotationByName('group'))) { + $relevantCests[$cest->getName()] = $cest; + continue; + } + + $relevantTests = []; + // extract relevant tests here + foreach ($cest->getTests() as $test) { + if (in_array($groupName, $test->getAnnotationByName('group'))) { + $relevantTests[$test->getName()] = $test; + continue; + } + } + + if (!empty($relevantTests)) { + $relevantCests[$cest->getName()] = new CestObject( + $cest->getName(), + $cest->getAnnotations(), + $relevantTests, + $cest->getHooks() + ); + } + } + + return $relevantCests; + } + /** * This method reads all Cest.xml files into objects and stores them in an array for future access. * @@ -88,6 +131,11 @@ private function initCestData() $cestObjectExtractor = new CestObjectExtractor(); + if (!$parsedCestArray) { + trigger_error("Could not parse any data.xml.", E_USER_NOTICE); + return; + } + foreach ($parsedCestArray[CestObjectHandler::XML_ROOT] as $cestName => $cestData) { if (!is_array($cestData)) { continue; diff --git a/src/Magento/FunctionalTestingFramework/Test/Objects/ActionGroupObject.php b/src/Magento/FunctionalTestingFramework/Test/Objects/ActionGroupObject.php index a88bcae61..c5beb1236 100644 --- a/src/Magento/FunctionalTestingFramework/Test/Objects/ActionGroupObject.php +++ b/src/Magento/FunctionalTestingFramework/Test/Objects/ActionGroupObject.php @@ -13,7 +13,7 @@ */ class ActionGroupObject { - const VAR_ATTRIBUTES = ['userInput', 'selector', 'page']; + const VAR_ATTRIBUTES = ['userInput', 'selector', 'page', 'url']; /** * The name of the action group @@ -80,7 +80,11 @@ public function getSteps($arguments, $actionReferenceKey) private function getResolvedActionsWithArgs($arguments, $actionReferenceKey) { $resolvedActions = []; - $regexPattern = '/{{([\w]+)/'; + + // $regexPattern match on: $matches[0] {{section.element(arg.field)}} + // $matches[1] = section.element + // $matches[2] = arg.field + $regexPattern = '/{{([\w.]+)\(*([\w.$\']+)*\)*}}/'; foreach ($this->parsedActions as $action) { $varAttributes = array_intersect(self::VAR_ATTRIBUTES, array_keys($action->getCustomActionAttributes())); @@ -90,11 +94,15 @@ private function getResolvedActionsWithArgs($arguments, $actionReferenceKey) foreach ($varAttributes as $varAttribute) { $attributeValue = $action->getCustomActionAttributes()[$varAttribute]; preg_match_all($regexPattern, $attributeValue, $matches); - if (empty($matches[0]) & empty($matches[1])) { + + if (empty($matches[0])) { continue; } - $newActionAttributes[$varAttribute] = $this->resolveNewAttribute( + //get rid of full match {{arg.field(arg.field)}} + unset($matches[0]); + + $newActionAttributes[$varAttribute] = $this->replaceAttributeArguments( $arguments, $attributeValue, $matches @@ -114,23 +122,78 @@ private function getResolvedActionsWithArgs($arguments, $actionReferenceKey) } /** - * Function which takes an array of arguments to use for replacement of var name, the string which contains - * the variable for replacement, an array of matching vars. + * Function that takes an array of replacement arguments, and matches them with args in an actionGroup's attribute. + * Determines if the replacement arguments are persisted data, and replaces them accordingly. * * @param array $arguments * @param string $attributeValue * @param array $matches * @return string */ - private function resolveNewAttribute($arguments, $attributeValue, $matches) + private function replaceAttributeArguments($arguments, $attributeValue, $matches) { + $matchParametersKey = 2; $newAttributeVal = $attributeValue; - foreach ($matches[1] as $var) { - if (array_key_exists($var, $arguments)) { - $newAttributeVal = str_replace($var, $arguments[$var], $newAttributeVal); + + foreach ($matches as $key => $match) { + foreach ($match as $variable) { + if (empty($variable)) { + continue; + } + // Truncate arg.field into arg + $variableName = strstr($variable, '.', true); + // Check if arguments has a mapping for the given variableName + if (!array_key_exists($variableName, $arguments)) { + continue; + } + $isPersisted = strstr($arguments[$variableName], '$'); + if ($isPersisted) { + $newAttributeVal = $this->replacePersistedArgument( + $arguments[$variableName], + $attributeValue, + $variable, + $variableName, + $key == $matchParametersKey ? true : false + ); + } else { + $newAttributeVal = str_replace($variableName, $arguments[$variableName], $attributeValue); + } } } return $newAttributeVal; } + + /** + * Replaces args with replacements given, behavior is specific to persisted arguments. + * @param string $replacement + * @param string $attributeValue + * @param string $fullVariable + * @param string $variable + * @param boolean $isParameter + * @return string + */ + private function replacePersistedArgument($replacement, $attributeValue, $fullVariable, $variable, $isParameter) + { + //hookPersisted will be true if replacement passed in is $$arg.field$$, otherwise assume it's $arg.field$ + $hookPersistedArgumentRegex = '/\$\$[\w.\[\]\',]+\$\$/'; + $hookPersisted = (preg_match($hookPersistedArgumentRegex, $replacement)); + + $newAttributeValue = $attributeValue; + + $scope = '$'; + if ($hookPersisted) { + $scope = '$$'; + } + + // parameter replacements require changing of (arg.field) to ($arg.field$) + if ($isParameter) { + $newAttributeValue = str_replace($fullVariable, $scope . $fullVariable . $scope, $newAttributeValue); + } else { + $newAttributeValue = str_replace('{{', $scope, str_replace('}}', $scope, $newAttributeValue)); + } + $newAttributeValue = str_replace($variable, trim($replacement, '$'), $newAttributeValue); + + return $newAttributeValue; + } } diff --git a/src/Magento/FunctionalTestingFramework/Test/Objects/ActionObject.php b/src/Magento/FunctionalTestingFramework/Test/Objects/ActionObject.php index cc473075e..9647d0f34 100644 --- a/src/Magento/FunctionalTestingFramework/Test/Objects/ActionObject.php +++ b/src/Magento/FunctionalTestingFramework/Test/Objects/ActionObject.php @@ -398,7 +398,7 @@ private function matchParameterReferences($reference, $parameters) $resolvedParameters = []; foreach ($parameters as $parameter) { $parameter = trim($parameter); - preg_match_all("/[$'][\w.$]+[$']/", $parameter, $match); + preg_match_all("/[$'][\w\D]+[$']/", $parameter, $match); if (!empty($match[0])) { $resolvedParameters[] = ltrim(rtrim($parameter, "'"), "'"); } else { diff --git a/src/Magento/FunctionalTestingFramework/Test/Objects/CestObject.php b/src/Magento/FunctionalTestingFramework/Test/Objects/CestObject.php index 5b7018657..81c67ff70 100644 --- a/src/Magento/FunctionalTestingFramework/Test/Objects/CestObject.php +++ b/src/Magento/FunctionalTestingFramework/Test/Objects/CestObject.php @@ -74,6 +74,21 @@ public function getAnnotations() return $this->annotations; } + /** + * Returns the value(s) of an annotation by a specific name such as group + * + * @param string $name + * @return array + */ + public function getAnnotationByName($name) + { + if (array_key_exists($name, $this->annotations)) { + return $this->annotations[$name]; + } + + return []; + } + /** * Returns tests. * diff --git a/src/Magento/FunctionalTestingFramework/Test/Objects/TestObject.php b/src/Magento/FunctionalTestingFramework/Test/Objects/TestObject.php index 97865adb1..6ccf23172 100644 --- a/src/Magento/FunctionalTestingFramework/Test/Objects/TestObject.php +++ b/src/Magento/FunctionalTestingFramework/Test/Objects/TestObject.php @@ -15,10 +15,6 @@ */ class TestObject { - const STEP_MISSING_ERROR_MSG = - "Merge Error - Step could not be found in either TestXML or DeltaXML. - \tTest = '%s'\tTestStep='%s'\tLinkedStep'%s'"; - /** * Name of the test * @@ -82,6 +78,21 @@ public function getAnnotations() return $this->annotations; } + /** + * Method to return the value(s) of a corresponding annotation such as group. + * + * @param string $name + * @return array + */ + public function getAnnotationByName($name) + { + if (array_key_exists($name, $this->annotations)) { + return $this->annotations[$name]; + } + + return []; + } + /** * Getter for the custom data * @return array|null diff --git a/src/Magento/FunctionalTestingFramework/Test/Util/ActionMergeUtil.php b/src/Magento/FunctionalTestingFramework/Test/Util/ActionMergeUtil.php index ad38df07f..391674650 100644 --- a/src/Magento/FunctionalTestingFramework/Test/Util/ActionMergeUtil.php +++ b/src/Magento/FunctionalTestingFramework/Test/Util/ActionMergeUtil.php @@ -6,6 +6,7 @@ namespace Magento\FunctionalTestingFramework\Test\Util; +use Magento\FunctionalTestingFramework\Exceptions\XmlException; use Magento\FunctionalTestingFramework\Test\Handlers\ActionGroupObjectHandler; use Magento\FunctionalTestingFramework\Test\Objects\ActionObject; @@ -14,6 +15,10 @@ */ class ActionMergeUtil { + const STEP_MISSING_ERROR_MSG = + "Merge Error - Step could not be found in either TestXML or DeltaXML. + \tTest = '%s'\tTestStep='%s'\tLinkedStep'%s'"; + const WAIT_ATTR = 'timeout'; const WAIT_ACTION_NAME = 'waitForPageLoad'; const WAIT_ACTION_SUFFIX = 'WaitForPageLoad'; diff --git a/src/Magento/FunctionalTestingFramework/Test/Util/TestEntityExtractor.php b/src/Magento/FunctionalTestingFramework/Test/Util/TestEntityExtractor.php index cd72af604..d6ce1b7bf 100644 --- a/src/Magento/FunctionalTestingFramework/Test/Util/TestEntityExtractor.php +++ b/src/Magento/FunctionalTestingFramework/Test/Util/TestEntityExtractor.php @@ -1,9 +1,7 @@ <?php /** - * Created by PhpStorm. - * User: imeron - * Date: 8/25/17 - * Time: 11:11 AM + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. */ namespace Magento\FunctionalTestingFramework\Test\Util; diff --git a/src/Magento/FunctionalTestingFramework/Test/Util/TestObjectExtractor.php b/src/Magento/FunctionalTestingFramework/Test/Util/TestObjectExtractor.php index 2948059e2..6e7fcb508 100644 --- a/src/Magento/FunctionalTestingFramework/Test/Util/TestObjectExtractor.php +++ b/src/Magento/FunctionalTestingFramework/Test/Util/TestObjectExtractor.php @@ -75,7 +75,7 @@ public function extractTestData($cestTestData) $testAnnotations = $this->annotationExtractor->extractAnnotations($testData[self::TEST_ANNOTATIONS]); } - $testObjects[] = new TestObject( + $testObjects[$testName] = new TestObject( $testName, $this->actionObjectExtractor->extractActions($testActions), $testAnnotations, diff --git a/src/Magento/FunctionalTestingFramework/Test/etc/sampleActionGroup.xml b/src/Magento/FunctionalTestingFramework/Test/etc/sampleActionGroup.xml index 6939e2493..e0be8ca20 100644 --- a/src/Magento/FunctionalTestingFramework/Test/etc/sampleActionGroup.xml +++ b/src/Magento/FunctionalTestingFramework/Test/etc/sampleActionGroup.xml @@ -1,4 +1,10 @@ <?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="testSchema.xsd"> <actionGroup name="sampleReusableActions"> diff --git a/src/Magento/FunctionalTestingFramework/Test/etc/sampleCest.xml b/src/Magento/FunctionalTestingFramework/Test/etc/sampleCest.xml index 2ae9e1260..c6966af5a 100644 --- a/src/Magento/FunctionalTestingFramework/Test/etc/sampleCest.xml +++ b/src/Magento/FunctionalTestingFramework/Test/etc/sampleCest.xml @@ -1,4 +1,10 @@ <?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="testSchema.xsd"> <cest name="sampleCest"> diff --git a/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd b/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd index 58f455a6e..3a31fe992 100644 --- a/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd +++ b/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd @@ -1,4 +1,11 @@ <?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + <xs:schema elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema"> <!-- Container types --> @@ -70,6 +77,7 @@ </xs:complexType> <xs:group name="actionTypeTags"> <xs:choice> + <xs:group ref="dataOperationTags"/> <xs:element type="acceptPopupType" name="acceptPopup" minOccurs="0" maxOccurs="unbounded"/> <xs:element type="amOnPageType" name="amOnPage" minOccurs="0" maxOccurs="unbounded"/> <xs:element type="amOnSubdomainType" name="amOnSubdomain" minOccurs="0" maxOccurs="unbounded"/> @@ -84,8 +92,8 @@ <xs:element type="closeAdminNotificationType" name="closeAdminNotification" minOccurs="0" maxOccurs="unbounded"/> <xs:element type="closeTabType" name="closeTab" minOccurs="0" maxOccurs="unbounded"/> <xs:element type="conditionalClickType" name="conditionalClick" minOccurs="0" maxOccurs="unbounded"/> - <xs:element type="createDataType" name="createData" minOccurs="0" maxOccurs="unbounded"/> - <xs:element type="deleteDataType" name="deleteData" minOccurs="0" maxOccurs="unbounded"/> + <xs:element type="updateDataType" name="updateData" minOccurs="0" maxOccurs="unbounded"/> + <xs:element type="getDataType" name="getData" minOccurs="0" maxOccurs="unbounded"/> <xs:element type="dontSeeType" name="dontSee" minOccurs="0" maxOccurs="unbounded"/> <xs:element type="dontSeeCheckboxIsCheckedType" name="dontSeeCheckboxIsChecked" minOccurs="0" maxOccurs="unbounded"/> <xs:element type="dontSeeCookieType" name="dontSeeCookie" minOccurs="0" maxOccurs="unbounded"/> @@ -176,6 +184,13 @@ </xs:choice> </xs:group> + <xs:group name="dataOperationTags"> + <xs:choice> + <xs:element type="createDataType" name="createData" minOccurs="0" maxOccurs="unbounded"/> + <xs:element type="deleteDataType" name="deleteData" minOccurs="0" maxOccurs="unbounded"/> + </xs:choice> + </xs:group> + <!-- Action Group ref --> <xs:complexType name="actions"> @@ -385,6 +400,18 @@ </xs:extension> </xs:simpleContent> </xs:complexType> + <xs:complexType name="updateDataType"> + <xs:choice minOccurs="0" maxOccurs="unbounded"> + <xs:element type="requiredEntityType" name="required-entity" minOccurs="0" maxOccurs="unbounded"/> + </xs:choice> + <xs:attribute type="xs:string" name="entity" use="required"/> + <xs:attribute type="xs:string" name="mergeKey" use="required"/> + <xs:attribute type="xs:string" name="createDataKey" use="required"/> + <xs:attribute type="xs:boolean" name="remove" default="false"/> + <xs:attribute type="xs:string" name="before"/> + <xs:attribute type="xs:string" name="after"/> + <xs:attribute type="xs:string" name="storeCode"/> + </xs:complexType> <xs:complexType name="deleteDataType"> <xs:simpleContent> <xs:extension base="xs:string"> @@ -397,6 +424,18 @@ </xs:extension> </xs:simpleContent> </xs:complexType> + <xs:complexType name="getDataType"> + <xs:choice minOccurs="0" maxOccurs="unbounded"> + <xs:element type="requiredEntityType" name="required-entity" minOccurs="0" maxOccurs="unbounded"/> + </xs:choice> + <xs:attribute type="xs:string" name="entity" use="required"/> + <xs:attribute type="xs:string" name="mergeKey" use="required"/> + <xs:attribute type="xs:integer" name="index"/> + <xs:attribute type="xs:boolean" name="remove" default="false"/> + <xs:attribute type="xs:string" name="before"/> + <xs:attribute type="xs:string" name="after"/> + <xs:attribute type="xs:string" name="storeCode"/> + </xs:complexType> <xs:complexType name="dontSeeType"> <xs:simpleContent> <xs:extension base="xs:string"> diff --git a/src/Magento/FunctionalTestingFramework/Util/Filesystem/DirSetupUtil.php b/src/Magento/FunctionalTestingFramework/Util/Filesystem/DirSetupUtil.php new file mode 100644 index 000000000..405b2a4f7 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Util/Filesystem/DirSetupUtil.php @@ -0,0 +1,52 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\FunctionalTestingFramework\Util\Filesystem; + +use FilesystemIterator; +use RecursiveDirectoryIterator; + +class DirSetupUtil +{ + /** + * Method used to clean export dir if needed and create new empty export dir. + * + * @param string $fullPath + * @return void + */ + public static function createGroupDir($fullPath) + { + if (file_exists($fullPath)) { + self::rmDirRecursive($fullPath); + } + + mkdir($fullPath, 0777, true); + } + + /** + * Takes a directory path and recursively deletes all files and folders. + * + * @param string $directory + * @return void + */ + private static function rmdirRecursive($directory) + { + $it = new RecursiveDirectoryIterator($directory, FilesystemIterator::SKIP_DOTS); + + while ($it->valid()) { + $path = $directory . DIRECTORY_SEPARATOR . $it->getFilename(); + if ($it->isDir()) { + self::rmDirRecursive($path); + } else { + unlink($path); + } + + $it->next(); + } + + rmdir($directory); + } +} diff --git a/src/Magento/FunctionalTestingFramework/Util/ModuleResolver/SequenceSorter.php b/src/Magento/FunctionalTestingFramework/Util/ModuleResolver/SequenceSorter.php index c8a0d7530..10c285923 100644 --- a/src/Magento/FunctionalTestingFramework/Util/ModuleResolver/SequenceSorter.php +++ b/src/Magento/FunctionalTestingFramework/Util/ModuleResolver/SequenceSorter.php @@ -1,4 +1,9 @@ <?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + namespace Magento\FunctionalTestingFramework\Util\ModuleResolver; /** diff --git a/src/Magento/FunctionalTestingFramework/Util/ModuleResolver/SequenceSorterInterface.php b/src/Magento/FunctionalTestingFramework/Util/ModuleResolver/SequenceSorterInterface.php index 1d19513e8..fa251c00f 100644 --- a/src/Magento/FunctionalTestingFramework/Util/ModuleResolver/SequenceSorterInterface.php +++ b/src/Magento/FunctionalTestingFramework/Util/ModuleResolver/SequenceSorterInterface.php @@ -1,4 +1,9 @@ <?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + namespace Magento\FunctionalTestingFramework\Util\ModuleResolver; /** diff --git a/src/Magento/FunctionalTestingFramework/Util/Protocol/CurlInterface.php b/src/Magento/FunctionalTestingFramework/Util/Protocol/CurlInterface.php index 792e0c6d4..06d843fea 100644 --- a/src/Magento/FunctionalTestingFramework/Util/Protocol/CurlInterface.php +++ b/src/Magento/FunctionalTestingFramework/Util/Protocol/CurlInterface.php @@ -1,6 +1,6 @@ <?php /** - * Copyright © 2017 Magento. All rights reserved. + * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ @@ -32,7 +32,7 @@ public function addOption($option, $value); * Send request to the remote server. * * @param string $url - * @param array $body + * @param array|string $body * @param string $method * @param array $headers * @return void diff --git a/src/Magento/FunctionalTestingFramework/Util/Protocol/CurlTransport.php b/src/Magento/FunctionalTestingFramework/Util/Protocol/CurlTransport.php index 5f02f476d..3c1c4e614 100644 --- a/src/Magento/FunctionalTestingFramework/Util/Protocol/CurlTransport.php +++ b/src/Magento/FunctionalTestingFramework/Util/Protocol/CurlTransport.php @@ -1,6 +1,6 @@ <?php /** - * Copyright © 2017 Magento. All rights reserved. + * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ @@ -118,7 +118,7 @@ public function setConfig(array $config = []) * Send request to the remote server. * * @param string $url - * @param array $body + * @param array|string $body * @param string $method * @param array $headers * @return void diff --git a/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php b/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php index c3e436f3b..86682fc2e 100644 --- a/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php +++ b/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php @@ -6,19 +6,19 @@ namespace Magento\FunctionalTestingFramework\Util; -use FilesystemIterator; use Magento\FunctionalTestingFramework\DataGenerator\Objects\EntityDataObject; use Magento\FunctionalTestingFramework\Exceptions\TestReferenceException; use Magento\FunctionalTestingFramework\Test\Handlers\CestObjectHandler; use Magento\FunctionalTestingFramework\Test\Objects\ActionObject; use Magento\FunctionalTestingFramework\DataGenerator\Handlers\DataObjectHandler; use Magento\FunctionalTestingFramework\Test\Objects\CestObject; -use RecursiveDirectoryIterator; +use Magento\FunctionalTestingFramework\Util\Filesystem\DirSetupUtil; class TestGenerator { const REQUIRED_ENTITY_REFERENCE = 'createDataKey'; + const GENERATED_DIR = '_generated'; /** * Path to the export dir. @@ -28,72 +28,56 @@ class TestGenerator private $exportDirectory; /** - * Test generator. + * Export dir name. * - * @var TestGenerator + * @var string */ - private static $testGenerator; + private $exportDirName; /** - * TestGenerator constructor. - * @param string $exportDir + * Array of CestObjects to be generated + * + * @var array */ - private function __construct($exportDir) - { - // private constructor for singleton - $this->exportDirectory = $exportDir; - } + private $cests; /** - * Method used to clean export dir if needed and create new empty export dir. + * TestGenerator constructor. * - * @return void + * @param string $exportDir + * @param array $cests */ - private function setupExportDir() + private function __construct($exportDir, $cests) { - if (file_exists($this->exportDirectory)) { - $this->rmDirRecursive($this->exportDirectory); - } - - mkdir($this->exportDirectory, 0777, true); + // private constructor for factory + $this->exportDirName = $exportDir ?? self::GENERATED_DIR; + $this->exportDirectory = rtrim( + TESTS_MODULE_PATH . DIRECTORY_SEPARATOR . self::GENERATED_DIR . DIRECTORY_SEPARATOR . $exportDir, + DIRECTORY_SEPARATOR + ); + $this->cests = $cests; } /** - * Takes a directory path and recursively deletes all files and folders. + * Singleton method to retrieve Test Generator * - * @param string $directory - * @return void + * @param string $dir + * @param array $cests + * @return TestGenerator */ - private function rmdirRecursive($directory) + public static function getInstance($dir = null, $cests = null) { - $it = new RecursiveDirectoryIterator($directory, FilesystemIterator::SKIP_DOTS); - - while ($it->valid()) { - $path = $directory . DIRECTORY_SEPARATOR . $it->getFilename(); - if ($it->isDir()) { - $this->rmDirRecursive($path); - } else { - unlink($path); - } - - $it->next(); - } - - rmdir($directory); + return new TestGenerator($dir, $cests); } /** - * Singleton method to retrieve Test Generator + * Returns the absolute path to the test export director for the generator instance. * - * @return TestGenerator + * @return string */ - public static function getInstance() + public function getExportDir() { - if (!self::$testGenerator) { - self::$testGenerator = new TestGenerator(TESTS_MODULE_PATH . DIRECTORY_SEPARATOR . "_generated"); - } - - return self::$testGenerator; + return $this->exportDirectory; } /** @@ -103,7 +87,11 @@ public static function getInstance() */ private function loadAllCestObjects() { - return CestObjectHandler::getInstance()->getAllObjects(); + if ($this->cests === null) { + return CestObjectHandler::getInstance()->getAllObjects(); + } + + return $this->cests; } /** @@ -132,16 +120,25 @@ private function createCestFile($cestPhp, $filename) * Assemble ALL PHP strings using the assembleAllCestPhp function. Loop over and pass each array item * to the createCestFile function. * + * @param string $runConfig + * @param string $env * @return void */ - public function createAllCestFiles() + public function createAllCestFiles($runConfig = null, $env = null) { - $this->setupExportDir(); - $cestPhpArray = $this->assembleAllCestPhp(); + DirSetupUtil::createGroupDir($this->exportDirectory); + + // create our manifest file here + $testManifest = new TestManifest($this->exportDirectory, $runConfig, $env); + $cestPhpArray = $this->assembleAllCestPhp($testManifest); foreach ($cestPhpArray as $cestPhpFile) { $this->createCestFile($cestPhpFile[1], $cestPhpFile[0]); } + + if ($testManifest->getManifestConfig() === TestManifest::SINGLE_RUN_CONFIG) { + $testManifest->recordPathToExportDir(); + } } /** @@ -149,11 +146,12 @@ public function createAllCestFiles() * Create all of the PHP strings for a Test. Concatenate the strings together. * * @param \Magento\FunctionalTestingFramework\Test\Objects\CestObject $cestObject + * @throws TestReferenceException * @return string */ private function assembleCestPhp($cestObject) { - $usePhp = $this->generateUseStatementsPhp($cestObject); + $usePhp = $this->generateUseStatementsPhp(); $classAnnotationsPhp = $this->generateClassAnnotationsPhp($cestObject->getAnnotations()); $className = $cestObject->getName(); $className = str_replace(' ', '', $className); @@ -165,7 +163,7 @@ private function assembleCestPhp($cestObject) } $cestPhp = "<?php\n"; - $cestPhp .= "namespace Magento\AcceptanceTest\Backend;\n\n"; + $cestPhp .= "namespace Magento\AcceptanceTest\\" . $this->exportDirName ."\Backend;\n\n"; $cestPhp .= $usePhp; $cestPhp .= $classAnnotationsPhp; $cestPhp .= sprintf("class %s\n", $className); @@ -180,24 +178,24 @@ private function assembleCestPhp($cestObject) /** * Load ALL Cest objects. Loop over and pass each to the assembleCestPhp function. * + * @param TestManifest $testManifest * @return array */ - private function assembleAllCestPhp() + private function assembleAllCestPhp($testManifest) { $cestObjects = $this->loadAllCestObjects(); $cestPhpArray = []; - // create our manifest file here - $testManifest = new TestManifest($this->exportDirectory); - foreach ($cestObjects as $cest) { $name = $cest->getName(); $name = $string = str_replace(' ', '', $name); $php = $this->assembleCestPhp($cest); $cestPhpArray[] = [$name, $php]; - //write to manifest here - $testManifest->recordCest($cest->getName(), $cest->getTests()); + //write to manifest here if config is not single run + if ($testManifest->getManifestConfig() != TestManifest::SINGLE_RUN_CONFIG) { + $testManifest->recordCest($cest->getName(), $cest->getTests()); + } } return $cestPhpArray; @@ -379,15 +377,7 @@ private function generateStepsPhp($stepsObject, $stepsData, $hookObject = false) } if (isset($customActionAttributes['parameterArray'])) { - $paramsWithUniqueness = []; - $params = explode( - ',', - $this->stripWrappedQuotes(rtrim(ltrim($customActionAttributes['parameterArray'], '['), ']')) - ); - foreach ($params as $param) { - $paramsWithUniqueness[] = $this->addUniquenessFunctionCall($param); - } - $parameterArray = '[' . implode(',', $paramsWithUniqueness) . ']'; + $parameterArray = $customActionAttributes['parameterArray']; } if (isset($customActionAttributes['requiredAction'])) { @@ -553,6 +543,133 @@ private function generateStepsPhp($stepsObject, $stepsData, $hookObject = false) $testSteps .= sprintf("\t\t$%s->deleteEntity();\n", $key); } break; + case "updateData": + $entity = $customActionAttributes['entity']; + $originalEntity = null; + if (isset($customActionAttributes['createDataKey'])) { + $originalEntity = $customActionAttributes['createDataKey']; + } + $key = $steps->getMergeKey(); + //Add an informative statement to help the user debug test runs + $testSteps .= sprintf( + "\t\t$%s->amGoingTo(\"update entity that has the mergeKey: %s\");\n", + $actor, + $key + ); + //Get Entity from Static data. + $testSteps .= sprintf( + "\t\t$%s = DataObjectHandler::getInstance()->getObject(\"%s\");\n", + $entity, + $entity + ); + + if ($hookObject) { + $updateEntityFunctionCall = sprintf("\t\t\$this->%s->updateEntity(", $key); + $dataPersistenceHandlerFunctionCall = sprintf( + "\t\t\$this->%s = new DataPersistenceHandler($%s, [\$this->%s]);\n", + $key, + $entity, + $originalEntity + ); + } else { + $updateEntityFunctionCall = sprintf("\t\t\$%s->updateEntity(", $key); + $dataPersistenceHandlerFunctionCall = sprintf( + "\t\t$%s = new DataPersistenceHandler($%s, [$%s]);\n", + $key, + $entity, + $originalEntity + ); + } + + if (isset($customActionAttributes['storeCode'])) { + $updateEntityFunctionCall .= sprintf("\"%s\");\n", $customActionAttributes['storeCode']); + } else { + $updateEntityFunctionCall .= ");\n"; + } + + $testSteps .= $dataPersistenceHandlerFunctionCall; + $testSteps .= $updateEntityFunctionCall; + break; + case "getData": + $entity = $customActionAttributes['entity']; + $key = $steps->getMergeKey(); + //Add an informative statement to help the user debug test runs + $testSteps .= sprintf( + "\t\t$%s->amGoingTo(\"get entity that has the mergeKey: %s\");\n", + $actor, + $key + ); + //Get Entity from Static data. + $testSteps .= sprintf( + "\t\t$%s = DataObjectHandler::getInstance()->getObject(\"%s\");\n", + $entity, + $entity + ); + + //HookObject End-Product needs to be created in the Class/Cest scope, + //otherwise create them in the Test scope. + //Determine if there are required-entities and create array of required-entities for merging. + $requiredEntities = []; + $requiredEntityObjects = []; + foreach ($customActionAttributes as $customAttribute) { + if (is_array($customAttribute) && $customAttribute['nodeName'] = 'required-entity') { + if ($hookObject) { + $requiredEntities [] = "\$this->" . $customAttribute[self::REQUIRED_ENTITY_REFERENCE] . + "->getName() => " . "\$this->" . $customAttribute[self::REQUIRED_ENTITY_REFERENCE] . + "->getType()"; + $requiredEntityObjects [] = '$this->' . $customAttribute + [self::REQUIRED_ENTITY_REFERENCE]; + } else { + $requiredEntities [] = "\$" . $customAttribute[self::REQUIRED_ENTITY_REFERENCE] + . "->getName() => " . "\$" . $customAttribute[self::REQUIRED_ENTITY_REFERENCE] . + "->getType()"; + $requiredEntityObjects [] = '$' . $customAttribute[self::REQUIRED_ENTITY_REFERENCE]; + } + } + } + + if ($hookObject) { + $getEntityFunctionCall = sprintf("\t\t\$this->%s->getEntity(", $key); + $dataPersistenceHandlerFunctionCall = sprintf( + "\t\t\$this->%s = new DataPersistenceHandler($%s", + $key, + $entity + ); + } else { + $getEntityFunctionCall = sprintf("\t\t\$%s->getEntity(", $key); + $dataPersistenceHandlerFunctionCall = sprintf( + "\t\t$%s = new DataPersistenceHandler($%s", + $key, + $entity + ); + } + + if (isset($customActionAttributes['index'])) { + $getEntityFunctionCall .= sprintf("%s", (int)$customActionAttributes['index']); + } else { + $getEntityFunctionCall .= 'null'; + } + + if (isset($customActionAttributes['storeCode'])) { + $getEntityFunctionCall .= sprintf(", \"%s\");\n", $customActionAttributes['storeCode']); + } else { + $getEntityFunctionCall .= ");\n"; + } + + //If required-entities are defined, reassign dataObject to not overwrite the static definition. + //Also, DataPersistenceHandler needs to be defined with customData array. + if (!empty($requiredEntities)) { + $dataPersistenceHandlerFunctionCall .= sprintf( + ", [%s]);\n", + implode(', ', $requiredEntityObjects) + ); + } else { + $dataPersistenceHandlerFunctionCall .= ");\n"; + } + + $testSteps .= $dataPersistenceHandlerFunctionCall; + $testSteps .= $getEntityFunctionCall; + break; case "dontSeeCurrentUrlEquals": case "dontSeeCurrentUrlMatches": case "seeInPopup": @@ -838,7 +955,10 @@ private function generateHooksPhp($hookObjects) $dependencies = 'AcceptanceTester $I'; foreach ($hookObject->getActions() as $step) { - if ($step->getType() == "createData") { + if (($step->getType() == "createData") + || ($step->getType() == "updateData") + || ($step->getType() == "getData") + ) { $hooks .= "\t/**\n"; $hooks .= sprintf("\t * @var DataPersistenceHandler $%s;\n", $step->getMergeKey()); $hooks .= "\t */\n"; diff --git a/src/Magento/FunctionalTestingFramework/Util/TestManifest.php b/src/Magento/FunctionalTestingFramework/Util/TestManifest.php index 2545141c8..4991a6644 100644 --- a/src/Magento/FunctionalTestingFramework/Util/TestManifest.php +++ b/src/Magento/FunctionalTestingFramework/Util/TestManifest.php @@ -10,6 +10,10 @@ class TestManifest { + const SINGLE_RUN_CONFIG = 'singleRun'; + const DEFAULT_BROWSER = 'chrome'; + const TEST_MANIFEST_FILENAME = 'testManifest.txt'; + /** * Test Manifest file path. * @@ -17,6 +21,20 @@ class TestManifest */ private $filePath; + /** + * Test Manifest environment flag. This is added to each dir or file in order for tests to execute properly. + * + * @var string $environment + */ + private $environment = self::DEFAULT_BROWSER; + + /** + * Type of manifest to generate. (Currently describes whether to path to a dir or for each test). + * + * @var string + */ + private $runTypeConfig; + /** * Relative dir path from functional yml file. For devOps execution flexibility. * @@ -28,14 +46,32 @@ class TestManifest * TestManifest constructor. * * @param string $path + * @param string $runConfig + * @param string $env */ - public function __construct($path) + public function __construct($path, $runConfig, $env) { $this->relativeDirPath = substr($path, strlen(dirname(dirname(TESTS_BP))) + 1); - $filePath = $path . DIRECTORY_SEPARATOR . 'testManifest.txt'; + $filePath = $path . DIRECTORY_SEPARATOR . self::TEST_MANIFEST_FILENAME; $this->filePath = $filePath; $fileResource = fopen($filePath, 'w'); fclose($fileResource); + + $this->runTypeConfig = $runConfig; + + if ($env) { + $this->environment = $env; + } + } + + /** + * Returns a string indicating the generation config (e.g. singleRun). + * + * @return string + */ + public function getManifestConfig() + { + return $this->runTypeConfig; } /** @@ -51,9 +87,36 @@ public function recordCest($cestName, $tests) foreach ($tests as $test) { $line = $this->relativeDirPath . DIRECTORY_SEPARATOR . $cestName . '.php:' . $test->getName(); - fwrite($fileResource, $line ."\n"); + fwrite($fileResource, $this->appendDefaultBrowser($line) ."\n"); } fclose($fileResource); } + + /** + * Function which simple prints the export dir as part of the manifest file rather than an itemized list of + * cestFile:testname. + * + * @return void + */ + public function recordPathToExportDir() + { + $fileResource = fopen($this->filePath, 'a'); + + $line = $this->relativeDirPath . DIRECTORY_SEPARATOR; + fwrite($fileResource, $this->appendDefaultBrowser($line) ."\n"); + + fclose($fileResource); + } + + /** + * Function which appends the --env flag to the test. This is needed to properly execute all tests in codeception. + * + * @param string $line + * @return string + */ + private function appendDefaultBrowser($line) + { + return "${line} --env " . $this->environment; + } } diff --git a/src/Magento/FunctionalTestingFramework/Util/msq.php b/src/Magento/FunctionalTestingFramework/Util/msq.php index 621de31ac..3968e3c82 100644 --- a/src/Magento/FunctionalTestingFramework/Util/msq.php +++ b/src/Magento/FunctionalTestingFramework/Util/msq.php @@ -1,4 +1,9 @@ <?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + use Magento\FunctionalTestingFramework\Module\MagentoSequence; if (!function_exists('msq')) { diff --git a/src/Magento/FunctionalTestingFramework/XmlParser/ParserInterface.php b/src/Magento/FunctionalTestingFramework/XmlParser/ParserInterface.php index 0b276f8ab..b48f3531c 100644 --- a/src/Magento/FunctionalTestingFramework/XmlParser/ParserInterface.php +++ b/src/Magento/FunctionalTestingFramework/XmlParser/ParserInterface.php @@ -1,4 +1,9 @@ <?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + namespace Magento\FunctionalTestingFramework\XmlParser; /** diff --git a/src/Magento/FunctionalTestingFramework/XmlParser/SectionParser.php b/src/Magento/FunctionalTestingFramework/XmlParser/SectionParser.php index 69202166f..4a77b97b2 100644 --- a/src/Magento/FunctionalTestingFramework/XmlParser/SectionParser.php +++ b/src/Magento/FunctionalTestingFramework/XmlParser/SectionParser.php @@ -1,4 +1,9 @@ <?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + namespace Magento\FunctionalTestingFramework\XmlParser; use Magento\FunctionalTestingFramework\Config\DataInterface;