diff --git a/composer.json b/composer.json index 158287fbf..ca1c02572 100755 --- a/composer.json +++ b/composer.json @@ -11,7 +11,10 @@ "require": { "php": "7.0.2||7.0.4||~7.0.6||~7.1.0||~7.2.0||~7.3.0", "ext-curl": "*", + "ext-json": "*", + "ext-openssl": "*", "allure-framework/allure-codeception": "~1.3.0", + "aws/aws-sdk-php": "^3.132", "codeception/codeception": "~2.4.5", "composer/composer": "^1.4", "consolidation/robo": "^1.0.0", diff --git a/composer.lock b/composer.lock index 8f2fcb8e9..6bfc30ab9 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "59e95cc1ae6311e93111bd7ced180d29", + "content-hash": "2325a3a38edb33b24f6e33bd3009fd8a", "packages": [ { "name": "allure-framework/allure-codeception", @@ -109,6 +109,90 @@ ], "time": "2016-12-07T12:15:46+00:00" }, + { + "name": "aws/aws-sdk-php", + "version": "3.132.2", + "source": { + "type": "git", + "url": "https://github.com/aws/aws-sdk-php.git", + "reference": "f890689c6db27625522ea2e7e9b8420b6fccb063" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/f890689c6db27625522ea2e7e9b8420b6fccb063", + "reference": "f890689c6db27625522ea2e7e9b8420b6fccb063", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-pcre": "*", + "ext-simplexml": "*", + "guzzlehttp/guzzle": "^5.3.3|^6.2.1|^7.0", + "guzzlehttp/promises": "^1.0", + "guzzlehttp/psr7": "^1.4.1", + "mtdowling/jmespath.php": "^2.5", + "php": ">=5.5" + }, + "require-dev": { + "andrewsville/php-token-reflection": "^1.4", + "aws/aws-php-sns-message-validator": "~1.0", + "behat/behat": "~3.0", + "doctrine/cache": "~1.4", + "ext-dom": "*", + "ext-openssl": "*", + "ext-pcntl": "*", + "ext-sockets": "*", + "nette/neon": "^2.3", + "phpunit/phpunit": "^4.8.35|^5.4.3", + "psr/cache": "^1.0", + "psr/simple-cache": "^1.0", + "sebastian/comparator": "^1.2.3" + }, + "suggest": { + "aws/aws-php-sns-message-validator": "To validate incoming SNS notifications", + "doctrine/cache": "To use the DoctrineCacheAdapter", + "ext-curl": "To send requests using cURL", + "ext-openssl": "Allows working with CloudFront private distributions and verifying received SNS messages", + "ext-sockets": "To use client-side monitoring" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "psr-4": { + "Aws\\": "src/" + }, + "files": [ + "src/functions.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Amazon Web Services", + "homepage": "http://aws.amazon.com" + } + ], + "description": "AWS SDK for PHP - Use Amazon Web Services in your PHP project", + "homepage": "http://aws.amazon.com/sdkforphp", + "keywords": [ + "amazon", + "aws", + "cloud", + "dynamodb", + "ec2", + "glacier", + "s3", + "sdk" + ], + "time": "2020-01-09T19:09:31+00:00" + }, { "name": "behat/gherkin", "version": "v4.4.5", @@ -2542,8 +2626,66 @@ "bcmath", "math" ], + "abandoned": "brick/math", "time": "2017-02-16T16:54:46+00:00" }, + { + "name": "mtdowling/jmespath.php", + "version": "2.5.0", + "source": { + "type": "git", + "url": "https://github.com/jmespath/jmespath.php.git", + "reference": "52168cb9472de06979613d365c7f1ab8798be895" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/jmespath/jmespath.php/zipball/52168cb9472de06979613d365c7f1ab8798be895", + "reference": "52168cb9472de06979613d365c7f1ab8798be895", + "shasum": "" + }, + "require": { + "php": ">=5.4.0", + "symfony/polyfill-mbstring": "^1.4" + }, + "require-dev": { + "composer/xdebug-handler": "^1.2", + "phpunit/phpunit": "^4.8.36|^7.5.15" + }, + "bin": [ + "bin/jp.php" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.5-dev" + } + }, + "autoload": { + "psr-4": { + "JmesPath\\": "src/" + }, + "files": [ + "src/JmesPath.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "Declaratively specify how to extract elements from a JSON document", + "keywords": [ + "json", + "jsonpath" + ], + "time": "2019-12-30T18:03:34+00:00" + }, { "name": "mustache/mustache", "version": "v2.12.0", @@ -6542,7 +6684,9 @@ "prefer-lowest": false, "platform": { "php": "7.0.2||7.0.4||~7.0.6||~7.1.0||~7.2.0||~7.3.0", - "ext-curl": "*" + "ext-curl": "*", + "ext-json": "*", + "ext-openssl": "*" }, "platform-dev": [] } diff --git a/dev/tests/functional/tests/MFTF/DevDocs/ActionGroup/DeprecatedCommentActionGroup.xml b/dev/tests/functional/tests/MFTF/DevDocs/ActionGroup/DeprecatedCommentActionGroup.xml new file mode 100644 index 000000000..908808a8c --- /dev/null +++ b/dev/tests/functional/tests/MFTF/DevDocs/ActionGroup/DeprecatedCommentActionGroup.xml @@ -0,0 +1,13 @@ + + + + + + + diff --git a/dev/tests/functional/tests/MFTF/DevDocs/Data/DeprecatedMessageData.xml b/dev/tests/functional/tests/MFTF/DevDocs/Data/DeprecatedMessageData.xml new file mode 100644 index 000000000..5835a78af --- /dev/null +++ b/dev/tests/functional/tests/MFTF/DevDocs/Data/DeprecatedMessageData.xml @@ -0,0 +1,14 @@ + + + + + + Introduction to the Magento Functional Testing Framework + + diff --git a/dev/tests/functional/tests/MFTF/DevDocs/Data/MessageData.xml b/dev/tests/functional/tests/MFTF/DevDocs/Data/MessageData.xml new file mode 100644 index 000000000..eb77e73bc --- /dev/null +++ b/dev/tests/functional/tests/MFTF/DevDocs/Data/MessageData.xml @@ -0,0 +1,14 @@ + + + + + + Introduction to the Magento Functional Testing Framework + + diff --git a/dev/tests/functional/tests/MFTF/DevDocs/Page/DeprecatedMFTFDocPage.xml b/dev/tests/functional/tests/MFTF/DevDocs/Page/DeprecatedMFTFDocPage.xml new file mode 100644 index 000000000..ee47fe954 --- /dev/null +++ b/dev/tests/functional/tests/MFTF/DevDocs/Page/DeprecatedMFTFDocPage.xml @@ -0,0 +1,14 @@ + + + + + +
+ + diff --git a/dev/tests/functional/tests/MFTF/DevDocs/Section/ContentSection.xml b/dev/tests/functional/tests/MFTF/DevDocs/Section/ContentSection.xml index cf82b69e9..15af0fa39 100644 --- a/dev/tests/functional/tests/MFTF/DevDocs/Section/ContentSection.xml +++ b/dev/tests/functional/tests/MFTF/DevDocs/Section/ContentSection.xml @@ -10,5 +10,6 @@ xsi:noNamespaceSchemaLocation="../../../../../../../src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd">
+
diff --git a/dev/tests/functional/tests/MFTF/DevDocs/Section/DeprecatedContentSection.xml b/dev/tests/functional/tests/MFTF/DevDocs/Section/DeprecatedContentSection.xml new file mode 100644 index 000000000..cc316d6ed --- /dev/null +++ b/dev/tests/functional/tests/MFTF/DevDocs/Section/DeprecatedContentSection.xml @@ -0,0 +1,14 @@ + + + + +
+ +
+
diff --git a/dev/tests/functional/tests/MFTF/DevDocs/Test/DeprecatedDevDocsTest.xml b/dev/tests/functional/tests/MFTF/DevDocs/Test/DeprecatedDevDocsTest.xml new file mode 100644 index 000000000..bf0078df4 --- /dev/null +++ b/dev/tests/functional/tests/MFTF/DevDocs/Test/DeprecatedDevDocsTest.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + <description value="[Deprecated] Magento Functional Testing Framework Documentation is available."/> + <severity value="CRITICAL"/> + <group value="mftf"/> + </annotations> + + <!-- Open MFTF DevDocs Page --> + <amOnPage stepKey="openMFTFDevDocPage" url="{{DeprecatedMFTFDocPage.url}}" /> + <see stepKey="verifyPageIntroText" selector="{{DeprecatedContentSection.pageIntro}}" userInput="{{DeprecatedMessageData.message}}" /> + <actionGroup ref="DeprecatedCommentActionGroup" stepKey="commentActionGroup" /> + </test> +</tests> diff --git a/dev/tests/functional/tests/MFTF/DevDocs/Test/DevDocsTest.xml b/dev/tests/functional/tests/MFTF/DevDocs/Test/DevDocsTest.xml index 3bdeeb9e8..c6b2575b9 100644 --- a/dev/tests/functional/tests/MFTF/DevDocs/Test/DevDocsTest.xml +++ b/dev/tests/functional/tests/MFTF/DevDocs/Test/DevDocsTest.xml @@ -21,6 +21,6 @@ <!-- Open MFTF DevDocs Page --> <amOnPage stepKey="openMFTFDevDocPage" url="{{MFTFDocPage.url}}" /> - <see stepKey="verifyPageIntroText" selector="{{contentSection.pageIntro}}" userInput="Introduction to the Magento Functional Testing Framework" /> + <see stepKey="verifyPageIntroText" selector="{{contentSection.pageIntro}}" userInput="{{MessageData.message}}" /> </test> </tests> diff --git a/dev/tests/unit/Magento/FunctionalTestFramework/Console/BaseGenerateCommandTest.php b/dev/tests/unit/Magento/FunctionalTestFramework/Console/BaseGenerateCommandTest.php index 1b1686ce2..234f677e8 100644 --- a/dev/tests/unit/Magento/FunctionalTestFramework/Console/BaseGenerateCommandTest.php +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Console/BaseGenerateCommandTest.php @@ -156,6 +156,24 @@ public function testThreeTestOneSuiteOneGroupMix() $this->assertEquals($expected, $actual); } + public function testSuiteToTestSyntax() + { + $testOne = new TestObject('Test1', [], [], []); + $suiteOne = new SuiteObject( + 'Suite1', + ['Test1' => $testOne], + [], + [] + ); + + $testArray = ['Test1' => $testOne]; + $suiteArray = ['Suite1' => $suiteOne]; + $this->mockHandlers($testArray, $suiteArray); + $actual = json_decode($this->callTestConfig(['Suite1:Test1']), true); + $expected = ['tests' => null, 'suites' => ['Suite1' => ['Test1']]]; + $this->assertEquals($expected, $actual); + } + /** * Mock handlers to skip parsing * @param array $testArray diff --git a/dev/tests/unit/Magento/FunctionalTestFramework/DataGenerator/Handlers/PersistedObjectHandlerTest.php b/dev/tests/unit/Magento/FunctionalTestFramework/DataGenerator/Handlers/PersistedObjectHandlerTest.php index 6c3823466..592b87f4e 100644 --- a/dev/tests/unit/Magento/FunctionalTestFramework/DataGenerator/Handlers/PersistedObjectHandlerTest.php +++ b/dev/tests/unit/Magento/FunctionalTestFramework/DataGenerator/Handlers/PersistedObjectHandlerTest.php @@ -32,6 +32,30 @@ public function setUp() TestLoggingUtil::getInstance()->setMockLoggingUtil(); } + public function testCreateEntityWithNonExistingName() + { + // Test Data and Variables + $entityName = "InvalidEntity"; + $entityStepKey = "StepKey"; + $scope = PersistedObjectHandler::TEST_SCOPE; + + $exceptionMessage = "Entity \"" . $entityName . "\" does not exist." . + "\nException occurred executing action at StepKey \"" . $entityStepKey . "\""; + + $this->expectException(TestReferenceException::class); + + $this->expectExceptionMessage($exceptionMessage); + + $handler = PersistedObjectHandler::getInstance(); + + // Call method + $handler->createEntity( + $entityStepKey, + $scope, + $entityName + ); + } + public function testCreateSimpleEntity() { // Test Data and Variables diff --git a/dev/tests/unit/Magento/FunctionalTestFramework/DataGenerator/Handlers/SecretStorage/AwsSecretsManagerStorageTest.php b/dev/tests/unit/Magento/FunctionalTestFramework/DataGenerator/Handlers/SecretStorage/AwsSecretsManagerStorageTest.php new file mode 100644 index 000000000..e1f4e4879 --- /dev/null +++ b/dev/tests/unit/Magento/FunctionalTestFramework/DataGenerator/Handlers/SecretStorage/AwsSecretsManagerStorageTest.php @@ -0,0 +1,65 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace tests\unit\Magento\FunctionalTestFramework\DataGenerator\Handlers\SecretStorage; + +use Aws\SecretsManager\SecretsManagerClient; +use Magento\FunctionalTestingFramework\DataGenerator\Handlers\SecretStorage\AwsSecretsManagerStorage; +use Aws\Result; +use Magento\FunctionalTestingFramework\Util\MagentoTestCase; +use ReflectionClass; + +class AwsSecretsManagerStorageTest extends MagentoTestCase +{ + /** + * Test encryption/decryption functionality in AwsSecretsManagerStorage class. + */ + public function testEncryptAndDecrypt() + { + // Setup test data + $testProfile = 'profile'; + $testRegion = 'region'; + $testLongKey = 'magento/myKey'; + $testShortKey = 'myKey'; + $testValue = 'myValue'; + $data = [ + 'Name' => 'mftf/magento/' . $testShortKey, + 'SecretString' => json_encode([$testShortKey => $testValue]) + ]; + /** @var Result */ + $result = new Result($data); + + $mockClient = $this->getMockBuilder(SecretsManagerClient::class) + ->disableOriginalConstructor() + ->setMethods(['__call']) + ->getMock(); + + $mockClient->expects($this->once()) + ->method('__call') + ->willReturnCallback(function ($name, $args) use ($result) { + return $result; + }); + + /** @var SecretsManagerClient */ + $credentialStorage = new AwsSecretsManagerStorage($testRegion, $testProfile); + $reflection = new ReflectionClass($credentialStorage); + $reflection_property = $reflection->getProperty('client'); + $reflection_property->setAccessible(true); + $reflection_property->setValue($credentialStorage, $mockClient); + + // Test getEncryptedValue() + $encryptedCred = $credentialStorage->getEncryptedValue($testLongKey); + + // Assert the value we've gotten is in fact not identical to our test value + $this->assertNotEquals($testValue, $encryptedCred); + + // Test getDecryptedValue() + $actualValue = $credentialStorage->getDecryptedValue($encryptedCred); + + // Assert that we are able to successfully decrypt our secret value + $this->assertEquals($testValue, $actualValue); + } +} diff --git a/dev/tests/unit/Magento/FunctionalTestFramework/Suite/SuiteGeneratorTest.php b/dev/tests/unit/Magento/FunctionalTestFramework/Suite/SuiteGeneratorTest.php index 29158b0f5..e3f820ede 100644 --- a/dev/tests/unit/Magento/FunctionalTestFramework/Suite/SuiteGeneratorTest.php +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Suite/SuiteGeneratorTest.php @@ -6,6 +6,7 @@ namespace Tests\unit\Magento\FunctionalTestFramework\Suite; use AspectMock\Test as AspectMock; +use Magento\FunctionalTestingFramework\Exceptions\TestReferenceException; use Magento\FunctionalTestingFramework\ObjectManager\ObjectManager; use Magento\FunctionalTestingFramework\ObjectManagerFactory; use Magento\FunctionalTestingFramework\Suite\SuiteGenerator; @@ -17,6 +18,7 @@ use Magento\FunctionalTestingFramework\Test\Parsers\TestDataParser; use Magento\FunctionalTestingFramework\Util\Manifest\DefaultTestManifest; use Magento\FunctionalTestingFramework\Util\MagentoTestCase; +use Magento\FunctionalTestingFramework\Util\Manifest\TestManifestFactory; use tests\unit\Util\SuiteDataArrayBuilder; use tests\unit\Util\TestDataArrayBuilder; use tests\unit\Util\TestLoggingUtil; @@ -143,6 +145,73 @@ public function testGenerateEmptySuite() $mockSuiteGenerator->generateSuite("basicTestSuite"); } + public function testInvalidSuiteTestPair() + { + // Mock Suite1 => Test1 and Suite2 => Test2 + $suiteDataArrayBuilder = new SuiteDataArrayBuilder(); + $mockData = $suiteDataArrayBuilder + ->withName('Suite1') + ->includeGroups(['group1']) + ->build(); + $suiteDataArrayBuilder = new SuiteDataArrayBuilder(); + $mockData2 = $suiteDataArrayBuilder + ->withName('Suite2') + ->includeGroups(['group2']) + ->build(); + $mockSuiteData = array_merge_recursive($mockData, $mockData2); + + $testDataArrayBuilder = new TestDataArrayBuilder(); + $mockSimpleTest = $testDataArrayBuilder + ->withName('Test1') + ->withAnnotations(['group' => [['value' => 'group1']]]) + ->withTestActions() + ->build(); + $testDataArrayBuilder = new TestDataArrayBuilder(); + $mockSimpleTest2 = $testDataArrayBuilder + ->withName('Test2') + ->withAnnotations(['group' => [['value' => 'group2']]]) + ->withTestActions() + ->build(); + $mockTestData = ['tests' => array_merge($mockSimpleTest, $mockSimpleTest2)]; + $this->setMockTestAndSuiteParserOutput($mockTestData, $mockSuiteData); + + // Make invalid manifest + $suiteConfig = ['Suite2' => ['Test1']]; + $manifest = TestManifestFactory::makeManifest('default', $suiteConfig); + + // Set up Expected Exception + $this->expectException(TestReferenceException::class); + $this->expectExceptionMessageRegExp('(Suite: "Suite2" Tests: "Test1")'); + + // parse and generate suite object with mocked data and manifest + $mockSuiteGenerator = SuiteGenerator::getInstance(); + $mockSuiteGenerator->generateAllSuites($manifest); + } + + public function testNonExistentSuiteTestPair() + { + $testDataArrayBuilder = new TestDataArrayBuilder(); + $mockSimpleTest = $testDataArrayBuilder + ->withName('Test1') + ->withAnnotations(['group' => [['value' => 'group1']]]) + ->withTestActions() + ->build(); + $mockTestData = ['tests' => array_merge($mockSimpleTest)]; + $this->setMockTestAndSuiteParserOutput($mockTestData, []); + + // Make invalid manifest + $suiteConfig = ['Suite3' => ['Test1']]; + $manifest = TestManifestFactory::makeManifest('default', $suiteConfig); + + // Set up Expected Exception + $this->expectException(TestReferenceException::class); + $this->expectExceptionMessageRegExp('#Suite3 is not defined#'); + + // parse and generate suite object with mocked data and manifest + $mockSuiteGenerator = SuiteGenerator::getInstance(); + $mockSuiteGenerator->generateAllSuites($manifest); + } + /** * Function used to set mock for parser return and force init method to run between tests. * diff --git a/dev/tests/unit/Magento/FunctionalTestFramework/Test/Handlers/TestObjectHandlerTest.php b/dev/tests/unit/Magento/FunctionalTestFramework/Test/Handlers/TestObjectHandlerTest.php index 39e80d556..67f6f2b43 100644 --- a/dev/tests/unit/Magento/FunctionalTestFramework/Test/Handlers/TestObjectHandlerTest.php +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Test/Handlers/TestObjectHandlerTest.php @@ -92,7 +92,7 @@ public function testGetTestObject() [ 'features' => ['NO MODULE DETECTED'], 'group' => ['test'], - 'description' => ['<br><br><b><font size=+0.9>Test files</font></b><br><br>'] + 'description' => ['test_files' => '<h3>Test files</h3>', 'deprecated' => []] ], [ TestObjectExtractor::TEST_BEFORE_HOOK => $expectedBeforeHookObject, diff --git a/dev/tests/verification/Resources/ActionGroupContainsStepKeyInArgText.txt b/dev/tests/verification/Resources/ActionGroupContainsStepKeyInArgText.txt index 1eb3d6566..23e0f0b69 100644 --- a/dev/tests/verification/Resources/ActionGroupContainsStepKeyInArgText.txt +++ b/dev/tests/verification/Resources/ActionGroupContainsStepKeyInArgText.txt @@ -13,7 +13,7 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/ActionGroupTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/ActionGroupTest.xml<br>") */ class ActionGroupContainsStepKeyInArgTextCest { diff --git a/dev/tests/verification/Resources/ActionGroupMergedViaInsertAfter.txt b/dev/tests/verification/Resources/ActionGroupMergedViaInsertAfter.txt index b4745e0c0..da3d84daf 100644 --- a/dev/tests/verification/Resources/ActionGroupMergedViaInsertAfter.txt +++ b/dev/tests/verification/Resources/ActionGroupMergedViaInsertAfter.txt @@ -13,7 +13,7 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/ActionGroupFunctionalTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/ActionGroupFunctionalTest.xml<br>") */ class ActionGroupMergedViaInsertAfterCest { diff --git a/dev/tests/verification/Resources/ActionGroupMergedViaInsertBefore.txt b/dev/tests/verification/Resources/ActionGroupMergedViaInsertBefore.txt index b27328275..22e8148d9 100644 --- a/dev/tests/verification/Resources/ActionGroupMergedViaInsertBefore.txt +++ b/dev/tests/verification/Resources/ActionGroupMergedViaInsertBefore.txt @@ -13,7 +13,7 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/ActionGroupFunctionalTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/ActionGroupFunctionalTest.xml<br>") */ class ActionGroupMergedViaInsertBeforeCest { diff --git a/dev/tests/verification/Resources/ActionGroupSkipReadiness.txt b/dev/tests/verification/Resources/ActionGroupSkipReadiness.txt index 11025fefe..efd5a8cb5 100644 --- a/dev/tests/verification/Resources/ActionGroupSkipReadiness.txt +++ b/dev/tests/verification/Resources/ActionGroupSkipReadiness.txt @@ -13,7 +13,7 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/ActionGroupTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/ActionGroupTest.xml<br>") */ class ActionGroupSkipReadinessCest { diff --git a/dev/tests/verification/Resources/ActionGroupToExtend.txt b/dev/tests/verification/Resources/ActionGroupToExtend.txt index 133c9fb9b..29d08c0b6 100644 --- a/dev/tests/verification/Resources/ActionGroupToExtend.txt +++ b/dev/tests/verification/Resources/ActionGroupToExtend.txt @@ -13,7 +13,7 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/ActionGroupTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/ActionGroupTest.xml<br>") */ class ActionGroupToExtendCest { diff --git a/dev/tests/verification/Resources/ActionGroupUsingCreateData.txt b/dev/tests/verification/Resources/ActionGroupUsingCreateData.txt index fb76b4f44..b64822cf6 100644 --- a/dev/tests/verification/Resources/ActionGroupUsingCreateData.txt +++ b/dev/tests/verification/Resources/ActionGroupUsingCreateData.txt @@ -13,7 +13,7 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/ActionGroupTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/ActionGroupTest.xml<br>") */ class ActionGroupUsingCreateDataCest { diff --git a/dev/tests/verification/Resources/ActionGroupUsingNestedArgument.txt b/dev/tests/verification/Resources/ActionGroupUsingNestedArgument.txt index 5814e85e0..5eb67f577 100644 --- a/dev/tests/verification/Resources/ActionGroupUsingNestedArgument.txt +++ b/dev/tests/verification/Resources/ActionGroupUsingNestedArgument.txt @@ -13,7 +13,7 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/ActionGroupTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/ActionGroupTest.xml<br>") */ class ActionGroupUsingNestedArgumentCest { diff --git a/dev/tests/verification/Resources/ActionGroupWithDataOverrideTest.txt b/dev/tests/verification/Resources/ActionGroupWithDataOverrideTest.txt index a455d98e0..bb6404315 100644 --- a/dev/tests/verification/Resources/ActionGroupWithDataOverrideTest.txt +++ b/dev/tests/verification/Resources/ActionGroupWithDataOverrideTest.txt @@ -14,7 +14,7 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @group functional - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/ActionGroupFunctionalTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/ActionGroupFunctionalTest.xml<br>") */ class ActionGroupWithDataOverrideTestCest { diff --git a/dev/tests/verification/Resources/ActionGroupWithDataTest.txt b/dev/tests/verification/Resources/ActionGroupWithDataTest.txt index 7ac32f550..6b22f83e0 100644 --- a/dev/tests/verification/Resources/ActionGroupWithDataTest.txt +++ b/dev/tests/verification/Resources/ActionGroupWithDataTest.txt @@ -14,7 +14,7 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @group functional - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/ActionGroupFunctionalTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/ActionGroupFunctionalTest.xml<br>") */ class ActionGroupWithDataTestCest { diff --git a/dev/tests/verification/Resources/ActionGroupWithDefaultArgumentAndStringSelectorParam.txt b/dev/tests/verification/Resources/ActionGroupWithDefaultArgumentAndStringSelectorParam.txt index 289bbe4e1..ca5f72cc0 100644 --- a/dev/tests/verification/Resources/ActionGroupWithDefaultArgumentAndStringSelectorParam.txt +++ b/dev/tests/verification/Resources/ActionGroupWithDefaultArgumentAndStringSelectorParam.txt @@ -14,7 +14,7 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @Title("[NO TESTCASEID]: Action Group With Default Argument Value and Hardcoded Value in Param") - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/ActionGroupTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/ActionGroupTest.xml<br>") */ class ActionGroupWithDefaultArgumentAndStringSelectorParamCest { diff --git a/dev/tests/verification/Resources/ActionGroupWithMultipleParameterSelectorsFromDefaultArgument.txt b/dev/tests/verification/Resources/ActionGroupWithMultipleParameterSelectorsFromDefaultArgument.txt index 4e94978ad..f3ad492b0 100644 --- a/dev/tests/verification/Resources/ActionGroupWithMultipleParameterSelectorsFromDefaultArgument.txt +++ b/dev/tests/verification/Resources/ActionGroupWithMultipleParameterSelectorsFromDefaultArgument.txt @@ -14,7 +14,7 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @Title("[NO TESTCASEID]: Action Group With Passed Argument Value and Multiple Argument Values in Param") - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/ActionGroupTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/ActionGroupTest.xml<br>") */ class ActionGroupWithMultipleParameterSelectorsFromDefaultArgumentCest { diff --git a/dev/tests/verification/Resources/ActionGroupWithNoArguments.txt b/dev/tests/verification/Resources/ActionGroupWithNoArguments.txt index 6158b5af7..0f69c40e8 100644 --- a/dev/tests/verification/Resources/ActionGroupWithNoArguments.txt +++ b/dev/tests/verification/Resources/ActionGroupWithNoArguments.txt @@ -14,7 +14,7 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @Title("[NO TESTCASEID]: Action Group With No Argument") - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/ActionGroupTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/ActionGroupTest.xml<br>") */ class ActionGroupWithNoArgumentsCest { diff --git a/dev/tests/verification/Resources/ActionGroupWithNoDefaultTest.txt b/dev/tests/verification/Resources/ActionGroupWithNoDefaultTest.txt index ca6474a7b..533e14b9d 100644 --- a/dev/tests/verification/Resources/ActionGroupWithNoDefaultTest.txt +++ b/dev/tests/verification/Resources/ActionGroupWithNoDefaultTest.txt @@ -14,7 +14,7 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @group functional - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/ActionGroupFunctionalTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/ActionGroupFunctionalTest.xml<br>") */ class ActionGroupWithNoDefaultTestCest { diff --git a/dev/tests/verification/Resources/ActionGroupWithParameterizedElementWithHyphen.txt b/dev/tests/verification/Resources/ActionGroupWithParameterizedElementWithHyphen.txt index 52dc8bd1b..acc6aa58a 100644 --- a/dev/tests/verification/Resources/ActionGroupWithParameterizedElementWithHyphen.txt +++ b/dev/tests/verification/Resources/ActionGroupWithParameterizedElementWithHyphen.txt @@ -13,7 +13,7 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/ActionGroupTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/ActionGroupTest.xml<br>") */ class ActionGroupWithParameterizedElementWithHyphenCest { diff --git a/dev/tests/verification/Resources/ActionGroupWithParameterizedElementsWithStepKeyReferences.txt b/dev/tests/verification/Resources/ActionGroupWithParameterizedElementsWithStepKeyReferences.txt index b0758e679..ac20299a8 100644 --- a/dev/tests/verification/Resources/ActionGroupWithParameterizedElementsWithStepKeyReferences.txt +++ b/dev/tests/verification/Resources/ActionGroupWithParameterizedElementsWithStepKeyReferences.txt @@ -13,7 +13,7 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/ActionGroupTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/ActionGroupTest.xml<br>") */ class ActionGroupWithParameterizedElementsWithStepKeyReferencesCest { diff --git a/dev/tests/verification/Resources/ActionGroupWithPassedArgumentAndStringSelectorParam.txt b/dev/tests/verification/Resources/ActionGroupWithPassedArgumentAndStringSelectorParam.txt index 254fbe0f2..e816da90b 100644 --- a/dev/tests/verification/Resources/ActionGroupWithPassedArgumentAndStringSelectorParam.txt +++ b/dev/tests/verification/Resources/ActionGroupWithPassedArgumentAndStringSelectorParam.txt @@ -14,7 +14,7 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @Title("[NO TESTCASEID]: Action Group With Passed Argument Value and Hardcoded Value in Param") - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/ActionGroupTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/ActionGroupTest.xml<br>") */ class ActionGroupWithPassedArgumentAndStringSelectorParamCest { diff --git a/dev/tests/verification/Resources/ActionGroupWithPersistedData.txt b/dev/tests/verification/Resources/ActionGroupWithPersistedData.txt index e8eb29996..7d0bb58e9 100644 --- a/dev/tests/verification/Resources/ActionGroupWithPersistedData.txt +++ b/dev/tests/verification/Resources/ActionGroupWithPersistedData.txt @@ -14,7 +14,7 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @group functional - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/ActionGroupFunctionalTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/ActionGroupFunctionalTest.xml<br>") */ class ActionGroupWithPersistedDataCest { diff --git a/dev/tests/verification/Resources/ActionGroupWithSectionAndDataAsArguments.txt b/dev/tests/verification/Resources/ActionGroupWithSectionAndDataAsArguments.txt index ec6111062..f629f32f1 100644 --- a/dev/tests/verification/Resources/ActionGroupWithSectionAndDataAsArguments.txt +++ b/dev/tests/verification/Resources/ActionGroupWithSectionAndDataAsArguments.txt @@ -13,7 +13,7 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/ActionGroupTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/ActionGroupTest.xml<br>") */ class ActionGroupWithSectionAndDataAsArgumentsCest { diff --git a/dev/tests/verification/Resources/ActionGroupWithSimpleDataUsageFromDefaultArgument.txt b/dev/tests/verification/Resources/ActionGroupWithSimpleDataUsageFromDefaultArgument.txt index f4faf0773..10206867a 100644 --- a/dev/tests/verification/Resources/ActionGroupWithSimpleDataUsageFromDefaultArgument.txt +++ b/dev/tests/verification/Resources/ActionGroupWithSimpleDataUsageFromDefaultArgument.txt @@ -14,7 +14,7 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @Title("[NO TESTCASEID]: Action Group With Simple Data Usage From Default Argument") - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/ActionGroupTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/ActionGroupTest.xml<br>") */ class ActionGroupWithSimpleDataUsageFromDefaultArgumentCest { diff --git a/dev/tests/verification/Resources/ActionGroupWithSimpleDataUsageFromPassedArgument.txt b/dev/tests/verification/Resources/ActionGroupWithSimpleDataUsageFromPassedArgument.txt index 86791407d..54aec35a7 100644 --- a/dev/tests/verification/Resources/ActionGroupWithSimpleDataUsageFromPassedArgument.txt +++ b/dev/tests/verification/Resources/ActionGroupWithSimpleDataUsageFromPassedArgument.txt @@ -14,7 +14,7 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @Title("[NO TESTCASEID]: Action Group With Simple Data Usage From Passed Argument") - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/ActionGroupTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/ActionGroupTest.xml<br>") */ class ActionGroupWithSimpleDataUsageFromPassedArgumentCest { diff --git a/dev/tests/verification/Resources/ActionGroupWithSingleParameterSelectorFromDefaultArgument.txt b/dev/tests/verification/Resources/ActionGroupWithSingleParameterSelectorFromDefaultArgument.txt index 5d8298108..c23b99cab 100644 --- a/dev/tests/verification/Resources/ActionGroupWithSingleParameterSelectorFromDefaultArgument.txt +++ b/dev/tests/verification/Resources/ActionGroupWithSingleParameterSelectorFromDefaultArgument.txt @@ -14,7 +14,7 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @Title("[NO TESTCASEID]: Action Group With Default Argument Value and Argument Value in Param") - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/ActionGroupTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/ActionGroupTest.xml<br>") */ class ActionGroupWithSingleParameterSelectorFromDefaultArgumentCest { diff --git a/dev/tests/verification/Resources/ActionGroupWithSingleParameterSelectorFromPassedArgument.txt b/dev/tests/verification/Resources/ActionGroupWithSingleParameterSelectorFromPassedArgument.txt index 283a9fdd7..4a0932b99 100644 --- a/dev/tests/verification/Resources/ActionGroupWithSingleParameterSelectorFromPassedArgument.txt +++ b/dev/tests/verification/Resources/ActionGroupWithSingleParameterSelectorFromPassedArgument.txt @@ -14,7 +14,7 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @Title("[NO TESTCASEID]: Action Group With Passed Argument Value and Argument Value in Param") - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/ActionGroupTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/ActionGroupTest.xml<br>") */ class ActionGroupWithSingleParameterSelectorFromPassedArgumentCest { diff --git a/dev/tests/verification/Resources/ActionGroupWithStepKeyReferences.txt b/dev/tests/verification/Resources/ActionGroupWithStepKeyReferences.txt index 2ee5b4bf9..8098142fd 100644 --- a/dev/tests/verification/Resources/ActionGroupWithStepKeyReferences.txt +++ b/dev/tests/verification/Resources/ActionGroupWithStepKeyReferences.txt @@ -13,7 +13,7 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/ActionGroupTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/ActionGroupTest.xml<br>") */ class ActionGroupWithStepKeyReferencesCest { diff --git a/dev/tests/verification/Resources/ActionGroupWithTopLevelPersistedData.txt b/dev/tests/verification/Resources/ActionGroupWithTopLevelPersistedData.txt index ca8325402..a8f80eae6 100644 --- a/dev/tests/verification/Resources/ActionGroupWithTopLevelPersistedData.txt +++ b/dev/tests/verification/Resources/ActionGroupWithTopLevelPersistedData.txt @@ -14,7 +14,7 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @group functional - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/ActionGroupFunctionalTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/ActionGroupFunctionalTest.xml<br>") */ class ActionGroupWithTopLevelPersistedDataCest { diff --git a/dev/tests/verification/Resources/ArgumentWithSameNameAsElement.txt b/dev/tests/verification/Resources/ArgumentWithSameNameAsElement.txt index 44a00dbd9..e45207249 100644 --- a/dev/tests/verification/Resources/ArgumentWithSameNameAsElement.txt +++ b/dev/tests/verification/Resources/ArgumentWithSameNameAsElement.txt @@ -14,7 +14,7 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @group functional - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/ActionGroupFunctionalTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/ActionGroupFunctionalTest.xml<br>") */ class ArgumentWithSameNameAsElementCest { diff --git a/dev/tests/verification/Resources/AssertTest.txt b/dev/tests/verification/Resources/AssertTest.txt index e7f24b605..bb29759d5 100644 --- a/dev/tests/verification/Resources/AssertTest.txt +++ b/dev/tests/verification/Resources/AssertTest.txt @@ -13,7 +13,7 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/AssertTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/AssertTest.xml<br>") */ class AssertTestCest { diff --git a/dev/tests/verification/Resources/BasicActionGroupTest.txt b/dev/tests/verification/Resources/BasicActionGroupTest.txt index 02915d1a8..7d8c40f0b 100644 --- a/dev/tests/verification/Resources/BasicActionGroupTest.txt +++ b/dev/tests/verification/Resources/BasicActionGroupTest.txt @@ -14,7 +14,7 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @group functional - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/ActionGroupFunctionalTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/ActionGroupFunctionalTest.xml<br>") */ class BasicActionGroupTestCest { diff --git a/dev/tests/verification/Resources/BasicFunctionalTest.txt b/dev/tests/verification/Resources/BasicFunctionalTest.txt index 5bcfd55dd..ac42f395e 100644 --- a/dev/tests/verification/Resources/BasicFunctionalTest.txt +++ b/dev/tests/verification/Resources/BasicFunctionalTest.txt @@ -15,7 +15,7 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @Title("[NO TESTCASEID]: A Functional Cest") * @group functional - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/BasicFunctionalTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/BasicFunctionalTest.xml<br>") */ class BasicFunctionalTestCest { @@ -126,6 +126,12 @@ class BasicFunctionalTestCest $I->comment($magentoCli3); // stepKey: magentoCli3 $magentoCli4 = $I->magentoCLISecret("config:set somePath " . $I->getSecret("someKey"), 120); // stepKey: magentoCli4 $I->comment($magentoCli4); // stepKey: magentoCli4 + $cronAllGroups = $I->magentoCron("", 70); // stepKey: cronAllGroups + $I->comment($cronAllGroups); + $cronSingleGroup = $I->magentoCron("index", 70); // stepKey: cronSingleGroup + $I->comment($cronSingleGroup); + $cronMultipleGroups = $I->magentoCron("a b c", 70); // stepKey: cronMultipleGroups + $I->comment($cronMultipleGroups); $I->makeScreenshot("screenShotInput"); // stepKey: makeScreenshotKey1 $I->maximizeWindow(); // stepKey: maximizeWindowKey1 $I->moveBack(); // stepKey: moveBackKey1 diff --git a/dev/tests/verification/Resources/BasicMergeTest.txt b/dev/tests/verification/Resources/BasicMergeTest.txt index c6fb58e3a..8171cd892 100644 --- a/dev/tests/verification/Resources/BasicMergeTest.txt +++ b/dev/tests/verification/Resources/BasicMergeTest.txt @@ -16,7 +16,7 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; * @Title("[NO TESTCASEID]: BasicMergeTest") * @group functional * @group mergeTest - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/MergeFunctionalTest.xml<br>verification/TestModuleMerged/Test/MergeFunctionalTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/MergeFunctionalTest.xml<br>verification/TestModuleMerged/Test/MergeFunctionalTest.xml<br>") */ class BasicMergeTestCest { diff --git a/dev/tests/verification/Resources/CharacterReplacementTest.txt b/dev/tests/verification/Resources/CharacterReplacementTest.txt index 61729cc8b..24d8e2ab1 100644 --- a/dev/tests/verification/Resources/CharacterReplacementTest.txt +++ b/dev/tests/verification/Resources/CharacterReplacementTest.txt @@ -13,7 +13,7 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/CharacterReplacementTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/CharacterReplacementTest.xml<br>") */ class CharacterReplacementTestCest { diff --git a/dev/tests/verification/Resources/ChildExtendedTestAddHooks.txt b/dev/tests/verification/Resources/ChildExtendedTestAddHooks.txt index 8362cf513..86d81f848 100644 --- a/dev/tests/verification/Resources/ChildExtendedTestAddHooks.txt +++ b/dev/tests/verification/Resources/ChildExtendedTestAddHooks.txt @@ -15,7 +15,7 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @Title("[NO TESTCASEID]: ChildExtendedTestAddHooks") * @group Parent - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/ExtendedFunctionalTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/ExtendedFunctionalTest.xml<br>") */ class ChildExtendedTestAddHooksCest { diff --git a/dev/tests/verification/Resources/ChildExtendedTestMerging.txt b/dev/tests/verification/Resources/ChildExtendedTestMerging.txt index 590ba6003..08345bd86 100644 --- a/dev/tests/verification/Resources/ChildExtendedTestMerging.txt +++ b/dev/tests/verification/Resources/ChildExtendedTestMerging.txt @@ -15,7 +15,7 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @Title("[NO TESTCASEID]: ChildExtendedTestMerging") * @group Child - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/ExtendedFunctionalTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/ExtendedFunctionalTest.xml<br>") */ class ChildExtendedTestMergingCest { diff --git a/dev/tests/verification/Resources/ChildExtendedTestNoParent.txt b/dev/tests/verification/Resources/ChildExtendedTestNoParent.txt index 5d72176c5..4cca6ec06 100644 --- a/dev/tests/verification/Resources/ChildExtendedTestNoParent.txt +++ b/dev/tests/verification/Resources/ChildExtendedTestNoParent.txt @@ -15,7 +15,7 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @Title("[NO TESTCASEID]: ChildExtendedTestNoParent") * @group Child - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/ExtendedFunctionalTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/ExtendedFunctionalTest.xml<br>") * @group skip */ class ChildExtendedTestNoParentCest diff --git a/dev/tests/verification/Resources/ChildExtendedTestRemoveAction.txt b/dev/tests/verification/Resources/ChildExtendedTestRemoveAction.txt index 766bfd331..370ffe404 100644 --- a/dev/tests/verification/Resources/ChildExtendedTestRemoveAction.txt +++ b/dev/tests/verification/Resources/ChildExtendedTestRemoveAction.txt @@ -15,7 +15,7 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @Title("[NO TESTCASEID]: ChildExtendedTestRemoveAction") * @group Child - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/ExtendedFunctionalTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/ExtendedFunctionalTest.xml<br>") */ class ChildExtendedTestRemoveActionCest { diff --git a/dev/tests/verification/Resources/ChildExtendedTestRemoveHookAction.txt b/dev/tests/verification/Resources/ChildExtendedTestRemoveHookAction.txt index 8497c29af..50b6e030d 100644 --- a/dev/tests/verification/Resources/ChildExtendedTestRemoveHookAction.txt +++ b/dev/tests/verification/Resources/ChildExtendedTestRemoveHookAction.txt @@ -15,7 +15,7 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @Title("[NO TESTCASEID]: ChildExtendedTestRemoveHookAction") * @group Child - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/ExtendedFunctionalTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/ExtendedFunctionalTest.xml<br>") */ class ChildExtendedTestRemoveHookActionCest { diff --git a/dev/tests/verification/Resources/ChildExtendedTestReplace.txt b/dev/tests/verification/Resources/ChildExtendedTestReplace.txt index 3b2e9d369..ce32f6af1 100644 --- a/dev/tests/verification/Resources/ChildExtendedTestReplace.txt +++ b/dev/tests/verification/Resources/ChildExtendedTestReplace.txt @@ -15,7 +15,7 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @Title("[NO TESTCASEID]: ChildExtendedTestReplace") * @group Child - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/ExtendedFunctionalTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/ExtendedFunctionalTest.xml<br>") */ class ChildExtendedTestReplaceCest { diff --git a/dev/tests/verification/Resources/ChildExtendedTestReplaceHook.txt b/dev/tests/verification/Resources/ChildExtendedTestReplaceHook.txt index 53ae22361..bbb6c5ef9 100644 --- a/dev/tests/verification/Resources/ChildExtendedTestReplaceHook.txt +++ b/dev/tests/verification/Resources/ChildExtendedTestReplaceHook.txt @@ -15,7 +15,7 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @Title("[NO TESTCASEID]: ChildExtendedTestReplaceHook") * @group Child - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/ExtendedFunctionalTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/ExtendedFunctionalTest.xml<br>") */ class ChildExtendedTestReplaceHookCest { diff --git a/dev/tests/verification/Resources/DataActionsTest.txt b/dev/tests/verification/Resources/DataActionsTest.txt index c3f8400b3..84a94174a 100644 --- a/dev/tests/verification/Resources/DataActionsTest.txt +++ b/dev/tests/verification/Resources/DataActionsTest.txt @@ -13,7 +13,7 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/DataActionsTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/DataActionsTest.xml<br>") */ class DataActionsTestCest { diff --git a/dev/tests/verification/Resources/DataReplacementTest.txt b/dev/tests/verification/Resources/DataReplacementTest.txt index 2912ee8a2..b75d93f0d 100644 --- a/dev/tests/verification/Resources/DataReplacementTest.txt +++ b/dev/tests/verification/Resources/DataReplacementTest.txt @@ -13,7 +13,7 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/DataReplacementTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/DataReplacementTest.xml<br>") */ class DataReplacementTestCest { diff --git a/dev/tests/verification/Resources/DeprecatedEntitiesTest.txt b/dev/tests/verification/Resources/DeprecatedEntitiesTest.txt new file mode 100644 index 000000000..489cb66ea --- /dev/null +++ b/dev/tests/verification/Resources/DeprecatedEntitiesTest.txt @@ -0,0 +1,34 @@ +<?php +namespace Magento\AcceptanceTest\_default\Backend; + +use Magento\FunctionalTestingFramework\AcceptanceTester; +use \Codeception\Util\Locator; +use Yandex\Allure\Adapter\Annotation\Features; +use Yandex\Allure\Adapter\Annotation\Stories; +use Yandex\Allure\Adapter\Annotation\Title; +use Yandex\Allure\Adapter\Annotation\Description; +use Yandex\Allure\Adapter\Annotation\Parameter; +use Yandex\Allure\Adapter\Annotation\Severity; +use Yandex\Allure\Adapter\Model\SeverityLevel; +use Yandex\Allure\Adapter\Annotation\TestCaseId; + +/** + * @Description("<h3 class='y-label y-label_status_broken'>Deprecated Notice(s):</h3><ul><li>DEPRECATED ACTION GROUP in Test: DeprecatedActionGroup Deprecated action group</li><li>DEPRECATED SECTION in Test: {{DeprecatedSection.deprecatedElement}} Deprecated section</li><li>DEPRECATED ELEMENT in Test: {{DeprecatedSection.deprecatedElement}} Deprecated element</li><li>DEPRECATED DATA ENTITY in Test: {{DeprecatedData.field}} Data entity deprecated</li><li>DEPRECATED PAGE in Test: {{DeprecatedPage.url}} Deprecated page</li></ul><h3>Test files</h3>verification/TestModule/Test/DeprecatedEntitiesTest.xml<br>") + */ +class DeprecatedEntitiesTestCest +{ + /** + * @Features({"TestModule"}) + * @Parameter(name = "AcceptanceTester", value="$I") + * @param AcceptanceTester $I + * @return void + * @throws \Exception + */ + public function DeprecatedEntitiesTest(AcceptanceTester $I) + { + $I->comment("Entering Action Group [deprecatedActionGroup] DeprecatedActionGroup"); + $I->see("deprecated", "#element"); // stepKey: deprecatedSeeDeprecatedActionGroup + $I->comment("Exiting Action Group [deprecatedActionGroup] DeprecatedActionGroup"); + $I->amOnPage("/test.html"); // stepKey: amOnPage + } +} diff --git a/dev/tests/verification/Resources/DeprecatedTest.txt b/dev/tests/verification/Resources/DeprecatedTest.txt new file mode 100644 index 000000000..845d13912 --- /dev/null +++ b/dev/tests/verification/Resources/DeprecatedTest.txt @@ -0,0 +1,34 @@ +<?php +namespace Magento\AcceptanceTest\_default\Backend; + +use Magento\FunctionalTestingFramework\AcceptanceTester; +use \Codeception\Util\Locator; +use Yandex\Allure\Adapter\Annotation\Features; +use Yandex\Allure\Adapter\Annotation\Stories; +use Yandex\Allure\Adapter\Annotation\Title; +use Yandex\Allure\Adapter\Annotation\Description; +use Yandex\Allure\Adapter\Annotation\Parameter; +use Yandex\Allure\Adapter\Annotation\Severity; +use Yandex\Allure\Adapter\Model\SeverityLevel; +use Yandex\Allure\Adapter\Annotation\TestCaseId; + +/** + * @Description("<h3 class='y-label y-label_status_broken'>Deprecated Notice(s):</h3><ul><li>Test is deprecated</li><li>DEPRECATED ACTION GROUP in Test: DeprecatedActionGroup Deprecated action group</li><li>DEPRECATED SECTION in Test: {{DeprecatedSection.deprecatedElement}} Deprecated section</li><li>DEPRECATED ELEMENT in Test: {{DeprecatedSection.deprecatedElement}} Deprecated element</li><li>DEPRECATED DATA ENTITY in Test: {{DeprecatedData.field}} Data entity deprecated</li><li>DEPRECATED PAGE in Test: {{DeprecatedPage.url}} Deprecated page</li></ul><h3>Test files</h3>verification/TestModule/Test/DeprecatedTest.xml<br>") + */ +class DeprecatedTestCest +{ + /** + * @Features({"TestModule"}) + * @Parameter(name = "AcceptanceTester", value="$I") + * @param AcceptanceTester $I + * @return void + * @throws \Exception + */ + public function DeprecatedTest(AcceptanceTester $I) + { + $I->comment("Entering Action Group [deprecatedActionGroup] DeprecatedActionGroup"); + $I->see("deprecated", "#element"); // stepKey: deprecatedSeeDeprecatedActionGroup + $I->comment("Exiting Action Group [deprecatedActionGroup] DeprecatedActionGroup"); + $I->amOnPage("/test.html"); // stepKey: amOnPage + } +} diff --git a/dev/tests/verification/Resources/ExecuteInSeleniumTest.txt b/dev/tests/verification/Resources/ExecuteInSeleniumTest.txt index cfd7a14ce..bbed61691 100644 --- a/dev/tests/verification/Resources/ExecuteInSeleniumTest.txt +++ b/dev/tests/verification/Resources/ExecuteInSeleniumTest.txt @@ -13,7 +13,7 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/ExecuteInSeleniumTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/ExecuteInSeleniumTest.xml<br>") */ class ExecuteInSeleniumTestCest { diff --git a/dev/tests/verification/Resources/ExecuteJsEscapingTest.txt b/dev/tests/verification/Resources/ExecuteJsEscapingTest.txt index d0b7acc83..ad3696e00 100644 --- a/dev/tests/verification/Resources/ExecuteJsEscapingTest.txt +++ b/dev/tests/verification/Resources/ExecuteJsEscapingTest.txt @@ -13,7 +13,7 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/ExecuteJsTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/ExecuteJsTest.xml<br>") */ class ExecuteJsEscapingTestCest { diff --git a/dev/tests/verification/Resources/ExtendParentDataTest.txt b/dev/tests/verification/Resources/ExtendParentDataTest.txt index d26d91b08..70cb6b70b 100644 --- a/dev/tests/verification/Resources/ExtendParentDataTest.txt +++ b/dev/tests/verification/Resources/ExtendParentDataTest.txt @@ -13,7 +13,7 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/ExtendedDataTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/ExtendedDataTest.xml<br>") */ class ExtendParentDataTestCest { diff --git a/dev/tests/verification/Resources/ExtendedActionGroup.txt b/dev/tests/verification/Resources/ExtendedActionGroup.txt index 3158161d9..8996526ba 100644 --- a/dev/tests/verification/Resources/ExtendedActionGroup.txt +++ b/dev/tests/verification/Resources/ExtendedActionGroup.txt @@ -13,7 +13,7 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/ActionGroupTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/ActionGroupTest.xml<br>") */ class ExtendedActionGroupCest { diff --git a/dev/tests/verification/Resources/ExtendedChildTestInSuiteCest.txt b/dev/tests/verification/Resources/ExtendedChildTestInSuiteCest.txt index bde2ce2b3..e4a006c8f 100644 --- a/dev/tests/verification/Resources/ExtendedChildTestInSuiteCest.txt +++ b/dev/tests/verification/Resources/ExtendedChildTestInSuiteCest.txt @@ -15,7 +15,7 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @Title("[NO TESTCASEID]: ExtendedChildTestInSuite") * @group ExtendedTestInSuite - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/ExtendedFunctionalTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/ExtendedFunctionalTest.xml<br>") */ class ExtendedChildTestInSuiteCest { diff --git a/dev/tests/verification/Resources/ExtendedChildTestNotInSuite.txt b/dev/tests/verification/Resources/ExtendedChildTestNotInSuite.txt index fc67be840..1d5af7d3e 100644 --- a/dev/tests/verification/Resources/ExtendedChildTestNotInSuite.txt +++ b/dev/tests/verification/Resources/ExtendedChildTestNotInSuite.txt @@ -14,7 +14,7 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @Title("[NO TESTCASEID]: ExtendedChildTestNotInSuite") - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/ExtendedFunctionalTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/ExtendedFunctionalTest.xml<br>") */ class ExtendedChildTestNotInSuiteCest { diff --git a/dev/tests/verification/Resources/ExtendedRemoveActionGroup.txt b/dev/tests/verification/Resources/ExtendedRemoveActionGroup.txt index 5ba7a3cce..fde8842e7 100644 --- a/dev/tests/verification/Resources/ExtendedRemoveActionGroup.txt +++ b/dev/tests/verification/Resources/ExtendedRemoveActionGroup.txt @@ -13,7 +13,7 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/ActionGroupTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/ActionGroupTest.xml<br>") */ class ExtendedRemoveActionGroupCest { diff --git a/dev/tests/verification/Resources/ExtendingSkippedTest.txt b/dev/tests/verification/Resources/ExtendingSkippedTest.txt index 73a1c89d7..93e9b6a60 100644 --- a/dev/tests/verification/Resources/ExtendingSkippedTest.txt +++ b/dev/tests/verification/Resources/ExtendingSkippedTest.txt @@ -15,7 +15,7 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @Title("[NO TESTCASEID]: ChildExtendedTestSkippedParent") * @group Child - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/ExtendedFunctionalTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/ExtendedFunctionalTest.xml<br>") */ class ExtendingSkippedTestCest { diff --git a/dev/tests/verification/Resources/HookActionsTest.txt b/dev/tests/verification/Resources/HookActionsTest.txt index c61cc86e2..0b77325c1 100644 --- a/dev/tests/verification/Resources/HookActionsTest.txt +++ b/dev/tests/verification/Resources/HookActionsTest.txt @@ -13,7 +13,7 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/HookActionsTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/HookActionsTest.xml<br>") */ class HookActionsTestCest { diff --git a/dev/tests/verification/Resources/LocatorFunctionTest.txt b/dev/tests/verification/Resources/LocatorFunctionTest.txt index f00392778..76c48a921 100644 --- a/dev/tests/verification/Resources/LocatorFunctionTest.txt +++ b/dev/tests/verification/Resources/LocatorFunctionTest.txt @@ -13,7 +13,7 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/LocatorFunctionTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/LocatorFunctionTest.xml<br>") */ class LocatorFunctionTestCest { diff --git a/dev/tests/verification/Resources/MergeMassViaInsertAfter.txt b/dev/tests/verification/Resources/MergeMassViaInsertAfter.txt index 90c8c5373..a57ca4471 100644 --- a/dev/tests/verification/Resources/MergeMassViaInsertAfter.txt +++ b/dev/tests/verification/Resources/MergeMassViaInsertAfter.txt @@ -13,7 +13,7 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/BasicFunctionalTest.xml<br>verification/TestModule/Test/MergeFunctionalTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/BasicFunctionalTest.xml<br>verification/TestModule/Test/MergeFunctionalTest.xml<br>") */ class MergeMassViaInsertAfterCest { diff --git a/dev/tests/verification/Resources/MergeMassViaInsertBefore.txt b/dev/tests/verification/Resources/MergeMassViaInsertBefore.txt index df569d66b..7c6fdc906 100644 --- a/dev/tests/verification/Resources/MergeMassViaInsertBefore.txt +++ b/dev/tests/verification/Resources/MergeMassViaInsertBefore.txt @@ -13,7 +13,7 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/BasicFunctionalTest.xml<br>verification/TestModule/Test/MergeFunctionalTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/BasicFunctionalTest.xml<br>verification/TestModule/Test/MergeFunctionalTest.xml<br>") */ class MergeMassViaInsertBeforeCest { diff --git a/dev/tests/verification/Resources/MergeSkip.txt b/dev/tests/verification/Resources/MergeSkip.txt index bf5903065..b2a1b8918 100644 --- a/dev/tests/verification/Resources/MergeSkip.txt +++ b/dev/tests/verification/Resources/MergeSkip.txt @@ -13,7 +13,7 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/MergeFunctionalTest.xml<br>verification/TestModuleMerged/Test/MergeFunctionalTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/MergeFunctionalTest.xml<br>verification/TestModuleMerged/Test/MergeFunctionalTest.xml<br>") */ class MergeSkipCest { diff --git a/dev/tests/verification/Resources/MergedActionGroupTest.txt b/dev/tests/verification/Resources/MergedActionGroupTest.txt index e0067b479..a9c740127 100644 --- a/dev/tests/verification/Resources/MergedActionGroupTest.txt +++ b/dev/tests/verification/Resources/MergedActionGroupTest.txt @@ -14,7 +14,7 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @group functional - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/ActionGroupFunctionalTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/ActionGroupFunctionalTest.xml<br>") */ class MergedActionGroupTestCest { diff --git a/dev/tests/verification/Resources/MergedReferencesTest.txt b/dev/tests/verification/Resources/MergedReferencesTest.txt index c4cc7276e..b3fc29966 100644 --- a/dev/tests/verification/Resources/MergedReferencesTest.txt +++ b/dev/tests/verification/Resources/MergedReferencesTest.txt @@ -15,7 +15,7 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @Title("[NO TESTCASEID]: MergedReferencesTest") * @group functional - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/MergeFunctionalTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/MergeFunctionalTest.xml<br>") */ class MergedReferencesTestCest { diff --git a/dev/tests/verification/Resources/MultipleActionGroupsTest.txt b/dev/tests/verification/Resources/MultipleActionGroupsTest.txt index 8e5ea542e..5f1db5081 100644 --- a/dev/tests/verification/Resources/MultipleActionGroupsTest.txt +++ b/dev/tests/verification/Resources/MultipleActionGroupsTest.txt @@ -14,7 +14,7 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @group functional - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/ActionGroupFunctionalTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/ActionGroupFunctionalTest.xml<br>") */ class MultipleActionGroupsTestCest { diff --git a/dev/tests/verification/Resources/PageReplacementTest.txt b/dev/tests/verification/Resources/PageReplacementTest.txt index f90a4fda7..cda119999 100644 --- a/dev/tests/verification/Resources/PageReplacementTest.txt +++ b/dev/tests/verification/Resources/PageReplacementTest.txt @@ -13,7 +13,7 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/PageReplacementTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/PageReplacementTest.xml<br>") */ class PageReplacementTestCest { diff --git a/dev/tests/verification/Resources/ParameterArrayTest.txt b/dev/tests/verification/Resources/ParameterArrayTest.txt index af47657ea..8918bb619 100644 --- a/dev/tests/verification/Resources/ParameterArrayTest.txt +++ b/dev/tests/verification/Resources/ParameterArrayTest.txt @@ -13,7 +13,7 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/ParameterArrayTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/ParameterArrayTest.xml<br>") */ class ParameterArrayTestCest { diff --git a/dev/tests/verification/Resources/ParentExtendedTest.txt b/dev/tests/verification/Resources/ParentExtendedTest.txt index 5662da744..8c756f010 100644 --- a/dev/tests/verification/Resources/ParentExtendedTest.txt +++ b/dev/tests/verification/Resources/ParentExtendedTest.txt @@ -15,7 +15,7 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @Title("[NO TESTCASEID]: ParentExtendedTest") * @group Parent - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/ExtendedFunctionalTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/ExtendedFunctionalTest.xml<br>") */ class ParentExtendedTestCest { diff --git a/dev/tests/verification/Resources/PersistedAndXmlEntityArguments.txt b/dev/tests/verification/Resources/PersistedAndXmlEntityArguments.txt index 2f86f0af7..f6956f5d9 100644 --- a/dev/tests/verification/Resources/PersistedAndXmlEntityArguments.txt +++ b/dev/tests/verification/Resources/PersistedAndXmlEntityArguments.txt @@ -13,7 +13,7 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/ActionGroupFunctionalTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/ActionGroupFunctionalTest.xml<br>") */ class PersistedAndXmlEntityArgumentsCest { diff --git a/dev/tests/verification/Resources/PersistedReplacementTest.txt b/dev/tests/verification/Resources/PersistedReplacementTest.txt index 009a8eac2..eb1c9995d 100644 --- a/dev/tests/verification/Resources/PersistedReplacementTest.txt +++ b/dev/tests/verification/Resources/PersistedReplacementTest.txt @@ -13,7 +13,7 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/PersistedReplacementTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/PersistedReplacementTest.xml<br>") */ class PersistedReplacementTestCest { diff --git a/dev/tests/verification/Resources/PersistenceActionGroupAppendingTest.txt b/dev/tests/verification/Resources/PersistenceActionGroupAppendingTest.txt index 118db353a..426003bc2 100644 --- a/dev/tests/verification/Resources/PersistenceActionGroupAppendingTest.txt +++ b/dev/tests/verification/Resources/PersistenceActionGroupAppendingTest.txt @@ -13,7 +13,7 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/PersistenceActionGroupAppendingTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/PersistenceActionGroupAppendingTest.xml<br>") */ class PersistenceActionGroupAppendingTestCest { diff --git a/dev/tests/verification/Resources/PersistenceCustomFieldsTest.txt b/dev/tests/verification/Resources/PersistenceCustomFieldsTest.txt index 221373be1..41251d2fb 100644 --- a/dev/tests/verification/Resources/PersistenceCustomFieldsTest.txt +++ b/dev/tests/verification/Resources/PersistenceCustomFieldsTest.txt @@ -13,7 +13,7 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/PersistenceCustomFieldsTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/PersistenceCustomFieldsTest.xml<br>") */ class PersistenceCustomFieldsTestCest { diff --git a/dev/tests/verification/Resources/SectionReplacementTest.txt b/dev/tests/verification/Resources/SectionReplacementTest.txt index 2b2a8bcd6..109c198eb 100644 --- a/dev/tests/verification/Resources/SectionReplacementTest.txt +++ b/dev/tests/verification/Resources/SectionReplacementTest.txt @@ -13,7 +13,7 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/SectionReplacementTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/SectionReplacementTest.xml<br>") */ class SectionReplacementTestCest { diff --git a/dev/tests/verification/Resources/SkippedTest.txt b/dev/tests/verification/Resources/SkippedTest.txt index dcad67e83..95783ca66 100644 --- a/dev/tests/verification/Resources/SkippedTest.txt +++ b/dev/tests/verification/Resources/SkippedTest.txt @@ -14,7 +14,7 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @Title("[NO TESTCASEID]: skippedTest") - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/SkippedTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/SkippedTest.xml<br>") */ class SkippedTestCest { diff --git a/dev/tests/verification/Resources/SkippedTestNoIssues.txt b/dev/tests/verification/Resources/SkippedTestNoIssues.txt index 3a7ca6f59..e8f4726d9 100644 --- a/dev/tests/verification/Resources/SkippedTestNoIssues.txt +++ b/dev/tests/verification/Resources/SkippedTestNoIssues.txt @@ -14,7 +14,7 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @Title("[NO TESTCASEID]: skippedNoIssuesTest") - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/SkippedTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/SkippedTest.xml<br>") * @group skip */ class SkippedTestNoIssuesCest diff --git a/dev/tests/verification/Resources/SkippedTestTwoIssues.txt b/dev/tests/verification/Resources/SkippedTestTwoIssues.txt index d3859978f..4a801856e 100644 --- a/dev/tests/verification/Resources/SkippedTestTwoIssues.txt +++ b/dev/tests/verification/Resources/SkippedTestTwoIssues.txt @@ -14,7 +14,7 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @Title("[NO TESTCASEID]: skippedMultipleIssuesTest") - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/SkippedTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/SkippedTest.xml<br>") */ class SkippedTestTwoIssuesCest { diff --git a/dev/tests/verification/Resources/SkippedTestWithHooks.txt b/dev/tests/verification/Resources/SkippedTestWithHooks.txt index 667be6747..ad59d48c3 100644 --- a/dev/tests/verification/Resources/SkippedTestWithHooks.txt +++ b/dev/tests/verification/Resources/SkippedTestWithHooks.txt @@ -14,7 +14,7 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @Title("[NO TESTCASEID]: skippedTestWithHooks") - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/SkippedTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/SkippedTest.xml<br>") */ class SkippedTestWithHooksCest { diff --git a/dev/tests/verification/Resources/XmlCommentedActionGroupTest.txt b/dev/tests/verification/Resources/XmlCommentedActionGroupTest.txt index 480e35b73..0eb546335 100644 --- a/dev/tests/verification/Resources/XmlCommentedActionGroupTest.txt +++ b/dev/tests/verification/Resources/XmlCommentedActionGroupTest.txt @@ -14,7 +14,7 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @Title("[NO TESTCASEID]: Action Group With comment block in arguments and action group body") - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/XmlCommentedActionGroupTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/XmlCommentedActionGroupTest.xml<br>") */ class XmlCommentedActionGroupTestCest { diff --git a/dev/tests/verification/Resources/XmlCommentedTest.txt b/dev/tests/verification/Resources/XmlCommentedTest.txt index 5b8057099..47d984d9e 100644 --- a/dev/tests/verification/Resources/XmlCommentedTest.txt +++ b/dev/tests/verification/Resources/XmlCommentedTest.txt @@ -14,7 +14,7 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @Title("[NO TESTCASEID]: Test With comment blocks in root element 'tests', in annotations and in test body.") - * @Description("<br><br><b><font size=+0.9>Test files</font></b><br><br>verification/TestModule/Test/XmlCommentedTest.xml<br>") + * @Description("<h3>Test files</h3>verification/TestModule/Test/XmlCommentedTest.xml<br>") */ class XmlCommentedTestCest { diff --git a/dev/tests/verification/TestModule/ActionGroup/DeprecatedActionGroup.xml b/dev/tests/verification/TestModule/ActionGroup/DeprecatedActionGroup.xml new file mode 100644 index 000000000..8f0341c09 --- /dev/null +++ b/dev/tests/verification/TestModule/ActionGroup/DeprecatedActionGroup.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../../../src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + <actionGroup name="DeprecatedActionGroup" deprecated="Deprecated action group"> + <see stepKey="deprecatedSee" userInput="{{DeprecatedData.field}}" selector="{{DeprecatedSection.deprecatedElement}}" /> + </actionGroup> +</actionGroups> diff --git a/dev/tests/verification/TestModule/Data/DeprecatedData.xml b/dev/tests/verification/TestModule/Data/DeprecatedData.xml new file mode 100644 index 000000000..060752419 --- /dev/null +++ b/dev/tests/verification/TestModule/Data/DeprecatedData.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../../../src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="DeprecatedData" deprecated="Data entity deprecated"> + <data key="field">deprecated</data> + </entity> +</entities> diff --git a/dev/tests/verification/TestModule/Page/DeprecatedPage.xml b/dev/tests/verification/TestModule/Page/DeprecatedPage.xml new file mode 100644 index 000000000..316b8b1d7 --- /dev/null +++ b/dev/tests/verification/TestModule/Page/DeprecatedPage.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../../../src/Magento/FunctionalTestingFramework/Page/etc/PageObject.xsd"> + <page name="DeprecatedPage" url="/test.html" area="storefront" module="UnknownVendor_TestModule" deprecated="Deprecated page"> + <section name="SampleSection"/> + </page> +</pages> diff --git a/dev/tests/verification/TestModule/Section/DeprecatedSection.xml b/dev/tests/verification/TestModule/Section/DeprecatedSection.xml new file mode 100644 index 000000000..a9ed20d98 --- /dev/null +++ b/dev/tests/verification/TestModule/Section/DeprecatedSection.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../../../src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + <section name="DeprecatedSection" deprecated="Deprecated section"> + <element name="deprecatedElement" type="button" selector="#element" deprecated="Deprecated element"/> + </section> +</sections> diff --git a/dev/tests/verification/TestModule/Test/BasicFunctionalTest.xml b/dev/tests/verification/TestModule/Test/BasicFunctionalTest.xml index c23a3ce60..1ef25b43d 100644 --- a/dev/tests/verification/TestModule/Test/BasicFunctionalTest.xml +++ b/dev/tests/verification/TestModule/Test/BasicFunctionalTest.xml @@ -79,6 +79,9 @@ <magentoCLI command="maintenance:enable" arguments=""stuffHere"" timeout="120" stepKey="magentoCli2"/> <magentoCLI command="config:set somePath {{_CREDS.someKey}}" stepKey="magentoCli3"/> <magentoCLI command="config:set somePath {{_CREDS.someKey}}" timeout="120" stepKey="magentoCli4"/> + <magentoCron stepKey="cronAllGroups"/> + <magentoCron groups="index" stepKey="cronSingleGroup"/> + <magentoCron groups="a b c" stepKey="cronMultipleGroups"/> <makeScreenshot userInput="screenShotInput" stepKey="makeScreenshotKey1"/> <maximizeWindow stepKey="maximizeWindowKey1"/> <moveBack stepKey="moveBackKey1"/> @@ -143,4 +146,4 @@ <fillField selector="#bar" userInput="bar" stepKey="fillField2"/> <fillField selector="#baz" userInput="baz" stepKey="fillField3"/> </test> -</tests> \ No newline at end of file +</tests> diff --git a/dev/tests/verification/TestModule/Test/DeprecatedEntitiesTest.xml b/dev/tests/verification/TestModule/Test/DeprecatedEntitiesTest.xml new file mode 100644 index 000000000..733a34e80 --- /dev/null +++ b/dev/tests/verification/TestModule/Test/DeprecatedEntitiesTest.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../../../src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + <test name="DeprecatedEntitiesTest"> + <actionGroup ref="DeprecatedActionGroup" stepKey="deprecatedActionGroup" /> + <amOnPage url="{{DeprecatedPage.url}}" stepKey="amOnPage" /> + </test> +</tests> diff --git a/dev/tests/verification/TestModule/Test/DeprecatedTest.xml b/dev/tests/verification/TestModule/Test/DeprecatedTest.xml new file mode 100644 index 000000000..cc5ae4994 --- /dev/null +++ b/dev/tests/verification/TestModule/Test/DeprecatedTest.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../../../src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + <test name="DeprecatedTest" deprecated="Test is deprecated"> + <actionGroup ref="DeprecatedActionGroup" stepKey="deprecatedActionGroup" /> + <amOnPage url="{{DeprecatedPage.url}}" stepKey="amOnPage" /> + </test> +</tests> diff --git a/dev/tests/verification/Tests/DeprecatedTest.php b/dev/tests/verification/Tests/DeprecatedTest.php new file mode 100644 index 000000000..78e3326b9 --- /dev/null +++ b/dev/tests/verification/Tests/DeprecatedTest.php @@ -0,0 +1,33 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace tests\verification\Tests; + +use tests\util\MftfTestCase; + +class DeprecatedTest extends MftfTestCase +{ + /** + * Tests flat generation of a deprecated test which uses deprecated entities. + * + * @throws \Exception + * @throws \Magento\FunctionalTestingFramework\Exceptions\TestReferenceException + */ + public function testDeprecatedTestEntitiesGeneration() + { + $this->generateAndCompareTest('DeprecatedTest'); + } + + /** + * Tests flat generation of a test which uses deprecated entities. + * + * @throws \Exception + * @throws \Magento\FunctionalTestingFramework\Exceptions\TestReferenceException + */ + public function testDeprecatedEntitiesOnlyGeneration() + { + $this->generateAndCompareTest('DeprecatedEntitiesTest'); + } +} diff --git a/docs/best-practices.md b/docs/best-practices.md index a3e5f2629..0b95b1c41 100644 --- a/docs/best-practices.md +++ b/docs/best-practices.md @@ -5,12 +5,12 @@ Check out our best practices below to ensure you are getting the absolute most o ## Action group 1. [Action group] names should be sufficiently descriptive to inform a test writer of what the action group does and when it should be used. - Add additional explanation in comments if needed. + Add additional explanation in annotations if needed. 2. Provide default values for the arguments that apply to your most common case scenarios. ## `actionGroups` vs `extends` -Use an action group to wraps a set of actions to reuse them multiple times. +Use an action group to wrap a set of actions to reuse them multiple times. Use an [extension] when a test or action group needs to be repeated with the exception of a few steps. @@ -58,12 +58,20 @@ The following pattern is used when merging with `extends`: Name files according to the following patterns to make searching in future more easy: +<!-- {% raw %} --> + #### Test file name Format: {_Admin_ or _Storefront_}{Functionality}_Test.xml_, where Functionality briefly describes the testing functionality. Example: _StorefrontCreateCustomerTest.xml_. +#### Action Group file name + +Format: {_Admin_ or _Storefront_}{Action Group Summary}ActionGroup.xml`, where Action Group Summary describes with a few words what we can expect from it. + +Example: _AdminCreateStoreActionGroup.xml_ + #### Section file name Format: {_Admin_ or _Storefront_}{UI Description}_Section.xml_, where UI Description briefly describes the testing UI. @@ -74,6 +82,8 @@ Example: _AdminNavbarSection.xml_. Format: {Type}_Data.xml_, where Type represents the entity type. +<!-- {% endraw %} --> + Example: _ProductData.xml_. ### Object names @@ -85,18 +95,19 @@ Use the _Foo.camelCase_ naming convention, which is similar to _Classes_ and _cl Use an upper case first letter for: - File names. Example: _StorefrontCreateCustomerTest.xml_ -- Test name attributes. Example: `<test name="TestAllTheThingsTest">`. -- Data entity names. Example: `<entity name="OutOfStockProduct">`. -- Page name. Example: `<page name="AdminLoginPage">`. -- Section name. Example: `<section name="AdminCategorySidebarActionSection">`. -- Action group name. Example: `<actionGroup name="LoginToAdminActionGroup">`. +- Test name attributes. Example: `<test name="TestAllTheThingsTest">` +- Data entity names. Example: `<entity name="OutOfStockProduct">` +- Page name. Example: `<page name="AdminLoginPage">` +- Section name. Example: `<section name="AdminCategorySidebarActionSection">` +- Action group name. Example: `<actionGroup name="LoginToAdminActionGroup">` #### Lower case Use a lower case first letter for: -- Data keys. Example: `<data key="firstName">`. -- Element names. Examples: `<element name="confirmDeleteButton"/>`. +- Data keys. Example: `<data key="firstName">` +- Element names. Examples: `<element name="confirmDeleteButton"/>` +- Step keys. For example: `<click selector="..." stepKey="clickLogin"/>` ## Page object diff --git a/docs/commands/mftf.md b/docs/commands/mftf.md index 2440428f9..b2c36dae4 100644 --- a/docs/commands/mftf.md +++ b/docs/commands/mftf.md @@ -42,6 +42,12 @@ vendor/bin/mftf generate:tests vendor/bin/mftf generate:tests AdminLoginTest StorefrontPersistedCustomerLoginTest ``` +### Generate test by test and suite name + +```bash +vendor/bin/mftf generate:tests LoginSuite:AdminLoginTest +``` + ### Generate and run the tests for a specified group ```bash @@ -58,6 +64,14 @@ vendor/bin/mftf run:test AdminLoginTest StorefrontPersistedCustomerLoginTest -r This command cleans up the previously generated tests; generates and runs the `LoginAsAdminTest` and `LoginAsCustomerTest` tests. +### Generate and run particular test in a specific suite's context + +```bash +vendor/bin/mftf run:test LoginSuite:AdminLoginTest -r +``` + +This command cleans up previously generated tests; generates and run `AdminLoginTest` within the context of the `LoginSuite`. + ### Generate and run a testManifest.txt file ```bash @@ -449,18 +463,46 @@ The example parameters are taken from the `etc/config/.env.example` file. ### `static-checks` -Runs all MFTF static-checks on the test codebase that MFTF is currently attached to. +Runs all or specific MFTF static-checks on the test codebase that MFTF is currently attached to. +If no script name argument is specified, all existing static check scripts will run. -#### Existing static checks +#### Usage -* Test Dependency: Checks that test dependencies do not violate Magento module's composer dependencies. +```bash +vendor/bin/mftf static-checks [<names>]... +``` -#### Usage +#### Examples + +To check what existing static check scripts are available + +```bash +vendor/bin/mftf static-checks --help +``` + +To run all existing static check scripts ```bash vendor/bin/mftf static-checks ``` +To run specific static check scripts + +```bash +vendor/bin/mftf static-checks testDependencies +``` +```bash +vendor/bin/mftf static-checks actionGroupArguments +``` +```bash +vendor/bin/mftf static-checks testDependencies actionGroupArguments +``` + +#### Existing static checks + +* Test Dependency: Checks that test dependencies do not violate Magento module's composer dependencies. +* Action Group Unused Arguments: Checks that action groups do not have unused arguments. + ### `upgrade:tests` Applies all the MFTF major version upgrade scripts to test components in the given path (`test.xml`, `data.xml`, etc). diff --git a/docs/configuration.md b/docs/configuration.md index 5140c01e7..9466f2bcc 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -277,6 +277,28 @@ Example: CREDENTIAL_VAULT_SECRET_BASE_PATH=secret ``` +### CREDENTIAL_AWS_SECRETS_MANAGER_REGION + +The region that AWS Secrets Manager is located. + +Example: + +```conf +# Region of AWS Secrets Manager +CREDENTIAL_AWS_SECRETS_MANAGER_REGION=us-east-1 +``` + +### CREDENTIAL_AWS_SECRETS_MANAGER_PROFILE + +The profile used to connect to AWS Secrets Manager. + +Example: + +```conf +# Profile used to connect to AWS Secrets Manager. +CREDENTIAL_AWS_SECRETS_MANAGER_PROFILE=default +``` + ### ENABLE_BROWSER_LOG Enables addition of browser logs to Allure steps diff --git a/docs/credentials.md b/docs/credentials.md index a2850cfe8..402030985 100644 --- a/docs/credentials.md +++ b/docs/credentials.md @@ -3,10 +3,11 @@ When you test functionality that involves external services such as UPS, FedEx, PayPal, or SignifyD, use the MFTF credentials feature to hide sensitive [data][] like integration tokens and API keys. -Currently the MFTF supports two types of credential storage: +Currently the MFTF supports three types of credential storage: - **.credentials file** -- **HashiCorp vault** +- **HashiCorp Vault** +- **AWS Secrets Manager** ## Configure File Storage @@ -135,11 +136,100 @@ CREDENTIAL_VAULT_ADDRESS=http://127.0.0.1:8200 CREDENTIAL_VAULT_SECRET_BASE_PATH=secret ``` -## Configure both File Storage and Vault Storage +## Configure AWS Secrets Manager -It is possible and sometimes useful to setup and use both `.credentials` file and vault for secret storage at the same time. -In this case, the MFTF tests are able to read secret data at runtime from both storage options, but the local `.credentials` file will take precedence. +AWS Secrets Manager offers secret management that supports: +- Secret rotation with built-in integration for Amazon RDS, Amazon Redshift, and Amazon DocumentDB +- Fine-grained policies and permissions +- Audit secret rotation centrally for resources in the AWS Cloud, third-party services, and on-premises +### Prerequisites + +#### Use AWS Secrets Manager from your own AWS account + +- An AWS account with Secrets Manager service +- An IAM user with AWS Secrets Manager access permission + +#### Use AWS Secrets Manager in CI/CD + +- AWS account ID where the AWS Secrets Manager service is hosted +- Authorized CI/CD EC2 instances with AWS Secrets Manager service access IAM role attached + +### Store secrets in AWS Secrets Manager + +#### Secrets format + +`Secret Name` and `Secret Value` are two key pieces of information for creating a secret. + +`Secret Value` can be either plaintext or key/value pairs in JSON format. + +`Secret Name` must use the following format: + +```conf +mftf/<VENDOR>/<YOUR/SECRET/KEY> +``` + +`Secret Value` can be stored in two different formats: plaintext or key/value pairs. + +For plaintext format, `Secret Value` can be any string you want to secure. + +For key/value pairs format, `Secret Value` is a key/value pair with `key` the same as `Secret Name` without `mftf/<VENDOR>/` prefix, which is `<YOUR/SECRET/KEY>`, and value can be any string you want to secure. + +##### Create Secrets using AWS CLI + +```bash +aws secretsmanager create-secret --name "mftf/magento/shipping/carriers_usps_userid" --description "Carriers USPS user id" --secret-string "1234567" +``` + +##### Create Secrets using AWS Console + +- Sign in to the AWS Secrets Manager console +- Choose Store a new secret +- In the Select secret type section, specify "Other type of secret" +- For `Secret Name`, `Secret Key` and `Secret Value` field, for example, to save the same secret in key/value JSON format, you should use + +```conf +# Secret Name +mftf/magento/shipping/carriers_usps_userid + +# Secret Key +shipping/carriers_usps_userid + +# Secret Value +1234567 +``` + +### Setup MFTF to use AWS Secrets Manager + +To use AWS Secrets Manager, the AWS region to connect to is required. You can set it through environment variable [`CREDENTIAL_AWS_SECRETS_MANAGER_REGION`][] in `.env`. + +MFTF uses the recommended [Default Credential Provider Chain][credential chain] to establish connection to AWS Secrets Manager service. +You can setup credentials according to [Default Credential Provider Chain][credential chain] and there is no MFTF specific setup required. +Optionally, however, you can explicitly set AWS profile through environment variable [`CREDENTIAL_AWS_SECRETS_MANAGER_PROFILE`][] in `.env`. + +```conf +# Sample AWS Secrets Manager configuration +CREDENTIAL_AWS_SECRETS_MANAGER_REGION=us-east-1 +CREDENTIAL_AWS_SECRETS_MANAGER_PROFILE=default +``` + +### Optionally set CREDENTIAL_AWS_ACCOUNT_ID environment variable + +In case AWS credentials cannot resolve to a valid AWS account, full AWS KMS ([Key Management Service][]) key ARN ([Amazon Resource Name][]) is required. +You will also need to set `CREDENTIAL_AWS_ACCOUNT_ID` environment variable so that MFTF can construct the full ARN. This is mostly used for CI/CD. + +```bash +export CREDENTIAL_AWS_ACCOUNT_ID=<Your_12_Digits_AWS_Account_ID> +``` + +## Configure multiple credential storage + +It is possible and sometimes useful to setup and use multiple credential storage at the same time. +In this case, the MFTF tests are able to read secret data at runtime from all storage options, in this case MFTF use the following precedence: + +``` +.credentials File > HashiCorp Vault > AWS Secrets Manager +``` <!-- {% raw %} --> ## Use credentials in a test @@ -150,7 +240,7 @@ Define the value as a reference to the corresponding key in the credentials file - `_CREDS` is an environment constant pointing to the `.credentials` file - `my_data_key` is a key in the the `.credentials` file or vault that contains the value to be used in a test step - - for File Storage, ensure your key contains the vendor prefix, i.e. `vendor/my_data_key` + - for File Storage, ensure your key contains the vendor prefix, which is `vendor/my_data_key` For example, to reference secret data in the [`fillField`][] action, use the `userInput` attribute using a typical File Storage: @@ -183,3 +273,8 @@ The MFTF tests delivered with Magento application do not use credentials and do [Vault KV2]: https://www.vaultproject.io/docs/secrets/kv/kv-v2.html [`CREDENTIAL_VAULT_ADDRESS`]: configuration.md#credential_vault_address [`CREDENTIAL_VAULT_SECRET_BASE_PATH`]: configuration.md#credential_vault_secret_base_path +[credential chain]: https://docs.aws.amazon.com/sdk-for-php/v3/developer-guide/guide_credentials.html +[`CREDENTIAL_AWS_SECRETS_MANAGER_PROFILE`]: configuration.md#credential_aws_secrets_manager_profile +[`CREDENTIAL_AWS_SECRETS_MANAGER_REGION`]: configuration.md#credential_aws_secrets_manager_region +[Key Management Service]: https://aws.amazon.com/kms/ +[Amazon Resource Name]: https://docs.aws.amazon.com/general/latest/gr/aws-arns-and-namespaces.html \ No newline at end of file diff --git a/docs/extending.md b/docs/extending.md index 91dc97c5d..064ed3208 100644 --- a/docs/extending.md +++ b/docs/extending.md @@ -26,7 +26,7 @@ __Use case__: Create two similar tests with different `url` (`"{{AdminCategoryPa > Test with "extends": ```xml -<tests > +<tests> <test name="AdminCategoryTest"> <annotations> ... @@ -47,7 +47,7 @@ __Use case__: Create two similar tests with different `url` (`"{{AdminCategoryPa > Test without "extends": ```xml -<tests > +<tests> <test name="AdminCategoryTest"> <annotations> ... @@ -77,7 +77,7 @@ __Use case__: Create two similar tests where the second test contains two additi > Tests with "extends": ```xml -<tests > +<tests> <test name="LogInAsAdminTest"> <amOnPage url="{{AdminLoginPage}}" stepKey="navigateToAdmin"/> <fillField selector="{{AdminLoginFormSection.username}}" userInput="admin" stepKey="fillUsername"/> @@ -95,7 +95,7 @@ __Use case__: Create two similar tests where the second test contains two additi > Tests without "extends": ```xml -<tests > +<tests> <test name="LogInAsAdminTest"> <amOnPage url="{{AdminLoginPage}}" stepKey="navigateToAdmin"/> <fillField selector="{{AdminLoginFormSection.username}}" userInput="admin" stepKey="fillUsername"/> @@ -125,7 +125,7 @@ __Use case__: Create two similar tests where the second one contains two additio > Tests with "extends": ```xml -<tests > +<tests> <test name="LogInAsAdminTest"> <before> <amOnPage url="{{AdminLoginPage}}" stepKey="navigateToAdmin"/> @@ -147,7 +147,7 @@ __Use case__: Create two similar tests where the second one contains two additio > Tests without "extends": ```xml -<tests > +<tests> <test name="LogInAsAdminTest"> <before> <amOnPage url="{{AdminLoginPage}}" stepKey="navigateToAdmin"/> @@ -295,7 +295,7 @@ __Use case__: Create an entity named `DivPanelGreen`, which is similar to the `D > Entities with "extends": ```xml -<entities > +<entities> <entity name="DivPanel"> <data key="divColor">Red</data> <data key="divSize">80px</data> @@ -310,7 +310,7 @@ __Use case__: Create an entity named `DivPanelGreen`, which is similar to the `D > Entities without "extends": ```xml -<entities > +<entities> <entity name="DivPanel"> <data key="divColor">Red</data> <data key="divSize">80px</data> @@ -331,7 +331,7 @@ __Use case__: Create an entity named `DivPanelGreen`, which is similar to the `D > Entities with "extends": ```xml -<entities > +<entities> <entity name="DivPanel"> <data key="divColor">Red</data> <data key="divSize">80px</data> @@ -347,7 +347,7 @@ __Use case__: Create an entity named `DivPanelGreen`, which is similar to the `D > Entities without "extends": ```xml -<entities > +<entities> <entity name="DivPanel"> <data key="divColor">Red</data> <data key="divSize">80px</data> diff --git a/docs/guides/selectors.md b/docs/guides/selectors.md index 3e019bb75..d1441865a 100644 --- a/docs/guides/selectors.md +++ b/docs/guides/selectors.md @@ -208,7 +208,7 @@ Here is an example of what NOT to do, but this demonstrates how the selector wor In the BAD example above, we are specifying a very precise path to an input element in the DOM, starting from the very top of the document. -Similarly, the relative XPath selector is a double forward slash `//`. It is used to start searching for an element anywhere in the DOM. +Similarly, the relative XPath selector is a double forward slash `//`. It is used to start searching for an element anywhere in the DOM starting from the specified element. If no element is defined, the entire DOM is searched. Example: @@ -216,6 +216,8 @@ Example: //div[@class=’form-group’]//input[@id='user-message'] ``` +In the `GOOD` example above, all `<div class='form-group'/>` elements in the DOM are matched first. Then all `<input id='user-message'/>` with `<div class='form-group'/>` as one of its parents are matched. The parent does not have to immediately precede it since it uses another double forward slash `//`. + #### Parent Selectors The parent selector (`..`) allows you to jump to the parent element. diff --git a/docs/mftf-tests.md b/docs/mftf-tests.md index b9d94baa9..4505975a9 100644 --- a/docs/mftf-tests.md +++ b/docs/mftf-tests.md @@ -5,7 +5,6 @@ dl dt{ font-weight:400; } - </style> # MFTF functional test reference diff --git a/docs/test/action-groups.md b/docs/test/action-groups.md index 0743481f6..5fdfbb522 100644 --- a/docs/test/action-groups.md +++ b/docs/test/action-groups.md @@ -10,8 +10,10 @@ The following diagram shows the structure of an MFTF action group: The following conventions apply to MFTF action groups: -- All action groups are declared in XML files and stored in the `<module>/ActionGroup/` directory. -- Every file name ends with `ActionGroup`, such as `LoginToAdminActionGroup`. +- All action groups are declared in XML files and stored in the `<module>/Test/Mftf/ActionGroup/` directory. +- Every file name ends with `ActionGroup` suffix. For exampe `LoginAsAdminActionGroup.xml`. +- Action group name should be the same as file name without extension. +- Single file should contain only one `<actionGroup>` node The XML format for the `actionGroups` declaration is: @@ -34,32 +36,31 @@ The XML format for the `actionGroups` declaration is: These examples build a declaration for a group of actions that grant authorization to the Admin area, and use the declaration in a test. -The _Backend/ActionGroup/LoginToAdminActionGroup.xml_ `<actionGroup>` relates to the functionality of the _Backend_ module. -In [test][], the name and identifier of the `<actionGroup>` is used as a reference in the `ref` parameter, such as `ref="LoginToAdminActionGroup"`. +The _Magento/Backend/Test/Mftf/ActionGroup/LoginAsAdminActionGroup.xml_ `<actionGroup>` relates to the functionality of the _Magento_Backend_ module. + +In [test][], the name and identifier of the `<actionGroup>` is used as a reference in the `ref` parameter, such as `ref="LoginAsAdminActionGroup"`. ### Create an action group declaration To create the `<actionGroup>` declaration: -1. Begin with a _Backend/ActionGroup/LoginToAdminActionGroup.xml_ template for the `<actionGroup>`: +1. Begin with a template for the `<actionGroup>`: ```xml <?xml version="1.0" encoding="UTF-8"?> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> - <actionGroup name="LoginToAdminActionGroup"> - ... + <actionGroup name="{Action Group Name}"> + </actionGroup> </actionGroups> ``` -<!-- {% raw %} --> - 1. Add actions to the `actionGroup` arguments: ```xml - <actionGroup name="LoginToAdminActionGroup"> + <actionGroup name="LoginAsAdminActionGroup"> <fillField stepKey="fillUsername" selector="#username" userInput="{{adminUser.username}}" /> <fillField stepKey="fillPassword" selector="#password" userInput="{{adminUser.password}}" /> <click stepKey="click" selector="#login" /> @@ -81,14 +82,20 @@ To create the `<actionGroup>` declaration: <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> - <actionGroup name="LoginToAdminActionGroup"> - <arguments> - <argument name="adminUser" defaultValue="_defaultAdmin"/> - </arguments> - <fillField stepKey="fillUsername" selector="#username" userInput="{{adminUser.username}}" /> - <fillField stepKey="fillPassword" selector="#password" userInput="{{adminUser.password}}" /> - <click stepKey="click" selector="#login" /> - </actionGroup> + <actionGroup name="LoginAsAdmin"> + <annotations> + <description>Login to Backend Admin using provided User Data. PLEASE NOTE: This Action Group does NOT validate that you are Logged In.</description> + </annotations> + <arguments> + <argument name="adminUser" type="entity" defaultValue="DefaultAdminUser"/> + </arguments> + + <amOnPage url="{{AdminLoginPage.url}}" stepKey="navigateToAdmin"/> + <fillField selector="{{AdminLoginFormSection.username}}" userInput="{{adminUser.username}}" stepKey="fillUsername"/> + <fillField selector="{{AdminLoginFormSection.password}}" userInput="{{adminUser.password}}" stepKey="fillPassword"/> + <click selector="{{AdminLoginFormSection.signIn}}" stepKey="clickLogin"/> + <closeAdminNotification stepKey="closeAdminNotification"/> + </actionGroup> </actionGroups> ``` @@ -97,23 +104,23 @@ To create the `<actionGroup>` declaration: In this test example, we want to add the following set of actions: ```xml -<fillField stepKey="fillUsername" selector="#username" userInput="{{CustomAdminUser.username}}" /> -<fillField stepKey="fillPassword" selector="#password" userInput="{{CustomAdminUser.password}}" /> -<click stepKey="click" selector="#login" /> +<fillField selector="{{AdminLoginFormSection.username}}" userInput="{{adminUser.username}}" stepKey="fillUsername"/> +<fillField selector="{{AdminLoginFormSection.password}}" userInput="{{adminUser.password}}" stepKey="fillPassword"/> +<click selector="{{AdminLoginFormSection.signIn}}" stepKey="clickLogin"/> ``` -Instead of adding this set of actions, use the _LoginToAdminActionGroup_ `<actionGroup>` declaration in tests: +Instead of adding this set of actions, use the _LoginAsAdminActionGroup_ `<actionGroup>` declaration in tests: -1. Reference the `LoginToAdminActionGroup` action group: +1. Reference the `LoginAsAdminActionGroup` action group: ```xml - <actionGroup stepKey="loginToAdminPanel" ref="LoginToAdminActionGroup"/> + <actionGroup stepKey="loginToAdminPanel" ref="LoginAsAdminActionGroup"/> ``` 1. Update the argument name/value pair to `adminUser` and `CustomAdminUser`: ```xml - <actionGroup stepKey="loginToAdminPanel" ref="LoginToAdminActionGroup"> + <actionGroup stepKey="loginToAdminPanel" ref="LoginAsAdminActionGroup"> <argument name="adminUser" value="CustomAdminUser"/> </actionGroup> ``` @@ -196,30 +203,34 @@ Starting with an action group such as: ``` It can be reworked into more manageable pieces, as below. These smaller steps are easier to read, update, and reuse. - -```xml -<actionGroup name="GoToCategoryGridAndAddNewCategory"> - <seeInCurrentUrl url="{{AdminCategoryPage.url}}" stepKey="seeOnCategoryPage"/> - <click selector="{{AdminCategorySidebarActionSection.AddSubcategoryButton}}" stepKey="clickOnAddSubCategory"/> - <see selector="{{AdminHeaderSection.pageTitle}}" userInput="New Category" stepKey="seeCategoryPageTitle"/> -</actionGroup> - -<actionGroup name="FillInBasicCategoryFields"> - <arguments> - <argument name="categoryEntity" defaultValue="_defaultCategory"/> - </arguments> - <fillField selector="{{AdminCategoryBasicFieldSection.CategoryNameInput}}" userInput="{{categoryEntity.name}}" stepKey="enterCategoryName"/> - <click selector="{{AdminCategorySEOSection.SectionHeader}}" stepKey="openSEO"/> - <fillField selector="{{AdminCategorySEOSection.UrlKeyInput}}" userInput="{{categoryEntity.name_lwr}}" stepKey="enterURLKey"/> -</actionGroup> - -<actionGroup name="SaveAndVerifyCategoryCreation"> - <click selector="{{AdminCategoryMainActionsSection.SaveButton}}" stepKey="saveCategory"/> - <seeElement selector="{{AdminCategoryMessagesSection.SuccessMessage}}" stepKey="assertSuccess"/> - <seeInTitle userInput="{{categoryEntity.name}}" stepKey="seeNewCategoryPageTitle"/> - <seeElement selector="{{AdminCategorySidebarTreeSection.categoryInTree(categoryEntity.name)}}" stepKey="seeCategoryInTree"/> -</actionGroup> -``` +* GoToCategoryGridAndAddNewCategory + ```xml + <actionGroup name="GoToCategoryGridAndAddNewCategory"> + <seeInCurrentUrl url="{{AdminCategoryPage.url}}" stepKey="seeOnCategoryPage"/> + <click selector="{{AdminCategorySidebarActionSection.AddSubcategoryButton}}" stepKey="clickOnAddSubCategory"/> + <see selector="{{AdminHeaderSection.pageTitle}}" userInput="New Category" stepKey="seeCategoryPageTitle"/> + </actionGroup> + ``` +* FillInBasicCategoryFields + ```xml + <actionGroup name="FillInBasicCategoryFields"> + <arguments> + <argument name="categoryEntity" defaultValue="_defaultCategory"/> + </arguments> + <fillField selector="{{AdminCategoryBasicFieldSection.CategoryNameInput}}" userInput="{{categoryEntity.name}}" stepKey="enterCategoryName"/> + <click selector="{{AdminCategorySEOSection.SectionHeader}}" stepKey="openSEO"/> + <fillField selector="{{AdminCategorySEOSection.UrlKeyInput}}" userInput="{{categoryEntity.name_lwr}}" stepKey="enterURLKey"/> + </actionGroup> + ``` +* SaveAndVerifyCategoryCreation + ```xml + <actionGroup name="SaveAndVerifyCategoryCreation"> + <click selector="{{AdminCategoryMainActionsSection.SaveButton}}" stepKey="saveCategory"/> + <seeElement selector="{{AdminCategoryMessagesSection.SuccessMessage}}" stepKey="assertSuccess"/> + <seeInTitle userInput="{{categoryEntity.name}}" stepKey="seeNewCategoryPageTitle"/> + <seeElement selector="{{AdminCategorySidebarTreeSection.categoryInTree(categoryEntity.name)}}" stepKey="seeCategoryInTree"/> + </actionGroup> + ``` <!-- {% endraw %} --> @@ -261,4 +272,4 @@ Attribute|Type|Use|Description [actions]: ./actions.md [test]: ../test.md [`argument`]: #argument-tag -[created]: ../data.md#persist-data \ No newline at end of file +[created]: ../data.md#persist-data diff --git a/docs/test/actions.md b/docs/test/actions.md index c5dc83fdb..59fd12aa2 100644 --- a/docs/test/actions.md +++ b/docs/test/actions.md @@ -1274,6 +1274,29 @@ Attribute|Type|Use|Description <magentoCLI command="indexer:reindex" stepKey="reindex"/> ``` +### magentoCron + +Used to execute Magento Cron jobs. Groups may be provided optionally. Internal mechanism of `<magentoCron>` ensures that Cron Job of single group is ran with 60 seconds interval. + +Attribute|Type|Use|Description +---|---|---|--- +`groups`|string |optional| Run only specified groups of Cron Jobs +`arguments`|string |optional| Unescaped arguments to be passed in with the CLI command. +`timeout`|string|optional| Number of seconds CLI command can run without outputting anything. +`stepKey`|string|required| A unique identifier of the action. +`before`|string|optional| `stepKey` of action that must be executed next. +`after`|string|optional| `stepKey` of preceding action. + + +#### Example +```xml +<magentoCron stepKey="runStagingCronJobs" groups="staging"/> +<!-- No interval here --> +<magentoCron stepKey="runIndexCronJobs" groups="index"/> +<!-- 60 seconds interval takes place here --> +<magentoCron stepKey="runAllCronJobs"/> +``` + ### makeScreenshot See [makeScreenshot docs on codeception.com](http://codeception.com/docs/modules/WebDriver#makeScreenshot). diff --git a/etc/config/.env.example b/etc/config/.env.example index 7320d8b8b..f5b6ef40e 100644 --- a/etc/config/.env.example +++ b/etc/config/.env.example @@ -30,10 +30,14 @@ BROWSER=chrome #MAGENTO_RESTAPI_SERVER_PORT=8080 #MAGENTO_RESTAPI_SERVER_PROTOCOL=https -#*** Uncomment and set vault address and secret base path if you want to use vault to manage _CREDS secrets ***# +#*** To use HashiCorp Vault to manage _CREDS secrets, uncomment and set vault address and secret base path ***# #CREDENTIAL_VAULT_ADDRESS=http://127.0.0.1:8200 #CREDENTIAL_VAULT_SECRET_BASE_PATH=secret +#*** To use AWS Secrets Manager to manage _CREDS secrets, uncomment and set region, profile is optional, when omitted, AWS default credential provider chain will be used ***# +#CREDENTIAL_AWS_SECRETS_MANAGER_PROFILE=default +#CREDENTIAL_AWS_SECRETS_MANAGER_REGION=us-east-1 + #*** Uncomment these properties to set up a dev environment with symlinked projects ***# #TESTS_BP= #FW_BP= diff --git a/etc/di.xml b/etc/di.xml index 3e6313bf3..e5e31cf87 100644 --- a/etc/di.xml +++ b/etc/di.xml @@ -8,7 +8,7 @@ <!-- Entity value gets replaced in Dom.php before reading $xml --> <!DOCTYPE config [ - <!ENTITY commonTestActions "acceptPopup|actionGroup|amOnPage|amOnUrl|amOnSubdomain|appendField|assertArrayIsSorted|assertArraySubset|assertElementContainsAttribute|attachFile|cancelPopup|checkOption|clearField|click|clickWithLeftButton|clickWithRightButton|closeAdminNotification|closeTab|comment|conditionalClick|createData|deleteData|updateData|getData|dontSee|dontSeeJsError|dontSeeCheckboxIsChecked|dontSeeCookie|dontSeeCurrentUrlEquals|dontSeeCurrentUrlMatches|dontSeeElement|dontSeeElementInDOM|dontSeeInCurrentUrl|dontSeeInField|dontSeeInFormFields|dontSeeInPageSource|dontSeeInSource|dontSeeInTitle|dontSeeLink|dontSeeOptionIsSelected|doubleClick|dragAndDrop|entity|executeJS|executeInSelenium|fillField|formatMoney|generateDate|grabAttributeFrom|grabCookie|grabFromCurrentUrl|grabMultiple|grabPageSource|grabTextFrom|grabValueFrom|loadSessionSnapshot|loginAsAdmin|magentoCLI|makeScreenshot|maximizeWindow|moveBack|moveForward|moveMouseOver|mSetLocale|mResetLocale|openNewTab|pauseExecution|parseFloat|performOn|pressKey|reloadPage|resetCookie|submitForm|resizeWindow|saveSessionSnapshot|scrollTo|scrollToTopOfPage|searchAndMultiSelectOption|see|seeCheckboxIsChecked|seeCookie|seeCurrentUrlEquals|seeCurrentUrlMatches|seeElement|seeElementInDOM|seeInCurrentUrl|seeInField|seeInFormFields|seeInPageSource|seeInPopup|seeInSource|seeInTitle|seeLink|seeNumberOfElements|seeOptionIsSelected|selectOption|setCookie|submitForm|switchToIFrame|switchToNextTab|switchToPreviousTab|switchToWindow|typeInPopup|uncheckOption|unselectOption|wait|waitForAjaxLoad|waitForElement|waitForElementChange|waitForElementNotVisible|waitForElementVisible|waitForPwaElementNotVisible|waitForPwaElementVisible|waitForJS|waitForLoadingMaskToDisappear|waitForPageLoad|waitForText|assertArrayHasKey|assertArrayNotHasKey|assertArraySubset|assertContains|assertCount|assertEmpty|assertEquals|assertFalse|assertFileExists|assertFileNotExists|assertGreaterOrEquals|assertGreaterThan|assertGreaterThanOrEqual|assertInstanceOf|assertInternalType|assertIsEmpty|assertLessOrEquals|assertLessThan|assertLessThanOrEqual|assertNotContains|assertNotEmpty|assertNotEquals|assertNotInstanceOf|assertNotNull|assertNotRegExp|assertNotSame|assertNull|assertRegExp|assertSame|assertStringStartsNotWith|assertStringStartsWith|assertTrue|expectException|fail|dontSeeFullUrlEquals|dontSee|dontSeeFullUrlMatches|dontSeeInFullUrl|seeFullUrlEquals|seeFullUrlMatches|seeInFullUrl|grabFromFullUrl"> + <!ENTITY commonTestActions "acceptPopup|actionGroup|amOnPage|amOnUrl|amOnSubdomain|appendField|assertArrayIsSorted|assertArraySubset|assertElementContainsAttribute|attachFile|cancelPopup|checkOption|clearField|click|clickWithLeftButton|clickWithRightButton|closeAdminNotification|closeTab|comment|conditionalClick|createData|deleteData|updateData|getData|dontSee|dontSeeJsError|dontSeeCheckboxIsChecked|dontSeeCookie|dontSeeCurrentUrlEquals|dontSeeCurrentUrlMatches|dontSeeElement|dontSeeElementInDOM|dontSeeInCurrentUrl|dontSeeInField|dontSeeInFormFields|dontSeeInPageSource|dontSeeInSource|dontSeeInTitle|dontSeeLink|dontSeeOptionIsSelected|doubleClick|dragAndDrop|entity|executeJS|executeInSelenium|fillField|formatMoney|generateDate|grabAttributeFrom|grabCookie|grabFromCurrentUrl|grabMultiple|grabPageSource|grabTextFrom|grabValueFrom|loadSessionSnapshot|loginAsAdmin|magentoCLI|magentoCron|makeScreenshot|maximizeWindow|moveBack|moveForward|moveMouseOver|mSetLocale|mResetLocale|openNewTab|pauseExecution|parseFloat|performOn|pressKey|reloadPage|resetCookie|submitForm|resizeWindow|saveSessionSnapshot|scrollTo|scrollToTopOfPage|searchAndMultiSelectOption|see|seeCheckboxIsChecked|seeCookie|seeCurrentUrlEquals|seeCurrentUrlMatches|seeElement|seeElementInDOM|seeInCurrentUrl|seeInField|seeInFormFields|seeInPageSource|seeInPopup|seeInSource|seeInTitle|seeLink|seeNumberOfElements|seeOptionIsSelected|selectOption|setCookie|submitForm|switchToIFrame|switchToNextTab|switchToPreviousTab|switchToWindow|typeInPopup|uncheckOption|unselectOption|wait|waitForAjaxLoad|waitForElement|waitForElementChange|waitForElementNotVisible|waitForElementVisible|waitForPwaElementNotVisible|waitForPwaElementVisible|waitForJS|waitForLoadingMaskToDisappear|waitForPageLoad|waitForText|assertArrayHasKey|assertArrayNotHasKey|assertArraySubset|assertContains|assertCount|assertEmpty|assertEquals|assertFalse|assertFileExists|assertFileNotExists|assertGreaterOrEquals|assertGreaterThan|assertGreaterThanOrEqual|assertInstanceOf|assertInternalType|assertIsEmpty|assertLessOrEquals|assertLessThan|assertLessThanOrEqual|assertNotContains|assertNotEmpty|assertNotEquals|assertNotInstanceOf|assertNotNull|assertNotRegExp|assertNotSame|assertNull|assertRegExp|assertSame|assertStringStartsNotWith|assertStringStartsWith|assertTrue|expectException|fail|dontSeeFullUrlEquals|dontSee|dontSeeFullUrlMatches|dontSeeInFullUrl|seeFullUrlEquals|seeFullUrlMatches|seeInFullUrl|grabFromFullUrl"> ]> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../src/Magento/FunctionalTestingFramework/ObjectManager/etc/config.xsd"> diff --git a/src/Magento/FunctionalTestingFramework/Codeception/Subscriber/Console.php b/src/Magento/FunctionalTestingFramework/Codeception/Subscriber/Console.php index 8c50f0670..90f04abae 100644 --- a/src/Magento/FunctionalTestingFramework/Codeception/Subscriber/Console.php +++ b/src/Magento/FunctionalTestingFramework/Codeception/Subscriber/Console.php @@ -7,17 +7,27 @@ namespace Magento\FunctionalTestingFramework\Codeception\Subscriber; use Codeception\Event\StepEvent; +use Codeception\Event\TestEvent; use Codeception\Lib\Console\Message; use Codeception\Step; use Codeception\Step\Comment; use Codeception\Test\Interfaces\ScenarioDriven; use Magento\FunctionalTestingFramework\Test\Objects\ActionGroupObject; use Magento\FunctionalTestingFramework\Test\Objects\ActionObject; +use Magento\FunctionalTestingFramework\Util\Logger\LoggingUtil; use Magento\FunctionalTestingFramework\Util\TestGenerator; use Symfony\Component\Console\Formatter\OutputFormatter; +/** + * @SuppressWarnings(PHPMD) + */ class Console extends \Codeception\Subscriber\Console { + /** + * Regular expresion to find deprecated notices. + */ + const DEPRECATED_NOTICE = '/<li>(?<deprecatedMessage>.*?)<\/li>/m'; + /** * Test files cache. * @@ -53,6 +63,36 @@ public function __construct($extensionOptions = [], $options = []) parent::__construct($options); } + /** + * Triggered event before each test. + * + * @param TestEvent $e + * @return void + * @throws \Exception + */ + public function startTest(TestEvent $e) + { + $test = $e->getTest()->getTestClass(); + try { + $testReflection = new \ReflectionClass($test); + $isDeprecated = preg_match_all(self::DEPRECATED_NOTICE, $testReflection->getDocComment(), $match); + if ($isDeprecated) { + $this->message('DEPRECATION NOTICE(S): ') + ->style('debug') + ->writeln(); + foreach ($match['deprecatedMessage'] as $deprecatedMessage) { + $this->message(' - ' . $deprecatedMessage) + ->style('debug') + ->writeln(); + } + } + } catch (\ReflectionException $e) { + LoggingUtil::getInstance()->getLogger(self::class)->error($e->getMessage(), $e->getTrace()); + } + + parent::startTest($e); + } + /** * Printing stepKey in before step action. * diff --git a/src/Magento/FunctionalTestingFramework/Config/Reader/Filesystem.php b/src/Magento/FunctionalTestingFramework/Config/Reader/Filesystem.php index 2f2cb8ad3..db96b010c 100644 --- a/src/Magento/FunctionalTestingFramework/Config/Reader/Filesystem.php +++ b/src/Magento/FunctionalTestingFramework/Config/Reader/Filesystem.php @@ -235,7 +235,8 @@ protected function validateSchema($configMerger, $filename = null) $error = str_replace(PHP_EOL, "", $error); LoggingUtil::getInstance()->getLogger(Filesystem::class)->criticalFailure( "Schema validation error ", - ($filename ? [ "file"=> $filename, "error" => $error]: ["error" => $error]) + ($filename ? [ "file"=> $filename, "error" => $error]: ["error" => $error]), + true ); } throw new \Exception("Schema validation errors found in xml file(s)" . $filename); diff --git a/src/Magento/FunctionalTestingFramework/Console/BaseGenerateCommand.php b/src/Magento/FunctionalTestingFramework/Console/BaseGenerateCommand.php index 0953e9d68..a0d94b16e 100644 --- a/src/Magento/FunctionalTestingFramework/Console/BaseGenerateCommand.php +++ b/src/Magento/FunctionalTestingFramework/Console/BaseGenerateCommand.php @@ -87,6 +87,10 @@ protected function getTestAndSuiteConfiguration(array $tests) $suiteToTestPair = []; foreach($tests as $test) { + if (strpos($test, ':') !== false) { + $suiteToTestPair[] = $test; + continue; + } if (array_key_exists($test, $testsReferencedInSuites)) { $suites = $testsReferencedInSuites[$test]; foreach ($suites as $suite) { diff --git a/src/Magento/FunctionalTestingFramework/Console/RunTestGroupCommand.php b/src/Magento/FunctionalTestingFramework/Console/RunTestGroupCommand.php index 98d121d40..6ea37785d 100644 --- a/src/Magento/FunctionalTestingFramework/Console/RunTestGroupCommand.php +++ b/src/Magento/FunctionalTestingFramework/Console/RunTestGroupCommand.php @@ -94,21 +94,31 @@ protected function execute(InputInterface $input, OutputInterface $output): int $command->run(new ArrayInput($args), $output); } - $codeceptionCommand = realpath(PROJECT_ROOT . '/vendor/bin/codecept') . ' run functional --verbose --steps'; + $commandString = realpath(PROJECT_ROOT . '/vendor/bin/codecept') . ' run functional --verbose --steps'; + $exitCode = -1; + $returnCodes = []; foreach ($groups as $group) { - $codeceptionCommand .= " -g {$group}"; - } + $codeceptionCommandString = $commandString . " -g {$group}"; + + $process = new Process($codeceptionCommandString); + $process->setWorkingDirectory(TESTS_BP); + $process->setIdleTimeout(600); + $process->setTimeout(0); - $process = new Process($codeceptionCommand); - $process->setWorkingDirectory(TESTS_BP); - $process->setIdleTimeout(600); - $process->setTimeout(0); + $returnCodes[] = $process->run( + function ($type, $buffer) use ($output) { + $output->write($buffer); + } + ); + } - return $process->run( - function ($type, $buffer) use ($output) { - $output->write($buffer); + foreach ($returnCodes as $returnCode) { + if ($returnCode != 0) { + return $returnCode; } - ); + $exitCode = 0; + } + return $exitCode; } } diff --git a/src/Magento/FunctionalTestingFramework/Console/StaticChecksCommand.php b/src/Magento/FunctionalTestingFramework/Console/StaticChecksCommand.php index 9757f494e..30c9cd600 100644 --- a/src/Magento/FunctionalTestingFramework/Console/StaticChecksCommand.php +++ b/src/Magento/FunctionalTestingFramework/Console/StaticChecksCommand.php @@ -8,10 +8,12 @@ namespace Magento\FunctionalTestingFramework\Console; +use Magento\FunctionalTestingFramework\StaticCheck\StaticCheckInterface; use Magento\FunctionalTestingFramework\StaticCheck\StaticChecksList; -use Magento\FunctionalTestingFramework\StaticCheck\StaticCheckListInterface; use Magento\FunctionalTestingFramework\Util\Logger\LoggingUtil; use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use Exception; @@ -19,11 +21,18 @@ class StaticChecksCommand extends Command { /** - * Pool of static check scripts to run + * Pool of all existing static check objects * - * @var StaticCheckListInterface + * @var StaticCheckInterface[] */ - private $staticChecksList; + private $allStaticCheckObjects; + + /** + * Static checks to run + * + * @var StaticCheckInterface[] + */ + private $staticCheckObjects; /** * Configures the current command. @@ -32,13 +41,22 @@ class StaticChecksCommand extends Command */ protected function configure() { + $list = new StaticChecksList(); + $this->allStaticCheckObjects = $list->getStaticChecks(); + $staticCheckNames = implode(', ', array_keys($this->allStaticCheckObjects)); + $description = "This command will run all static checks on xml test materials. " + . "Available static check scripts are:\n{$staticCheckNames}"; $this->setName('static-checks') - ->setDescription('This command will run all static checks on xml test materials.'); - $this->staticChecksList = new StaticChecksList(); + ->setDescription($description) + ->addArgument( + 'names', + InputArgument::OPTIONAL | InputArgument::IS_ARRAY, + 'name(s) of specific static check script(s) to run' + ); } /** - * + * Run required static check scripts * * @param InputInterface $input * @param OutputInterface $output @@ -47,11 +65,23 @@ protected function configure() */ protected function execute(InputInterface $input, OutputInterface $output) { - $staticCheckObjects = $this->staticChecksList->getStaticChecks(); + try { + $this->validateInputArguments($input, $output); + } catch (InvalidArgumentException $e) { + LoggingUtil::getInstance()->getLogger(StaticChecksCommand::class)->error($e->getMessage()); + $output->writeln($e->getMessage() . " Please fix input arguments and rerun."); + return 1; + } $errors = []; + foreach ($this->staticCheckObjects as $name => $staticCheck) { + LoggingUtil::getInstance()->getLogger(get_class($staticCheck))->info( + "\nRunning static check script for: " . $name + ); + $output->writeln( + "\nRunning static check script for: " . $name + ); - foreach ($staticCheckObjects as $staticCheck) { $staticCheck->execute($input); $staticOutput = $staticCheck->getOutput(); @@ -66,4 +96,38 @@ protected function execute(InputInterface $input, OutputInterface $output) return 1; } } + + /** + * Validate input arguments + * + * @param InputInterface $input + * @return void + * @throws InvalidArgumentException + */ + private function validateInputArguments(InputInterface $input) + { + $this->staticCheckObjects = []; + $requiredChecksNames = $input->getArgument('names'); + $invalidCheckNames = []; + // Found user required static check script(s) to run, + // If no static check name is supplied, run all static check scripts + if (empty($requiredChecksNames)) { + $this->staticCheckObjects = $this->allStaticCheckObjects; + } else { + for ($index = 0; $index < count($requiredChecksNames); $index++) { + if (in_array($requiredChecksNames[$index], array_keys($this->allStaticCheckObjects))) { + $this->staticCheckObjects[$requiredChecksNames[$index]] = + $this->allStaticCheckObjects[$requiredChecksNames[$index]]; + } else { + $invalidCheckNames[] = $requiredChecksNames[$index]; + } + } + } + + if (!empty($invalidCheckNames)) { + throw new InvalidArgumentException( + "Invalid static check script(s): " . implode(', ', $invalidCheckNames) . "." + ); + } + } } diff --git a/src/Magento/FunctionalTestingFramework/DataGenerator/Handlers/CredentialStore.php b/src/Magento/FunctionalTestingFramework/DataGenerator/Handlers/CredentialStore.php index 94ff40069..d6e6b9a69 100644 --- a/src/Magento/FunctionalTestingFramework/DataGenerator/Handlers/CredentialStore.php +++ b/src/Magento/FunctionalTestingFramework/DataGenerator/Handlers/CredentialStore.php @@ -6,23 +6,37 @@ namespace Magento\FunctionalTestingFramework\DataGenerator\Handlers; +use Magento\FunctionalTestingFramework\Exceptions\Collector\ExceptionCollector; use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; +use Magento\FunctionalTestingFramework\DataGenerator\Handlers\SecretStorage\BaseStorage; use Magento\FunctionalTestingFramework\DataGenerator\Handlers\SecretStorage\FileStorage; use Magento\FunctionalTestingFramework\DataGenerator\Handlers\SecretStorage\VaultStorage; +use Magento\FunctionalTestingFramework\DataGenerator\Handlers\SecretStorage\AwsSecretsManagerStorage; use Magento\FunctionalTestingFramework\Util\Path\UrlFormatter; class CredentialStore { const ARRAY_KEY_FOR_VAULT = 'vault'; const ARRAY_KEY_FOR_FILE = 'file'; + const ARRAY_KEY_FOR_AWS_SECRETS_MANAGER = 'aws'; + + const CREDENTIAL_STORAGE_INFO = 'You need to configure at least one of these options: ' + . '.credentials file, HashiCorp Vault or AWS Secrets Manager correctly'; /** * Credential storage array * - * @var array + * @var BaseStorage[] */ private $credStorage = []; + /** + * Boolean to indicate if credential storage have been initialized + * + * @var boolean + */ + private $initialized; + /** * Singleton instance * @@ -30,11 +44,17 @@ class CredentialStore */ private static $INSTANCE = null; + /** + * Exception contexts + * + * @var ExceptionCollector + */ + private $exceptionContexts; + /** * Static singleton getter for CredentialStore Instance * * @return CredentialStore - * @throws TestFrameworkException */ public static function getInstance() { @@ -47,35 +67,11 @@ public static function getInstance() /** * CredentialStore constructor - * - * @throws TestFrameworkException */ private function __construct() { - // Initialize file storage - try { - $this->credStorage[self::ARRAY_KEY_FOR_FILE] = new FileStorage(); - } catch (TestFrameworkException $e) { - } - - // Initialize vault storage - $cvAddress = getenv('CREDENTIAL_VAULT_ADDRESS'); - $cvSecretPath = getenv('CREDENTIAL_VAULT_SECRET_BASE_PATH'); - if ($cvAddress !== false && $cvSecretPath !== false) { - try { - $this->credStorage[self::ARRAY_KEY_FOR_VAULT] = new VaultStorage( - UrlFormatter::format($cvAddress, false), - '/' . trim($cvSecretPath, '/') - ); - } catch (TestFrameworkException $e) { - } - } - - if (empty($this->credStorage)) { - throw new TestFrameworkException( - "No credential storage is properly configured. Please configure vault or .credentials file." - ); - } + $this->initialized = false; + $this->exceptionContexts = new ExceptionCollector(); } /** @@ -87,8 +83,11 @@ private function __construct() */ public function getSecret($key) { - // Get secret data from storage according to the order they are stored - // File storage is preferred over vault storage to allow local secret value overriding remote secret value + // Initialize credential storage if it's not been done + $this->initializeCredentialStorage(); + + // Get secret data from storage according to the order they are stored which follows this precedence: + // FileStorage > VaultStorage > AwsSecretsManagerStorage foreach ($this->credStorage as $storage) { $value = $storage->getEncryptedValue($key); if (null !== $value) { @@ -96,9 +95,12 @@ public function getSecret($key) } } + $exceptionContexts = $this->getExceptionContexts(); + $this->resetExceptionContext(); throw new TestFrameworkException( - "\"{$key}\" not defined in vault or .credentials file, " - . "please provide a value in order to use this secret in a test." + "{$key} not found. " . self::CREDENTIAL_STORAGE_INFO + . " and ensure key, value exists to use _CREDS in tests." + . $exceptionContexts ); } @@ -106,27 +108,187 @@ public function getSecret($key) * Return decrypted input value * * @param string $value - * @return string + * @return string|false The decrypted string on success or false on failure + * @throws TestFrameworkException */ public function decryptSecretValue($value) { - // Loop through storage to decrypt value - foreach ($this->credStorage as $storage) { - return $storage->getDecryptedValue($value); - } + // Initialize credential storage if it's not been done + $this->initializeCredentialStorage(); + + // Decrypt secret value + return BaseStorage::getDecryptedValue($value); } /** * Return decrypted values for all occurrences from input string * * @param string $string - * @return mixed + * @return string|false The decrypted string on success or false on failure + * @throws TestFrameworkException */ public function decryptAllSecretsInString($string) { - // Loop through storage to decrypt all occurrences from input string - foreach ($this->credStorage as $storage) { - return $storage->getAllDecryptedValuesInString($string); + // Initialize credential storage if it's not been done + $this->initializeCredentialStorage(); + + // Decrypt all secret values in string + return BaseStorage::getAllDecryptedValuesInString($string); + } + + /** + * Setter for exception contexts + * + * @param string $type + * @param string $context + * @return void + */ + public function setExceptionContexts($type, $context) + { + $typeArray = [self::ARRAY_KEY_FOR_FILE, self::ARRAY_KEY_FOR_VAULT, self::ARRAY_KEY_FOR_AWS_SECRETS_MANAGER]; + if (in_array($type, $typeArray) && !empty($context)) { + $this->exceptionContexts->addError($type, $context); + } + } + + /** + * Return collected exception contexts + * + * @return string + */ + private function getExceptionContexts() + { + // Gather all exceptions collected + $exceptionMessage = "\n"; + foreach ($this->exceptionContexts->getErrors() as $type => $exceptions) { + $exceptionMessage .= "\nException from "; + if ($type == self::ARRAY_KEY_FOR_FILE) { + $exceptionMessage .= "File Storage: \n"; + } + if ($type == self::ARRAY_KEY_FOR_VAULT) { + $exceptionMessage .= "Vault Storage: \n"; + } + if ($type == self::ARRAY_KEY_FOR_AWS_SECRETS_MANAGER) { + $exceptionMessage .= "AWS Secrets Manager Storage: \n"; + } + + if (is_array($exceptions)) { + $exceptionMessage .= implode("\n", $exceptions) . "\n"; + } else { + $exceptionMessage .= $exceptions . "\n"; + } + } + return $exceptionMessage; + } + + /** + * Reset exception contexts to empty array + * + * @return void + */ + private function resetExceptionContext() + { + $this->exceptionContexts->reset(); + } + + /** + * Initialize all available credential storage + * + * @return void + * @throws TestFrameworkException + */ + private function initializeCredentialStorage() + { + if (!$this->initialized) { + // Initialize credential storage by defined order of precedence as the following + $this->initializeFileStorage(); + $this->initializeVaultStorage(); + $this->initializeAwsSecretsManagerStorage(); + $this->initialized = true; + } + + if (empty($this->credStorage)) { + throw new TestFrameworkException( + 'Invalid Credential Storage. ' . self::CREDENTIAL_STORAGE_INFO + . '.' . $this->getExceptionContexts() + ); + } + $this->resetExceptionContext(); + } + + /** + * Initialize file storage + * + * @return void + */ + private function initializeFileStorage() + { + // Initialize file storage + try { + $this->credStorage[self::ARRAY_KEY_FOR_FILE] = new FileStorage(); + } catch (TestFrameworkException $e) { + // Print error message in console + print_r($e->getMessage()); + // Save to exception context for Allure report + $this->setExceptionContexts(self::ARRAY_KEY_FOR_FILE, $e->getMessage()); + } + } + + /** + * Initialize Vault storage + * + * @return void + */ + private function initializeVaultStorage() + { + // Initialize vault storage + $cvAddress = getenv('CREDENTIAL_VAULT_ADDRESS'); + $cvSecretPath = getenv('CREDENTIAL_VAULT_SECRET_BASE_PATH'); + if ($cvAddress !== false && $cvSecretPath !== false) { + try { + $this->credStorage[self::ARRAY_KEY_FOR_VAULT] = new VaultStorage( + UrlFormatter::format($cvAddress, false), + '/' . trim($cvSecretPath, '/') + ); + } catch (TestFrameworkException $e) { + // Print error message in console + print_r($e->getMessage()); + // Save to exception context for Allure report + $this->setExceptionContexts(self::ARRAY_KEY_FOR_VAULT, $e->getMessage()); + } + } + } + + /** + * Initialize AWS Secrets Manager storage + * + * @return void + */ + private function initializeAwsSecretsManagerStorage() + { + // Initialize AWS Secrets Manager storage + $awsRegion = getenv('CREDENTIAL_AWS_SECRETS_MANAGER_REGION'); + $awsProfile = getenv('CREDENTIAL_AWS_SECRETS_MANAGER_PROFILE'); + $awsId = getenv('CREDENTIAL_AWS_ACCOUNT_ID'); + if (!empty($awsRegion)) { + if (empty($awsProfile)) { + $awsProfile = null; + } + if (empty($awsId)) { + $awsId = null; + } + try { + $this->credStorage[self::ARRAY_KEY_FOR_AWS_SECRETS_MANAGER] = new AwsSecretsManagerStorage( + $awsRegion, + $awsProfile, + $awsId + ); + } catch (TestFrameworkException $e) { + // Print error message in console + print_r($e->getMessage()); + // Save to exception context for Allure report + $this->setExceptionContexts(self::ARRAY_KEY_FOR_AWS_SECRETS_MANAGER, $e->getMessage()); + } } } } diff --git a/src/Magento/FunctionalTestingFramework/DataGenerator/Handlers/DataObjectHandler.php b/src/Magento/FunctionalTestingFramework/DataGenerator/Handlers/DataObjectHandler.php index 95a2b8b93..a210710db 100644 --- a/src/Magento/FunctionalTestingFramework/DataGenerator/Handlers/DataObjectHandler.php +++ b/src/Magento/FunctionalTestingFramework/DataGenerator/Handlers/DataObjectHandler.php @@ -13,6 +13,7 @@ use Magento\FunctionalTestingFramework\ObjectManager\ObjectHandlerInterface; use Magento\FunctionalTestingFramework\ObjectManagerFactory; use Magento\FunctionalTestingFramework\DataGenerator\Util\DataExtensionUtil; +use Magento\FunctionalTestingFramework\Util\Logger\LoggingUtil; class DataObjectHandler implements ObjectHandlerInterface { @@ -119,6 +120,7 @@ public function getAllObjects() * @param string[] $parserOutput Primitive array output from the Magento parser. * @return EntityDataObject[] * @throws XmlException + * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ private function processParserOutput($parserOutput) { @@ -132,6 +134,7 @@ private function processParserOutput($parserOutput) $type = $rawEntity[self::_TYPE] ?? null; $data = []; + $deprecated = null; $linkedEntities = []; $uniquenessData = []; $vars = []; @@ -163,6 +166,14 @@ private function processParserOutput($parserOutput) $parentEntity = $rawEntity[self::_EXTENDS]; } + if (array_key_exists(self::OBJ_DEPRECATED, $rawEntity)) { + $deprecated = $rawEntity[self::OBJ_DEPRECATED]; + LoggingUtil::getInstance()->getLogger(self::class)->deprecation( + $deprecated, + ["dataName" => $filename, "deprecatedEntity" => $deprecated] + ); + } + $entityDataObject = new EntityDataObject( $name, $type, @@ -171,7 +182,8 @@ private function processParserOutput($parserOutput) $uniquenessData, $vars, $parentEntity, - $filename + $filename, + $deprecated ); $entityDataObjects[$entityDataObject->getName()] = $entityDataObject; diff --git a/src/Magento/FunctionalTestingFramework/DataGenerator/Handlers/OperationDefinitionObjectHandler.php b/src/Magento/FunctionalTestingFramework/DataGenerator/Handlers/OperationDefinitionObjectHandler.php index b768d9bd8..8c2ec7b0a 100644 --- a/src/Magento/FunctionalTestingFramework/DataGenerator/Handlers/OperationDefinitionObjectHandler.php +++ b/src/Magento/FunctionalTestingFramework/DataGenerator/Handlers/OperationDefinitionObjectHandler.php @@ -11,6 +11,7 @@ use Magento\FunctionalTestingFramework\DataGenerator\Util\OperationElementExtractor; use Magento\FunctionalTestingFramework\ObjectManager\ObjectHandlerInterface; use Magento\FunctionalTestingFramework\ObjectManagerFactory; +use Magento\FunctionalTestingFramework\Util\Logger\LoggingUtil; class OperationDefinitionObjectHandler implements ObjectHandlerInterface { @@ -128,9 +129,7 @@ public function getOperationDefinition($operation, $dataType) * @return void * @throws \Exception * - * @SuppressWarnings(PHPMD.NPathComplexity) - * @SuppressWarnings(PHPMD.UnusedPrivateMethod) - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + * @SuppressWarnings(PHPMD) */ private function initialize() { @@ -145,6 +144,7 @@ private function initialize() $auth = $opDefArray[OperationDefinitionObjectHandler::ENTITY_OPERATION_AUTH] ?? null; $successRegex = $opDefArray[OperationDefinitionObjectHandler::ENTITY_OPERATION_SUCCESS_REGEX] ?? null; $returnRegex = $opDefArray[OperationDefinitionObjectHandler::ENTITY_OPERATION_RETURN_REGEX] ?? null; + $deprecated = $opDefArray[ObjectHandlerInterface::OBJ_DEPRECATED] ?? null; $returnIndex = $opDefArray[OperationDefinitionObjectHandler::ENTITY_OPERATION_RETURN_INDEX] ?? 0; $contentType = $opDefArray[OperationDefinitionObjectHandler::ENTITY_OPERATION_CONTENT_TYPE][0]['value'] ?? null; @@ -205,6 +205,13 @@ private function initialize() } } + if ($deprecated !== null) { + LoggingUtil::getInstance()->getLogger(self::class)->deprecation( + $deprecated, + ["operationName" => $dataDefName, "deprecatedOperation" => $deprecated] + ); + } + $this->operationDefinitionObjects[$operation . $dataType] = new OperationDefinitionObject( $dataDefName, $operation, @@ -219,7 +226,8 @@ private function initialize() $removeBackend, $successRegex, $returnRegex, - $returnIndex + $returnIndex, + $deprecated ); } } diff --git a/src/Magento/FunctionalTestingFramework/DataGenerator/Handlers/PersistedObjectHandler.php b/src/Magento/FunctionalTestingFramework/DataGenerator/Handlers/PersistedObjectHandler.php index 2a0f11a01..552b0dc07 100644 --- a/src/Magento/FunctionalTestingFramework/DataGenerator/Handlers/PersistedObjectHandler.php +++ b/src/Magento/FunctionalTestingFramework/DataGenerator/Handlers/PersistedObjectHandler.php @@ -80,7 +80,7 @@ public function createEntity( $entity, $dependentObjectKeys = [], $overrideFields = [], - $storeCode = "" + $storeCode = "all" ) { $retrievedDependentObjects = []; foreach ($dependentObjectKeys as $objectKey) { @@ -89,14 +89,24 @@ public function createEntity( foreach ($overrideFields as $index => $field) { try { - $overrideFields[$index] = CredentialStore::getInstance()->decryptAllSecretsInString($field); + $decrptedField = CredentialStore::getInstance()->decryptAllSecretsInString($field); + if ($decrptedField !== false) { + $overrideFields[$index] = $decrptedField; + } } catch (TestFrameworkException $e) { - //do not rethrow if Credentials are not defined - $overrideFields[$index] = $field; + //catch exception if Credentials are not defined } } $retrievedEntity = DataObjectHandler::getInstance()->getObject($entity); + + if ($retrievedEntity === null) { + throw new TestReferenceException( + "Entity \"" . $entity . "\" does not exist." . + "\nException occurred executing action at StepKey \"" . $key . "\"" + ); + } + $persistedObject = new DataPersistenceHandler( $retrievedEntity, $retrievedDependentObjects, diff --git a/src/Magento/FunctionalTestingFramework/DataGenerator/Handlers/SecretStorage/AwsSecretsManagerStorage.php b/src/Magento/FunctionalTestingFramework/DataGenerator/Handlers/SecretStorage/AwsSecretsManagerStorage.php new file mode 100644 index 000000000..6bd1ff144 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/DataGenerator/Handlers/SecretStorage/AwsSecretsManagerStorage.php @@ -0,0 +1,212 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\FunctionalTestingFramework\DataGenerator\Handlers\SecretStorage; + +use Magento\FunctionalTestingFramework\Config\MftfApplicationConfig; +use Magento\FunctionalTestingFramework\DataGenerator\Handlers\CredentialStore; +use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; +use Magento\FunctionalTestingFramework\Util\Logger\LoggingUtil; +use Aws\SecretsManager\SecretsManagerClient; +use Aws\Exception\AwsException; +use Aws\Result; +use InvalidArgumentException; +use Exception; + +class AwsSecretsManagerStorage extends BaseStorage +{ + /** + * Mftf project path + */ + const MFTF_PATH = 'mftf'; + + /** + * AWS Secrets Manager partial ARN + */ + const AWS_SM_PARTIAL_ARN = 'arn:aws:secretsmanager:'; + + /** + * AWS Secrets Manager version + * + * Last tested version '2017-10-17' + */ + const LATEST_VERSION = 'latest'; + + /** + * SecretsManagerClient client + * + * @var SecretsManagerClient + */ + private $client = null; + + /** + * AWS account id + * + * @var string + */ + private $awsAccountId; + + /** + * AWS account region + * + * @var string + */ + private $region; + + /** + * AwsSecretsManagerStorage constructor + * + * @param string $region + * @param string $profile + * @param string $accountId + * @throws TestFrameworkException + * @throws InvalidArgumentException + */ + public function __construct($region, $profile = null, $accountId = null) + { + parent::__construct(); + $this->createAwsSecretsManagerClient($region, $profile); + $this->region = $region; + $this->awsAccountId = $accountId; + } + + /** + * Returns the value of a secret based on corresponding key + * + * @param string $key + * @return string|null + * @throws Exception + */ + public function getEncryptedValue($key) + { + // Check if secret is in cached array + if (null !== ($value = parent::getEncryptedValue($key))) { + return $value; + } + + if (MftfApplicationConfig::getConfig()->verboseEnabled()) { + LoggingUtil::getInstance()->getLogger(AwsSecretsManagerStorage::class)->debug( + "Retrieving value for key name {$key} from AWS Secrets Manager" + ); + } + + $reValue = null; + try { + // Split vendor/key to construct secret id + list($vendor, $key) = explode('/', trim($key, '/'), 2); + // If AWS account id is specified, create and use full ARN, otherwise use partial ARN as secret id + $secretId = ''; + if (!empty($this->awsAccountId)) { + $secretId = self::AWS_SM_PARTIAL_ARN . $this->region . ':' . $this->awsAccountId . ':secret:'; + } + $secretId .= self::MFTF_PATH + . '/' + . $vendor + . '/' + . $key; + // Read value by id from AWS Secrets Manager, and parse the result + $value = $this->parseAwsSecretResult( + $this->client->getSecretValue(['SecretId' => $secretId]), + $key + ); + // Encrypt value for return + $reValue = openssl_encrypt($value, parent::ENCRYPTION_ALGO, parent::$encodedKey, 0, parent::$iv); + parent::$cachedSecretData[$key] = $reValue; + } catch (AwsException $e) { + $errMessage = "\nAWS Exception:\n" . $e->getAwsErrorMessage() + . "\nUnable to read value for key {$key} from AWS Secrets Manager\n"; + // Print error message in console + print_r($errMessage); + // Add error message in mftf log if verbose is enable + if (MftfApplicationConfig::getConfig()->verboseEnabled()) { + LoggingUtil::getInstance()->getLogger(AwsSecretsManagerStorage::class)->debug($errMessage); + } + // Save to exception context for Allure report + CredentialStore::getInstance()->setExceptionContexts( + CredentialStore::ARRAY_KEY_FOR_AWS_SECRETS_MANAGER, + $errMessage + ); + } catch (\Exception $e) { + $errMessage = "\nException:\n" . $e->getMessage() + . "\nUnable to read value for key {$key} from AWS Secrets Manager\n"; + // Print error message in console + print_r($errMessage); + // Add error message in mftf log if verbose is enable + if (MftfApplicationConfig::getConfig()->verboseEnabled()) { + LoggingUtil::getInstance()->getLogger(AwsSecretsManagerStorage::class)->debug($errMessage); + } + // Save to exception context for Allure report + CredentialStore::getInstance()->setExceptionContexts( + CredentialStore::ARRAY_KEY_FOR_AWS_SECRETS_MANAGER, + $errMessage + ); + } + return $reValue; + } + + /** + * Parse AWS result object and return secret for key + * + * @param Result $awsResult + * @param string $key + * @return string + * @throws TestFrameworkException + */ + private function parseAwsSecretResult($awsResult, $key) + { + // Return secret from the associated KMS CMK + if (isset($awsResult['SecretString'])) { + $rawSecret = $awsResult['SecretString']; + } else { + throw new TestFrameworkException( + "'SecretString' field is not set in AWS Result. Error parsing result from AWS Secrets Manager" + ); + } + + // Secrets are saved as JSON structures of key/value pairs if using AWS Secrets Manager console, and + // Secrets are saved as plain text if using AWS CLI. We need to handle both cases. + $secret = json_decode($rawSecret, true); + if (isset($secret[$key])) { + return $secret[$key]; + } elseif (is_string($rawSecret)) { + return $rawSecret; + } + throw new TestFrameworkException( + "$key not found or value is not string . Error parsing result from AWS Secrets Manager" + ); + } + + /** + * Create Aws Secrets Manager client + * + * @param string $region + * @param string $profile + * @return void + * @throws TestFrameworkException + * @throws InvalidArgumentException + */ + private function createAwsSecretsManagerClient($region, $profile) + { + if (null !== $this->client) { + return; + } + + $options = [ + 'region' => $region, + 'version' => self::LATEST_VERSION, + ]; + + if (!empty($profile)) { + $options['profile'] = $profile; + } + + // Create AWS Secrets Manager client + $this->client = new SecretsManagerClient($options); + if ($this->client === null) { + throw new TestFrameworkException("Unable to create AWS Secrets Manager client"); + } + } +} diff --git a/src/Magento/FunctionalTestingFramework/DataGenerator/Handlers/SecretStorage/BaseStorage.php b/src/Magento/FunctionalTestingFramework/DataGenerator/Handlers/SecretStorage/BaseStorage.php index cb892a545..6eb7fa7e8 100644 --- a/src/Magento/FunctionalTestingFramework/DataGenerator/Handlers/SecretStorage/BaseStorage.php +++ b/src/Magento/FunctionalTestingFramework/DataGenerator/Handlers/SecretStorage/BaseStorage.php @@ -58,30 +58,38 @@ public function getEncryptedValue($key) /** * Takes a value encrypted at runtime and decrypts it using the object's initial vector + * return the decrypted string on success or false on failure * * @param string $value - * @return string + * @return string|false The decrypted string on success or false on failure */ - public function getDecryptedValue($value) + public static function getDecryptedValue($value) { return openssl_decrypt($value, self::ENCRYPTION_ALGO, self::$encodedKey, 0, self::$iv); } /** * Takes a string that contains encrypted data at runtime and decrypts each value + * return false if no decryption happens or a failure occurs * * @param string $string - * @return mixed + * @return string|false The decrypted string on success or false on failure */ - public function getAllDecryptedValuesInString($string) + public static function getAllDecryptedValuesInString($string) { - $newString = $string; + $decrypted = false; foreach (self::$cachedSecretData as $key => $secretValue) { - if (strpos($newString, $secretValue) !== false) { + if (strpos($string, $secretValue) !== false) { $decryptedValue = self::getDecryptedValue($secretValue); - $newString = str_replace($secretValue, $decryptedValue, $newString); + if ($decryptedValue === false) { + return false; + } + if (!$decrypted) { + $decrypted = true; + } + $string = str_replace($secretValue, $decryptedValue, $string); } } - return $newString; + return $decrypted ? $string : false; } } diff --git a/src/Magento/FunctionalTestingFramework/DataGenerator/Handlers/SecretStorage/VaultStorage.php b/src/Magento/FunctionalTestingFramework/DataGenerator/Handlers/SecretStorage/VaultStorage.php index 6a9e9f0cf..39839e8f9 100644 --- a/src/Magento/FunctionalTestingFramework/DataGenerator/Handlers/SecretStorage/VaultStorage.php +++ b/src/Magento/FunctionalTestingFramework/DataGenerator/Handlers/SecretStorage/VaultStorage.php @@ -7,6 +7,7 @@ namespace Magento\FunctionalTestingFramework\DataGenerator\Handlers\SecretStorage; use Magento\FunctionalTestingFramework\Config\MftfApplicationConfig; +use Magento\FunctionalTestingFramework\DataGenerator\Handlers\CredentialStore; use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; use Magento\FunctionalTestingFramework\Util\Logger\LoggingUtil; use Vault\Client; @@ -67,7 +68,7 @@ class VaultStorage extends BaseStorage private $secretBasePath; /** - * CredentialVault constructor + * VaultStorage constructor * * @param string $baseUrl * @param string $secretBasePath @@ -123,10 +124,14 @@ public function getEncryptedValue($key) $reValue = openssl_encrypt($value, parent::ENCRYPTION_ALGO, parent::$encodedKey, 0, parent::$iv); parent::$cachedSecretData[$key] = $reValue; } catch (\Exception $e) { + $errMessage = "\nUnable to read secret for key name {$key} from vault." . $e->getMessage(); + // Print error message in console + print_r($errMessage); + // Save to exception context for Allure report + CredentialStore::getInstance()->setExceptionContexts('vault', $errMessage); + // Add error message in mftf log if verbose is enable if (MftfApplicationConfig::getConfig()->verboseEnabled()) { - LoggingUtil::getInstance()->getLogger(VaultStorage::class)->debug( - "Unable to read secret for key name {$key} from vault" - ); + LoggingUtil::getInstance()->getLogger(VaultStorage::class)->debug($errMessage); } } return $reValue; @@ -149,6 +154,13 @@ private function authenticated() return true; } } catch (\Exception $e) { + // Print error message in console + print_r($e->getMessage()); + // Save to exception context for Allure report + CredentialStore::getInstance()->setExceptionContexts( + CredentialStore::ARRAY_KEY_FOR_VAULT, + $e->getMessage() + ); } return false; } diff --git a/src/Magento/FunctionalTestingFramework/DataGenerator/Objects/EntityDataObject.php b/src/Magento/FunctionalTestingFramework/DataGenerator/Objects/EntityDataObject.php index 47a966f84..2204364f5 100644 --- a/src/Magento/FunctionalTestingFramework/DataGenerator/Objects/EntityDataObject.php +++ b/src/Magento/FunctionalTestingFramework/DataGenerator/Objects/EntityDataObject.php @@ -80,17 +80,25 @@ class EntityDataObject */ private $filename; + /** + * Deprecated message. + * + * @var string + */ + private $deprecated; + /** * Constructor * - * @param string $name - * @param string $type - * @param string[] $data - * @param string[] $linkedEntities - * @param string[] $uniquenessData - * @param string[] $vars - * @param string $parentEntity - * @param string $filename + * @param string $name + * @param string $type + * @param string[] $data + * @param string[] $linkedEntities + * @param string[] $uniquenessData + * @param string[] $vars + * @param string $parentEntity + * @param string $filename + * @param string|null $deprecated */ public function __construct( $name, @@ -100,7 +108,8 @@ public function __construct( $uniquenessData, $vars = [], $parentEntity = null, - $filename = null + $filename = null, + $deprecated = null ) { $this->name = $name; $this->type = $type; @@ -113,6 +122,17 @@ public function __construct( $this->vars = $vars; $this->parentEntity = $parentEntity; $this->filename = $filename; + $this->deprecated = $deprecated; + } + + /** + * Getter for the deprecated attr of the section. + * + * @return string + */ + public function getDeprecated() + { + return $this->deprecated; } /** diff --git a/src/Magento/FunctionalTestingFramework/DataGenerator/Objects/OperationDefinitionObject.php b/src/Magento/FunctionalTestingFramework/DataGenerator/Objects/OperationDefinitionObject.php index 721a9dcc9..67fe48300 100644 --- a/src/Magento/FunctionalTestingFramework/DataGenerator/Objects/OperationDefinitionObject.php +++ b/src/Magento/FunctionalTestingFramework/DataGenerator/Objects/OperationDefinitionObject.php @@ -8,6 +8,7 @@ /** * Class OperationDefinitionObject + * @SuppressWarnings(PHPMD) */ class OperationDefinitionObject { @@ -117,22 +118,30 @@ class OperationDefinitionObject */ private $removeBackend; + /** + * Deprecated message. + * + * @var string + */ + private $deprecated; + /** * OperationDefinitionObject constructor. - * @param string $name - * @param string $operation - * @param string $dataType - * @param string $apiMethod - * @param string $apiUri - * @param string $auth - * @param array $headers - * @param array $params - * @param array $metaData - * @param string $contentType - * @param boolean $removeBackend - * @param string $successRegex - * @param string $returnRegex - * @param string $returnIndex + * @param string $name + * @param string $operation + * @param string $dataType + * @param string $apiMethod + * @param string $apiUri + * @param string $auth + * @param array $headers + * @param array $params + * @param array $metaData + * @param string $contentType + * @param boolean $removeBackend + * @param string $successRegex + * @param string $returnRegex + * @param string $returnIndex + * @param string|null $deprecated * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( @@ -149,7 +158,8 @@ public function __construct( $removeBackend, $successRegex = null, $returnRegex = null, - $returnIndex = null + $returnIndex = null, + $deprecated = null ) { $this->name = $name; $this->operation = $operation; @@ -164,6 +174,7 @@ public function __construct( $this->returnRegex = $returnRegex; $this->returnIndex = $returnIndex; $this->removeBackend = $removeBackend; + $this->deprecated = $deprecated; $this->apiUrl = null; if (!empty($contentType)) { @@ -176,6 +187,16 @@ public function __construct( $this->headers[] = self::HTTP_CONTENT_TYPE_HEADER . ': ' . $this->contentType; } + /** + * Getter for the deprecated attr of the section + * + * @return string + */ + public function getDeprecated() + { + return $this->deprecated; + } + /** * Getter for data's data type * diff --git a/src/Magento/FunctionalTestingFramework/DataGenerator/Persist/DataPersistenceHandler.php b/src/Magento/FunctionalTestingFramework/DataGenerator/Persist/DataPersistenceHandler.php index dcb9d158a..73ee2be19 100644 --- a/src/Magento/FunctionalTestingFramework/DataGenerator/Persist/DataPersistenceHandler.php +++ b/src/Magento/FunctionalTestingFramework/DataGenerator/Persist/DataPersistenceHandler.php @@ -59,7 +59,10 @@ public function __construct($entityObject, $dependentObjects = [], $customFields array_merge($entityObject->getAllData(), $customFields), $entityObject->getLinkedEntities(), $this->stripCustomFieldsFromUniquenessData($entityObject->getUniquenessData(), $customFields), - $entityObject->getVarReferences() + $entityObject->getVarReferences(), + $entityObject->getParentName(), + $entityObject->getFilename(), + $entityObject->getDeprecated() ); } else { $this->entityObject = clone $entityObject; diff --git a/src/Magento/FunctionalTestingFramework/DataGenerator/Util/DataExtensionUtil.php b/src/Magento/FunctionalTestingFramework/DataGenerator/Util/DataExtensionUtil.php index 2766b39a0..3a12bdce1 100644 --- a/src/Magento/FunctionalTestingFramework/DataGenerator/Util/DataExtensionUtil.php +++ b/src/Magento/FunctionalTestingFramework/DataGenerator/Util/DataExtensionUtil.php @@ -93,7 +93,8 @@ public function extendEntity($entityObject) $newUniqueReferences, $newVarReferences, $entityObject->getParentName(), - $entityObject->getFilename() + $entityObject->getFilename(), + $entityObject->getDeprecated() ); return $extendedEntity; } diff --git a/src/Magento/FunctionalTestingFramework/DataGenerator/Util/RuntimeDataReferenceResolver.php b/src/Magento/FunctionalTestingFramework/DataGenerator/Util/RuntimeDataReferenceResolver.php index a09afd01a..220d4ab7f 100644 --- a/src/Magento/FunctionalTestingFramework/DataGenerator/Util/RuntimeDataReferenceResolver.php +++ b/src/Magento/FunctionalTestingFramework/DataGenerator/Util/RuntimeDataReferenceResolver.php @@ -9,6 +9,7 @@ use Magento\FunctionalTestingFramework\DataGenerator\Handlers\CredentialStore; use Magento\FunctionalTestingFramework\DataGenerator\Handlers\DataObjectHandler; use Magento\FunctionalTestingFramework\Exceptions\TestReferenceException; +use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; use Magento\FunctionalTestingFramework\Test\Objects\ActionObject; /** @@ -23,7 +24,7 @@ class RuntimeDataReferenceResolver implements DataReferenceResolverInterface * @param string $originalDataEntity * @return array|false|string|null * @throws TestReferenceException - * @throws \Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException + * @throws TestFrameworkException */ public function getDataReference(string $data, string $originalDataEntity) { @@ -43,6 +44,9 @@ public function getDataReference(string $data, string $originalDataEntity) case ActionObject::__CREDS: $value = CredentialStore::getInstance()->getSecret($var); $result = CredentialStore::getInstance()->decryptSecretValue($value); + if ($result === false) { + throw new TestFrameworkException("\nFailed to decrypt value {$value}\n"); + } $result = str_replace($matches['reference'], $result, $data); break; default: diff --git a/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataOperation.xsd b/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataOperation.xsd index 8b59ccae0..58fad8d8b 100644 --- a/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataOperation.xsd +++ b/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataOperation.xsd @@ -22,6 +22,13 @@ </xs:choice> <xs:attribute type="xs:string" name="name"/> <xs:attribute type="xs:string" name="dataType" use="required"/> + <xs:attribute type="xs:string" name="deprecated"> + <xs:annotation> + <xs:documentation> + Message and flag which shows that entity is deprecated. + </xs:documentation> + </xs:annotation> + </xs:attribute> <xs:attribute type="operationEnum" name="type" use="required"/> <xs:attribute type="xs:string" name="url"/> <xs:attribute type="authEnum" name="auth"/> diff --git a/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd b/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd index aec157309..9d0d8bb35 100644 --- a/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd +++ b/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd @@ -69,6 +69,14 @@ </xs:documentation> </xs:annotation> </xs:attribute> + <xs:attribute type="xs:string" name="deprecated"> + <xs:annotation> + <xs:documentation> + Message and flag which shows that entity is deprecated. + </xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attribute type="xs:string" name="type"> <xs:annotation> <xs:documentation> diff --git a/src/Magento/FunctionalTestingFramework/Exceptions/Collector/ExceptionCollector.php b/src/Magento/FunctionalTestingFramework/Exceptions/Collector/ExceptionCollector.php index c1e023ef4..f64b35446 100644 --- a/src/Magento/FunctionalTestingFramework/Exceptions/Collector/ExceptionCollector.php +++ b/src/Magento/FunctionalTestingFramework/Exceptions/Collector/ExceptionCollector.php @@ -43,6 +43,26 @@ public function throwException() throw new \Exception("\n" . $errorMsg); } + /** + * Return all errors + * + * @return array + */ + public function getErrors() + { + return $this->errors ?? []; + } + + /** + * Reset error to empty array + * + * @return void + */ + public function reset() + { + $this->errors = []; + } + /** * If there are multiple exceptions for a single file, the function flattens the array so they can be printed * as separate messages. diff --git a/src/Magento/FunctionalTestingFramework/Module/MagentoWebDriver.php b/src/Magento/FunctionalTestingFramework/Module/MagentoWebDriver.php index a95586b26..cd2768a06 100644 --- a/src/Magento/FunctionalTestingFramework/Module/MagentoWebDriver.php +++ b/src/Magento/FunctionalTestingFramework/Module/MagentoWebDriver.php @@ -53,6 +53,9 @@ class MagentoWebDriver extends WebDriver { use AttachmentSupport; + const MAGENTO_CRON_INTERVAL = 60; + const MAGENTO_CRON_COMMAND = 'cron:run'; + /** * List of known magento loading masks by selector * @@ -121,6 +124,13 @@ class MagentoWebDriver extends WebDriver */ private $jsErrors = []; + /** + * Contains last execution times for Cron + * + * @var int[] + */ + private $cronExecution = []; + /** * Sanitizes config, then initializes using parent. * @@ -552,6 +562,75 @@ public function magentoCLI($command, $timeout = null, $arguments = null) return $response; } + /** + * Executes Magento Cron keeping the interval (> 60 seconds between each run) + * + * @param string|null $cronGroups + * @param integer|null $timeout + * @param string|null $arguments + * @return string + */ + public function magentoCron($cronGroups = null, $timeout = null, $arguments = null) + { + $cronGroups = explode(' ', $cronGroups); + return $this->executeCronjobs($cronGroups, $timeout, $arguments); + } + + /** + * Updates last execution time for Cron + * + * @param array $cronGroups + * @return void + */ + private function notifyCronFinished(array $cronGroups = []) + { + if (empty($cronGroups)) { + $this->cronExecution['*'] = time(); + } + + foreach ($cronGroups as $group) { + $this->cronExecution[$group] = time(); + } + } + + /** + * Returns last Cron execution time for specific cron or all crons + * + * @param array $cronGroups + * @return integer + */ + private function getLastCronExecution(array $cronGroups = []) + { + if (empty($cronGroups)) { + return (int)max($this->cronExecution); + } + + $cronGroups = array_merge($cronGroups, ['*']); + + return array_reduce($cronGroups, function ($lastExecution, $group) { + if (isset($this->cronExecution[$group]) && $this->cronExecution[$group] > $lastExecution) { + $lastExecution = $this->cronExecution[$group]; + } + + return (int)$lastExecution; + }, 0); + } + + /** + * Returns time to wait for next run + * + * @param array $cronGroups + * @param integer $cronInterval + * @return integer + */ + private function getCronWait(array $cronGroups = [], int $cronInterval = self::MAGENTO_CRON_INTERVAL) + { + $nextRun = $this->getLastCronExecution($cronGroups) + $cronInterval; + $toNextRun = $nextRun - time(); + + return max(0, $toNextRun); + } + /** * Runs DELETE request to delete a Magento entity against the url given. * @@ -690,6 +769,9 @@ public function fillSecretField($field, $value) // decrypted value $decryptedValue = CredentialStore::getInstance()->decryptSecretValue($value); + if ($decryptedValue === false) { + throw new TestFrameworkException("\nFailed to decrypt value {$value} for field {$field}\n"); + } $this->fillField($field, $decryptedValue); } @@ -709,6 +791,9 @@ public function magentoCLISecret($command, $timeout = null, $arguments = null) // decrypted value $decryptedCommand = CredentialStore::getInstance()->decryptAllSecretsInString($command); + if ($decryptedCommand === false) { + throw new TestFrameworkException("\nFailed to decrypt magentoCLI command {$command}\n"); + } return $this->magentoCLI($decryptedCommand, $timeout, $arguments); } @@ -971,4 +1056,36 @@ public function getSecret($key) { return CredentialStore::getInstance()->getSecret($key); } + + /** + * Waits proper amount of time to perform Cron execution + * + * @param string $cronGroups + * @param integer $timeout + * @param string $arguments + * @return string + * @throws TestFrameworkException + */ + private function executeCronjobs($cronGroups, $timeout, $arguments): string + { + $cronGroups = array_filter($cronGroups); + + $waitFor = $this->getCronWait($cronGroups); + + if ($waitFor) { + $this->wait($waitFor); + } + + $command = array_reduce($cronGroups, function ($command, $cronGroup) { + $command .= ' --group=' . $cronGroup; + return $command; + }, self::MAGENTO_CRON_COMMAND); + $timeStart = microtime(true); + $cronResult = $this->magentoCLI($command, $timeout, $arguments); + $timeEnd = microtime(true); + + $this->notifyCronFinished($cronGroups); + + return sprintf('%s (wait: %ss, execution: %ss)', $cronResult, $waitFor, round($timeEnd - $timeStart, 2)); + } } diff --git a/src/Magento/FunctionalTestingFramework/ObjectManager/ObjectHandlerInterface.php b/src/Magento/FunctionalTestingFramework/ObjectManager/ObjectHandlerInterface.php index 1bc79cbbb..94147dc08 100644 --- a/src/Magento/FunctionalTestingFramework/ObjectManager/ObjectHandlerInterface.php +++ b/src/Magento/FunctionalTestingFramework/ObjectManager/ObjectHandlerInterface.php @@ -11,6 +11,8 @@ */ interface ObjectHandlerInterface { + const OBJ_DEPRECATED = 'deprecated'; + /** * Function to enforce singleton design pattern * diff --git a/src/Magento/FunctionalTestingFramework/Page/Handlers/PageObjectHandler.php b/src/Magento/FunctionalTestingFramework/Page/Handlers/PageObjectHandler.php index d9eec1af1..c310b9bef 100644 --- a/src/Magento/FunctionalTestingFramework/Page/Handlers/PageObjectHandler.php +++ b/src/Magento/FunctionalTestingFramework/Page/Handlers/PageObjectHandler.php @@ -9,6 +9,7 @@ use Magento\FunctionalTestingFramework\ObjectManager\ObjectHandlerInterface; use Magento\FunctionalTestingFramework\ObjectManagerFactory; use Magento\FunctionalTestingFramework\Page\Objects\PageObject; +use Magento\FunctionalTestingFramework\Util\Logger\LoggingUtil; use Magento\FunctionalTestingFramework\XmlParser\PageParser; use Magento\FunctionalTestingFramework\Exceptions\XmlException; @@ -68,9 +69,17 @@ private function __construct() $sectionNames = array_keys($pageData[self::SECTION] ?? []); $parameterized = $pageData[self::PARAMETERIZED] ?? false; $filename = $pageData[self::FILENAME] ?? null; + $deprecated = $pageData[self::OBJ_DEPRECATED] ?? null; + + if ($deprecated !== null) { + LoggingUtil::getInstance()->getLogger(self::class)->deprecation( + $deprecated, + ["pageName" => $filename, "deprecatedPage" => $deprecated] + ); + } $this->pageObjects[$pageName] = - new PageObject($pageName, $url, $module, $sectionNames, $parameterized, $area, $filename); + new PageObject($pageName, $url, $module, $sectionNames, $parameterized, $area, $filename, $deprecated); } } diff --git a/src/Magento/FunctionalTestingFramework/Page/Handlers/SectionObjectHandler.php b/src/Magento/FunctionalTestingFramework/Page/Handlers/SectionObjectHandler.php index e77e4c502..2736100fd 100644 --- a/src/Magento/FunctionalTestingFramework/Page/Handlers/SectionObjectHandler.php +++ b/src/Magento/FunctionalTestingFramework/Page/Handlers/SectionObjectHandler.php @@ -10,6 +10,8 @@ use Magento\FunctionalTestingFramework\ObjectManagerFactory; use Magento\FunctionalTestingFramework\Page\Objects\ElementObject; use Magento\FunctionalTestingFramework\Page\Objects\SectionObject; +use Magento\FunctionalTestingFramework\Test\Util\TestObjectExtractor; +use Magento\FunctionalTestingFramework\Util\Logger\LoggingUtil; use Magento\FunctionalTestingFramework\XmlParser\SectionParser; use Magento\FunctionalTestingFramework\Exceptions\XmlException; @@ -73,14 +75,21 @@ private function __construct() $elementLocatorFunc = $elementData[SectionObjectHandler::LOCATOR_FUNCTION] ?? null; $elementTimeout = $elementData[SectionObjectHandler::TIMEOUT] ?? null; $elementParameterized = $elementData[SectionObjectHandler::PARAMETERIZED] ?? false; - + $elementDeprecated = $elementData[self::OBJ_DEPRECATED] ?? null; + if ($elementDeprecated !== null) { + LoggingUtil::getInstance()->getLogger(ElementObject::class)->deprecation( + $elementDeprecated, + ["elementName" => $elementName, "deprecatedElement" => $elementDeprecated] + ); + } $elements[$elementName] = new ElementObject( $elementName, $elementType, $elementSelector, $elementLocatorFunc, $elementTimeout, - $elementParameterized + $elementParameterized, + $elementDeprecated ); } } catch (XmlException $exception) { @@ -88,7 +97,21 @@ private function __construct() } $filename = $sectionData[self::FILENAME] ?? null; - $this->sectionObjects[$sectionName] = new SectionObject($sectionName, $elements, $filename); + $sectionDeprecated = $sectionData[self::OBJ_DEPRECATED] ?? null; + + if ($sectionDeprecated !== null) { + LoggingUtil::getInstance()->getLogger(SectionObject::class)->deprecation( + $sectionDeprecated, + ["sectionName" => $filename, "deprecatedSection" => $sectionDeprecated] + ); + } + + $this->sectionObjects[$sectionName] = new SectionObject( + $sectionName, + $elements, + $filename, + $sectionDeprecated + ); } } diff --git a/src/Magento/FunctionalTestingFramework/Page/Objects/ElementObject.php b/src/Magento/FunctionalTestingFramework/Page/Objects/ElementObject.php index 6b0626f10..567872fc7 100644 --- a/src/Magento/FunctionalTestingFramework/Page/Objects/ElementObject.php +++ b/src/Magento/FunctionalTestingFramework/Page/Objects/ElementObject.php @@ -56,6 +56,13 @@ class ElementObject */ private $parameterized; + /** + * Deprecated message. + * + * @var string + */ + private $deprecated; + /** * ElementObject constructor. * @param string $name @@ -66,7 +73,7 @@ class ElementObject * @param boolean $parameterized * @throws XmlException */ - public function __construct($name, $type, $selector, $locatorFunction, $timeout, $parameterized) + public function __construct($name, $type, $selector, $locatorFunction, $timeout, $parameterized, $deprecated = null) { if ($selector != null && $locatorFunction != null) { throw new XmlException("Element '{$name}' cannot have both a selector and a locatorFunction."); @@ -83,6 +90,17 @@ public function __construct($name, $type, $selector, $locatorFunction, $timeout, } $this->timeout = $timeout; $this->parameterized = $parameterized; + $this->deprecated = $deprecated; + } + + /** + * Getter for the deprecated attr + * + * @return string + */ + public function getDeprecated() + { + return $this->deprecated; } /** diff --git a/src/Magento/FunctionalTestingFramework/Page/Objects/PageObject.php b/src/Magento/FunctionalTestingFramework/Page/Objects/PageObject.php index d99c40240..e4a1acdc0 100644 --- a/src/Magento/FunctionalTestingFramework/Page/Objects/PageObject.php +++ b/src/Magento/FunctionalTestingFramework/Page/Objects/PageObject.php @@ -65,18 +65,34 @@ class PageObject */ private $filename; + /** + * Deprecated message. + * + * @var string + */ + private $deprecated; + /** * PageObject constructor. - * @param string $name - * @param string $url - * @param string $module - * @param array $sections - * @param boolean $parameterized - * @param string $area - * @param string $filename - */ - public function __construct($name, $url, $module, $sections, $parameterized, $area, $filename = null) - { + * @param string $name + * @param string $url + * @param string $module + * @param array $sections + * @param boolean $parameterized + * @param string $area + * @param string|null $filename + * @param string|null $deprecated + */ + public function __construct( + $name, + $url, + $module, + $sections, + $parameterized, + $area, + $filename = null, + $deprecated = null + ) { $this->name = $name; $this->url = $url; $this->module = $module; @@ -84,6 +100,17 @@ public function __construct($name, $url, $module, $sections, $parameterized, $ar $this->parameterized = $parameterized; $this->area = $area; $this->filename = $filename; + $this->deprecated = $deprecated; + } + + /** + * Getter for the deprecated attr of the section + * + * @return string + */ + public function getDeprecated() + { + return $this->deprecated; } /** diff --git a/src/Magento/FunctionalTestingFramework/Page/Objects/SectionObject.php b/src/Magento/FunctionalTestingFramework/Page/Objects/SectionObject.php index 8c1cac326..3674eea16 100644 --- a/src/Magento/FunctionalTestingFramework/Page/Objects/SectionObject.php +++ b/src/Magento/FunctionalTestingFramework/Page/Objects/SectionObject.php @@ -32,17 +32,26 @@ class SectionObject */ private $filename; + /** + * Deprecated message. + * + * @var string + */ + private $deprecated; + /** * SectionObject constructor. - * @param string $name - * @param array $elements - * @param string $filename + * @param string $name + * @param array $elements + * @param string|null $filename + * @param string|null $deprecated */ - public function __construct($name, $elements, $filename = null) + public function __construct($name, $elements, $filename = null, $deprecated = null) { $this->name = $name; $this->elements = $elements; $this->filename = $filename; + $this->deprecated = $deprecated; } /** @@ -55,6 +64,16 @@ public function getName() return $this->name; } + /** + * Getter for the deprecated attr of the section + * + * @return string + */ + public function getDeprecated() + { + return $this->deprecated; + } + /** * Getter for the Section Filename * diff --git a/src/Magento/FunctionalTestingFramework/Page/etc/PageObject.xsd b/src/Magento/FunctionalTestingFramework/Page/etc/PageObject.xsd index c6f399a67..65ac8290d 100644 --- a/src/Magento/FunctionalTestingFramework/Page/etc/PageObject.xsd +++ b/src/Magento/FunctionalTestingFramework/Page/etc/PageObject.xsd @@ -48,6 +48,14 @@ </xs:documentation> </xs:annotation> </xs:attribute> + <xs:attribute type="xs:string" name="deprecated"> + <xs:annotation> + <xs:documentation> + Message and flag which shows that entity is deprecated. + </xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attribute type="notEmptyType" name="url" use="required"> <xs:annotation> <xs:documentation> diff --git a/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd b/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd index 44e057369..1b5b44ddd 100644 --- a/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd +++ b/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd @@ -44,6 +44,13 @@ </xs:documentation> </xs:annotation> </xs:attribute> + <xs:attribute type="xs:string" name="deprecated"> + <xs:annotation> + <xs:documentation> + Message and flag which shows that entity is deprecated. + </xs:documentation> + </xs:annotation> + </xs:attribute> <xs:attributeGroup ref="removeAttribute"/> <xs:attribute type="xs:string" name="filename"/> </xs:complexType> @@ -58,6 +65,13 @@ </xs:documentation> </xs:annotation> </xs:attribute> + <xs:attribute type="xs:string" name="deprecated"> + <xs:annotation> + <xs:documentation> + Message and flag which shows that entity is deprecated. + </xs:documentation> + </xs:annotation> + </xs:attribute> <xs:attribute type="uiElementType" name="type" use="required"> <xs:annotation> <xs:documentation> diff --git a/src/Magento/FunctionalTestingFramework/StaticCheck/ActionGroupArgumentsCheck.php b/src/Magento/FunctionalTestingFramework/StaticCheck/ActionGroupArgumentsCheck.php new file mode 100644 index 000000000..32c7661fe --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/StaticCheck/ActionGroupArgumentsCheck.php @@ -0,0 +1,211 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\FunctionalTestingFramework\StaticCheck; + +use Magento\FunctionalTestingFramework\Config\MftfApplicationConfig; +use Magento\FunctionalTestingFramework\Exceptions\XmlException; +use Magento\FunctionalTestingFramework\Test\Handlers\ActionGroupObjectHandler; +use Magento\FunctionalTestingFramework\Test\Objects\ActionGroupObject; +use Symfony\Component\Console\Input\InputInterface; +use Magento\FunctionalTestingFramework\Util\ModuleResolver; +use Symfony\Component\Finder\Finder; +use Exception; + +/** + * Class ActionGroupArgumentsCheck + * @package Magento\FunctionalTestingFramework\StaticCheck + */ +class ActionGroupArgumentsCheck implements StaticCheckInterface +{ + + const ACTIONGROUP_XML_REGEX_PATTERN = '/<actionGroup\sname=(?: (?!<\/actionGroup>).)*/mxs'; + const ACTIONGROUP_ARGUMENT_REGEX_PATTERN = '/<argument[^\/>]*name="([^"\']*)/mxs'; + const ACTIONGROUP_NAME_REGEX_PATTERN = '/<actionGroup name=["\']([^\'"]*)/'; + + const ERROR_LOG_FILENAME = 'mftf-arguments-checks'; + const ERROR_LOG_MESSAGE = 'MFTF Action Group Unused Arguments Check'; + + /** + * Array containing all errors found after running the execute() function. + * @var array + */ + private $errors = []; + + /** + * String representing the output summary found after running the execute() function. + * @var string + */ + private $output; + + /** + * Checks unused arguments in action groups and prints out error to file. + * + * @param InputInterface $input + * @return void + * @throws Exception + */ + public function execute(InputInterface $input) + { + MftfApplicationConfig::create( + true, + MftfApplicationConfig::UNIT_TEST_PHASE, + false, + MftfApplicationConfig::LEVEL_NONE, + true + ); + + $allModules = ModuleResolver::getInstance()->getModulesPath(); + + $actionGroupXmlFiles = StaticCheckHelper::buildFileList( + $allModules, + DIRECTORY_SEPARATOR . 'ActionGroup' . DIRECTORY_SEPARATOR + ); + + $this->errors = $this->findErrorsInFileSet($actionGroupXmlFiles); + + $this->output = StaticCheckHelper::printErrorsToFile( + $this->errors, + self::ERROR_LOG_FILENAME, + self::ERROR_LOG_MESSAGE + ); + } + + /** + * Return array containing all errors found after running the execute() function. + * @return array + */ + public function getErrors() + { + return $this->errors; + } + + /** + * Return string of a short human readable result of the check. For example: "No unused arguments found." + * @return string + */ + public function getOutput() + { + return $this->output; + } + + /** + * Finds all unused arguments in given set of actionGroup files + * @param Finder $files + * @return array $testErrors + */ + private function findErrorsInFileSet($files) + { + $actionGroupErrors = []; + foreach ($files as $filePath) { + $contents = file_get_contents($filePath); + preg_match_all(self::ACTIONGROUP_XML_REGEX_PATTERN, $contents, $actionGroups); + $actionGroupToArguments = $this->buildUnusedArgumentList($actionGroups[0]); + $actionGroupErrors += $this->setErrorOutput($actionGroupToArguments, $filePath); + } + return $actionGroupErrors; + } + + /** + * Builds array of action group => unused arguments + * @param array $actionGroups + * @return array $actionGroupToArguments + */ + private function buildUnusedArgumentList($actionGroups) + { + $actionGroupToArguments = []; + + foreach ($actionGroups as $actionGroupXml) { + preg_match(self::ACTIONGROUP_NAME_REGEX_PATTERN, $actionGroupXml, $actionGroupName); + $unusedArguments = $this->findUnusedArguments($actionGroupXml); + if (!empty($unusedArguments)) { + $actionGroupToArguments[$actionGroupName[1]] = $unusedArguments; + } + } + return $actionGroupToArguments; + } + + /** + * Returns unused arguments in an action group + * @param string $actionGroupXml + * @return array + */ + private function findUnusedArguments($actionGroupXml) + { + $unusedArguments = []; + + preg_match_all(self::ACTIONGROUP_ARGUMENT_REGEX_PATTERN, $actionGroupXml, $arguments); + preg_match(self::ACTIONGROUP_NAME_REGEX_PATTERN, $actionGroupXml, $actionGroupName); + try { + $actionGroup = ActionGroupObjectHandler::getInstance()->getObject($actionGroupName[1]); + } catch (XmlException $e) { + } + foreach ($arguments[1] as $argument) { + //pattern to match all argument references + $patterns = [ + '(\{{2}' . $argument . '(\.[a-zA-Z0-9_\[\]\(\).,\'\/ ]+)?}{2})', + '([(,\s\'$$]' . $argument . '(\.[a-zA-Z0-9_$\[\]]+)?[),\s\'])' + ]; + // matches entity references + if (preg_match($patterns[0], $actionGroupXml)) { + continue; + } + //matches parametrized references + if (preg_match($patterns[1], $actionGroupXml)) { + continue; + } + //for extending action groups, exclude arguments that are also defined in parent action group + if ($this->isParentActionGroupArgument($argument, $actionGroup)) { + continue; + } + $unusedArguments[] = $argument; + } + return $unusedArguments; + } + + /** + * Checks if the argument is also defined in the parent for extending action groups. + * @param string $argument + * @param ActionGroupObject $actionGroup + * @return boolean + */ + private function isParentActionGroupArgument($argument, $actionGroup) + { + $parentActionGroupName = $actionGroup->getParentName(); + if ($parentActionGroupName !== null) { + $parentActionGroup = ActionGroupObjectHandler::getInstance()->getObject($parentActionGroupName); + $parentArguments = $parentActionGroup->getArguments(); + foreach ($parentArguments as $parentArgument) { + if ($argument === $parentArgument->getName()) { + return true; + } + } + } + return false; + } + + /** + * Builds and returns error output for violating references + * + * @param array $actionGroupToArguments + * @param string $path + * @return mixed + */ + private function setErrorOutput($actionGroupToArguments, $path) + { + $actionGroupErrors = []; + if (!empty($actionGroupToArguments)) { + // Build error output + $errorOutput = "\nFile \"{$path->getRealPath()}\""; + $errorOutput .= "\ncontains action group(s) with unused arguments.\n\t\t"; + foreach ($actionGroupToArguments as $actionGroup => $arguments) { + $errorOutput .= "\n\t {$actionGroup} has unused argument(s): " . implode(", ", $arguments); + } + $actionGroupErrors[$path->getRealPath()][] = $errorOutput; + } + return $actionGroupErrors; + } +} diff --git a/src/Magento/FunctionalTestingFramework/StaticCheck/StaticCheckHelper.php b/src/Magento/FunctionalTestingFramework/StaticCheck/StaticCheckHelper.php new file mode 100644 index 000000000..f534544fc --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/StaticCheck/StaticCheckHelper.php @@ -0,0 +1,56 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\FunctionalTestingFramework\StaticCheck; + +use Symfony\Component\Finder\Finder; + +class StaticCheckHelper +{ + /** + * Prints out given errors to file, and returns summary result string + * @param array $errors + * @param string $filename + * @param string $message + * @return string + */ + public static function printErrorsToFile($errors, $filename, $message) + { + if (empty($errors)) { + return $message . ": No errors found."; + } + + $outputPath = getcwd() . DIRECTORY_SEPARATOR . $filename . ".txt"; + $fileResource = fopen($outputPath, 'w'); + + foreach ($errors as $test => $error) { + fwrite($fileResource, $error[0] . PHP_EOL); + } + + fclose($fileResource); + $errorCount = count($errors); + $output = $message . ": Errors found across {$errorCount} file(s). Error details output to {$outputPath}"; + + return $output; + } + + /** + * Builds list of all XML files in given modulePaths + path given + * @param array $modulePaths + * @param string $path + * @return Finder + */ + public static function buildFileList($modulePaths, $path) + { + $finder = new Finder(); + foreach ($modulePaths as $modulePath) { + if (!realpath($modulePath . $path)) { + continue; + } + $finder->files()->in($modulePath . $path)->name("*.xml"); + } + return $finder->files(); + } +} diff --git a/src/Magento/FunctionalTestingFramework/StaticCheck/StaticChecksList.php b/src/Magento/FunctionalTestingFramework/StaticCheck/StaticChecksList.php index f3cf20739..ffa63389d 100644 --- a/src/Magento/FunctionalTestingFramework/StaticCheck/StaticChecksList.php +++ b/src/Magento/FunctionalTestingFramework/StaticCheck/StaticChecksList.php @@ -29,6 +29,7 @@ public function __construct(array $checks = []) { $this->checks = [ 'testDependencies' => new TestDependencyCheck(), + 'actionGroupArguments' => new ActionGroupArgumentsCheck(), ] + $checks; } diff --git a/src/Magento/FunctionalTestingFramework/StaticCheck/TestDependencyCheck.php b/src/Magento/FunctionalTestingFramework/StaticCheck/TestDependencyCheck.php index 19725dc18..3c3ed6a37 100644 --- a/src/Magento/FunctionalTestingFramework/StaticCheck/TestDependencyCheck.php +++ b/src/Magento/FunctionalTestingFramework/StaticCheck/TestDependencyCheck.php @@ -32,6 +32,9 @@ class TestDependencyCheck implements StaticCheckInterface const ACTIONGROUP_REGEX_PATTERN = '/ref=["\']([^\'"]*)/'; const ACTIONGROUP_ARGUMENT_REGEX_PATTERN = '/<argument[^\/>]*name="([^"\']*)/'; + const ERROR_LOG_FILENAME = 'mftf-dependency-checks'; + const ERROR_LOG_MESSAGE = 'MFTF File Dependency Check'; + /** * Array of FullModuleName => [dependencies] * @var array @@ -113,9 +116,9 @@ public function execute(InputInterface $input) DIRECTORY_SEPARATOR . 'Data' . DIRECTORY_SEPARATOR, ]; // These files can contain references to other modules. - $testXmlFiles = $this->buildFileList($allModules, $filePaths[0]); - $actionGroupXmlFiles = $this->buildFileList($allModules, $filePaths[1]); - $dataXmlFiles= $this->buildFileList($allModules, $filePaths[2]); + $testXmlFiles = StaticCheckHelper::buildFileList($allModules, $filePaths[0]); + $actionGroupXmlFiles = StaticCheckHelper::buildFileList($allModules, $filePaths[1]); + $dataXmlFiles= StaticCheckHelper::buildFileList($allModules, $filePaths[2]); $this->errors = []; $this->errors += $this->findErrorsInFileSet($testXmlFiles); @@ -123,7 +126,11 @@ public function execute(InputInterface $input) $this->errors += $this->findErrorsInFileSet($dataXmlFiles); // hold on to the output and print any errors to a file - $this->output = $this->printErrorsToFile(); + $this->output = StaticCheckHelper::printErrorsToFile( + $this->errors, + self::ERROR_LOG_FILENAME, + self::ERROR_LOG_MESSAGE + ); } /** @@ -413,24 +420,6 @@ private function getModuleDependenciesFromReferences($array) return $filenames; } - /** - * Builds list of all XML files in given modulePaths + path given - * @param string $modulePaths - * @param string $path - * @return Finder - */ - private function buildFileList($modulePaths, $path) - { - $finder = new Finder(); - foreach ($modulePaths as $modulePath) { - if (!realpath($modulePath . $path)) { - continue; - } - $finder->files()->in($modulePath . $path)->name("*.xml"); - } - return $finder->files(); - } - /** * Attempts to find any MFTF entity by its name. Returns null if none are found. * @param string $name @@ -461,32 +450,4 @@ private function findEntity($name) } return null; } - - /** - * Prints out given errors to file, and returns summary result string - * @return string - */ - private function printErrorsToFile() - { - $errors = $this->getErrors(); - - if (empty($errors)) { - return "No Dependency errors found."; - } - - $outputPath = getcwd() . DIRECTORY_SEPARATOR . "mftf-dependency-checks.txt"; - $fileResource = fopen($outputPath, 'w'); - $header = "MFTF File Dependency Check:\n"; - fwrite($fileResource, $header); - - foreach ($errors as $test => $error) { - fwrite($fileResource, $error[0] . PHP_EOL); - } - - fclose($fileResource); - $errorCount = count($errors); - $output = "Dependency errors found across {$errorCount} file(s). Error details output to {$outputPath}"; - - return $output; - } } diff --git a/src/Magento/FunctionalTestingFramework/Suite/Handlers/SuiteObjectHandler.php b/src/Magento/FunctionalTestingFramework/Suite/Handlers/SuiteObjectHandler.php index 30a67c31d..bbaaf4379 100644 --- a/src/Magento/FunctionalTestingFramework/Suite/Handlers/SuiteObjectHandler.php +++ b/src/Magento/FunctionalTestingFramework/Suite/Handlers/SuiteObjectHandler.php @@ -5,6 +5,7 @@ */ namespace Magento\FunctionalTestingFramework\Suite\Handlers; +use Magento\FunctionalTestingFramework\Exceptions\TestReferenceException; use Magento\FunctionalTestingFramework\Exceptions\XmlException; use Magento\FunctionalTestingFramework\ObjectManager\ObjectHandlerInterface; use Magento\FunctionalTestingFramework\ObjectManagerFactory; @@ -73,7 +74,7 @@ public static function getInstance(): ObjectHandlerInterface public function getObject($objectName): SuiteObject { if (!array_key_exists($objectName, $this->suiteObjects)) { - trigger_error("Suite ${objectName} is not defined.", E_USER_ERROR); + throw new TestReferenceException("Suite ${objectName} is not defined in xml."); } return $this->suiteObjects[$objectName]; } diff --git a/src/Magento/FunctionalTestingFramework/Suite/SuiteGenerator.php b/src/Magento/FunctionalTestingFramework/Suite/SuiteGenerator.php index 4df7daa75..e3da10497 100644 --- a/src/Magento/FunctionalTestingFramework/Suite/SuiteGenerator.php +++ b/src/Magento/FunctionalTestingFramework/Suite/SuiteGenerator.php @@ -178,12 +178,14 @@ private function validateTestsReferencedInSuite($suiteName, $testsReferenced, $o { $suiteRef = $originalSuiteName ?? $suiteName; $possibleTestRef = SuiteObjectHandler::getInstance()->getObject($suiteRef)->getTests(); - $errorMsg = "Cannot reference tests whcih are not declared as part of suite."; + $errorMsg = "Cannot reference tests which are not declared as part of suite"; $invalidTestRef = array_diff($testsReferenced, array_keys($possibleTestRef)); if (!empty($invalidTestRef)) { - throw new TestReferenceException($errorMsg, ['suite' => $suiteRef, 'test' => $invalidTestRef]); + $testList = implode("\", \"", $invalidTestRef); + $fullError = $errorMsg . " (Suite: \"{$suiteRef}\" Tests: \"{$testList}\")"; + throw new TestReferenceException($fullError, ['suite' => $suiteRef, 'test' => $invalidTestRef]); } } diff --git a/src/Magento/FunctionalTestingFramework/Suite/etc/suiteSchema.xsd b/src/Magento/FunctionalTestingFramework/Suite/etc/suiteSchema.xsd index 34ef37594..b36e35517 100644 --- a/src/Magento/FunctionalTestingFramework/Suite/etc/suiteSchema.xsd +++ b/src/Magento/FunctionalTestingFramework/Suite/etc/suiteSchema.xsd @@ -61,6 +61,13 @@ <xs:element type="hookType" name="after" maxOccurs="1"/> </xs:choice> <xs:attribute type="xs:string" name="name"/> + <xs:attribute type="xs:string" name="deprecated"> + <xs:annotation> + <xs:documentation> + Message and flag which shows that entity is deprecated. + </xs:documentation> + </xs:annotation> + </xs:attribute> </xs:complexType> <xs:complexType name="suiteConfigType"> <xs:choice minOccurs="0" maxOccurs="unbounded"> diff --git a/src/Magento/FunctionalTestingFramework/Test/Objects/ActionGroupObject.php b/src/Magento/FunctionalTestingFramework/Test/Objects/ActionGroupObject.php index fc38a7fce..dc1b158e1 100644 --- a/src/Magento/FunctionalTestingFramework/Test/Objects/ActionGroupObject.php +++ b/src/Magento/FunctionalTestingFramework/Test/Objects/ActionGroupObject.php @@ -94,6 +94,13 @@ class ActionGroupObject */ private $cachedStepKeys = null; + /** + * Deprecation message. + * + * @var string|null + */ + private $deprecated; + /** * ActionGroupObject constructor. * @@ -103,9 +110,17 @@ class ActionGroupObject * @param array $actions * @param string $parentActionGroup * @param string $filename + * @param string|null $deprecated */ - public function __construct($name, $annotations, $arguments, $actions, $parentActionGroup, $filename = null) - { + public function __construct( + $name, + $annotations, + $arguments, + $actions, + $parentActionGroup, + $filename = null, + $deprecated = null + ) { $this->varAttributes = array_merge( ActionObject::SELECTOR_ENABLED_ATTRIBUTES, ActionObject::DATA_ENABLED_ATTRIBUTES @@ -117,6 +132,17 @@ public function __construct($name, $annotations, $arguments, $actions, $parentAc $this->parsedActions = $actions; $this->parentActionGroup = $parentActionGroup; $this->filename = $filename; + $this->deprecated = $deprecated; + } + + /** + * Returns deprecated messages. + * + * @return string|null + */ + public function getDeprecated() + { + return $this->deprecated; } /** @@ -213,7 +239,8 @@ private function getResolvedActionsWithArgs($arguments, $actionReferenceKey) $action->getLinkedAction() == null ? null : $action->getLinkedAction() . ucfirst($actionReferenceKey), $orderOffset, [self::ACTION_GROUP_ORIGIN_NAME => $this->name, - self::ACTION_GROUP_ORIGIN_TEST_REF => $actionReferenceKey] + self::ACTION_GROUP_ORIGIN_TEST_REF => $actionReferenceKey], + $action->getDeprecatedUsages() ); } @@ -543,10 +570,20 @@ private function addContextCommentsToActionList($actionList, $actionReferenceKey { $actionStartComment = self::ACTION_GROUP_CONTEXT_START . "[" . $actionReferenceKey . "] " . $this->name; $actionEndComment = self::ACTION_GROUP_CONTEXT_END . "[" . $actionReferenceKey . "] " . $this->name; + + $deprecationNotices = []; + if ($this->getDeprecated() !== null) { + $deprecationNotices[] = "DEPRECATED ACTION GROUP in Test: " . $this->name . ' ' . $this->getDeprecated(); + } + $startAction = new ActionObject( $actionStartComment, ActionObject::ACTION_TYPE_COMMENT, - [ActionObject::ACTION_ATTRIBUTE_USERINPUT => $actionStartComment] + [ActionObject::ACTION_ATTRIBUTE_USERINPUT => $actionStartComment], + null, + ActionObject::MERGE_ACTION_ORDER_BEFORE, + null, + $deprecationNotices ); $endAction = new ActionObject( $actionEndComment, diff --git a/src/Magento/FunctionalTestingFramework/Test/Objects/ActionObject.php b/src/Magento/FunctionalTestingFramework/Test/Objects/ActionObject.php index 53743330e..45b5db69d 100644 --- a/src/Magento/FunctionalTestingFramework/Test/Objects/ActionObject.php +++ b/src/Magento/FunctionalTestingFramework/Test/Objects/ActionObject.php @@ -84,6 +84,13 @@ class ActionObject */ private $stepKey; + /** + * Array of deprecated entities used in action. + * + * @var array + */ + private $deprecatedUsage = []; + /** * The type of action (e.g. fillField, createData, etc) * @@ -142,6 +149,7 @@ class ActionObject * @param string|null $linkedAction * @param string $order * @param array $actionOrigin + * @param array $deprecatedUsage */ public function __construct( $stepKey, @@ -149,13 +157,15 @@ public function __construct( $actionAttributes, $linkedAction = null, $order = ActionObject::MERGE_ACTION_ORDER_BEFORE, - $actionOrigin = null + $actionOrigin = null, + $deprecatedUsage = [] ) { $this->stepKey = $stepKey; $this->type = $type === self::COMMENT_ACTION ? self::ACTION_TYPE_COMMENT : $type; $this->actionAttributes = $actionAttributes; $this->linkedAction = $linkedAction; $this->actionOrigin = $actionOrigin; + $this->deprecatedUsage = $deprecatedUsage; if ($order === ActionObject::MERGE_ACTION_ORDER_AFTER) { $this->orderOffset = 1; @@ -304,7 +314,8 @@ public function trimAssertionAttributes() if ($appConfig->getPhase() == MftfApplicationConfig::GENERATION_PHASE && $appConfig->verboseEnabled()) { LoggingUtil::getInstance()->getLogger(ActionObject::class)->deprecation( "use of one line Assertion actions will be deprecated in MFTF 3.0.0, please use nested syntax", - ["action" => $this->type, "stepKey" => $this->stepKey] + ["action" => $this->type, "stepKey" => $this->stepKey], + true ); } return; @@ -534,11 +545,18 @@ private function findAndReplaceReferences($objectHandler, $inputString) $replacement = null; $parameterized = false; } elseif (get_class($obj) == PageObject::class) { + if ($obj->getDeprecated() !== null) { + $this->deprecatedUsage[] = "DEPRECATED PAGE in Test: " . $match . ' ' . $obj->getDeprecated(); + } $this->validateUrlAreaAgainstActionType($obj); $replacement = $obj->getUrl(); $parameterized = $obj->isParameterized(); } elseif (get_class($obj) == SectionObject::class) { + if ($obj->getDeprecated() !== null) { + $this->deprecatedUsage[] = "DEPRECATED SECTION in Test: " . $match . ' ' . $obj->getDeprecated(); + } list(,$objField) = $this->stripAndSplitReference($match); + if ($obj->getElement($objField) == null) { throw new TestReferenceException( "Could not resolve entity reference \"{$inputString}\" " @@ -549,7 +567,15 @@ private function findAndReplaceReferences($objectHandler, $inputString) $parameterized = $obj->getElement($objField)->isParameterized(); $replacement = $obj->getElement($objField)->getPrioritizedSelector(); $this->setTimeout($obj->getElement($objField)->getTimeout()); + if ($obj->getElement($objField)->getDeprecated() !== null) { + $this->deprecatedUsage[] = "DEPRECATED ELEMENT in Test: " . $match . ' ' + . $obj->getElement($objField)->getDeprecated(); + } } elseif (get_class($obj) == EntityDataObject::class) { + if ($obj->getDeprecated() !== null) { + $this->deprecatedUsage[] = "DEPRECATED DATA ENTITY in Test: " + . $match . ' ' . $obj->getDeprecated(); + } $replacement = $this->resolveEntityDataObjectReference($obj, $match); if (is_array($replacement)) { @@ -766,4 +792,14 @@ private function checkParameterCount($matches, $parameters, $reference) ); } } + + /** + * Returns array of deprecated usages in Action. + * + * @return array + */ + public function getDeprecatedUsages() + { + return $this->deprecatedUsage; + } } diff --git a/src/Magento/FunctionalTestingFramework/Test/Util/ActionGroupAnnotationExtractor.php b/src/Magento/FunctionalTestingFramework/Test/Util/ActionGroupAnnotationExtractor.php index 44b004105..9d3a9877e 100644 --- a/src/Magento/FunctionalTestingFramework/Test/Util/ActionGroupAnnotationExtractor.php +++ b/src/Magento/FunctionalTestingFramework/Test/Util/ActionGroupAnnotationExtractor.php @@ -64,7 +64,8 @@ private function validateMissingAnnotations($annotationObjects, $filename) $message = "Action Group File {$filename} is missing required annotations."; LoggingUtil::getInstance()->getLogger(ActionObject::class)->deprecation( $message, - ["actionGroup" => $filename, "missingAnnotations" => implode(", ", $missingAnnotations)] + ["actionGroup" => $filename, "missingAnnotations" => implode(", ", $missingAnnotations)], + true ); } } diff --git a/src/Magento/FunctionalTestingFramework/Test/Util/ActionGroupObjectExtractor.php b/src/Magento/FunctionalTestingFramework/Test/Util/ActionGroupObjectExtractor.php index e41329cb3..618cddc09 100644 --- a/src/Magento/FunctionalTestingFramework/Test/Util/ActionGroupObjectExtractor.php +++ b/src/Magento/FunctionalTestingFramework/Test/Util/ActionGroupObjectExtractor.php @@ -11,6 +11,8 @@ use Magento\FunctionalTestingFramework\Test\Objects\ActionGroupObject; use Magento\FunctionalTestingFramework\Test\Objects\ActionObject; use Magento\FunctionalTestingFramework\Test\Objects\ArgumentObject; +use Magento\FunctionalTestingFramework\Test\Objects\TestObject; +use Magento\FunctionalTestingFramework\Util\Logger\LoggingUtil; /** * Class ActionGroupObjectExtractor @@ -58,7 +60,15 @@ public function __construct() public function extractActionGroup($actionGroupData) { $arguments = []; + $deprecated = null; + if (array_key_exists(self::OBJ_DEPRECATED, $actionGroupData)) { + $deprecated = $actionGroupData[self::OBJ_DEPRECATED]; + LoggingUtil::getInstance()->getLogger(ActionGroupObject::class)->deprecation( + $deprecated, + ["actionGroupName" => $actionGroupData[self::FILENAME], "deprecatedActionGroup" => $deprecated] + ); + } $actionGroupReference = $actionGroupData[self::EXTENDS_ACTION_GROUP] ?? null; $actionData = $this->stripDescriptorTags( $actionGroupData, @@ -69,7 +79,8 @@ public function extractActionGroup($actionGroupData) self::FILENAME, self::ACTION_GROUP_INSERT_BEFORE, self::ACTION_GROUP_INSERT_AFTER, - self::EXTENDS_ACTION_GROUP + self::EXTENDS_ACTION_GROUP, + 'deprecated' ); // TODO filename is now available to the ActionGroupObject, integrate this into debug and error statements @@ -99,7 +110,8 @@ public function extractActionGroup($actionGroupData) $arguments, $actions, $actionGroupReference, - $actionGroupData[self::FILENAME] + $actionGroupData[self::FILENAME], + $deprecated ); } diff --git a/src/Magento/FunctionalTestingFramework/Test/Util/ActionMergeUtil.php b/src/Magento/FunctionalTestingFramework/Test/Util/ActionMergeUtil.php index cd81ade24..159822db1 100644 --- a/src/Magento/FunctionalTestingFramework/Test/Util/ActionMergeUtil.php +++ b/src/Magento/FunctionalTestingFramework/Test/Util/ActionMergeUtil.php @@ -128,7 +128,8 @@ private function resolveSecretFieldAccess($resolvedActions) $action->getCustomActionAttributes(), $action->getLinkedAction(), $action->getOrderOffset(), - $action->getActionOrigin() + $action->getActionOrigin(), + $action->getDeprecatedUsages() ); $actions[$action->getStepKey()] = $action; diff --git a/src/Magento/FunctionalTestingFramework/Test/Util/AnnotationExtractor.php b/src/Magento/FunctionalTestingFramework/Test/Util/AnnotationExtractor.php index d30ed351e..1859f3195 100644 --- a/src/Magento/FunctionalTestingFramework/Test/Util/AnnotationExtractor.php +++ b/src/Magento/FunctionalTestingFramework/Test/Util/AnnotationExtractor.php @@ -164,7 +164,8 @@ private function validateMissingAnnotations($annotationObjects, $filename) $message = "Test {$filename} is missing required annotations."; LoggingUtil::getInstance()->getLogger(ActionObject::class)->deprecation( $message, - ["testName" => $filename, "missingAnnotations" => implode(", ", $missingAnnotations)] + ["testName" => $filename, "missingAnnotations" => implode(", ", $missingAnnotations)], + true ); } } diff --git a/src/Magento/FunctionalTestingFramework/Test/Util/BaseObjectExtractor.php b/src/Magento/FunctionalTestingFramework/Test/Util/BaseObjectExtractor.php index 26644937d..516f79d14 100644 --- a/src/Magento/FunctionalTestingFramework/Test/Util/BaseObjectExtractor.php +++ b/src/Magento/FunctionalTestingFramework/Test/Util/BaseObjectExtractor.php @@ -13,6 +13,7 @@ class BaseObjectExtractor { const NODE_NAME = 'nodeName'; const NAME = 'name'; + const OBJ_DEPRECATED = 'deprecated'; /** * BaseObjectExtractor constructor. diff --git a/src/Magento/FunctionalTestingFramework/Test/Util/TestObjectExtractor.php b/src/Magento/FunctionalTestingFramework/Test/Util/TestObjectExtractor.php index d5420e355..a25db0935 100644 --- a/src/Magento/FunctionalTestingFramework/Test/Util/TestObjectExtractor.php +++ b/src/Magento/FunctionalTestingFramework/Test/Util/TestObjectExtractor.php @@ -8,13 +8,16 @@ use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; use Magento\FunctionalTestingFramework\Exceptions\XmlException; +use Magento\FunctionalTestingFramework\Page\Objects\ElementObject; use Magento\FunctionalTestingFramework\Test\Objects\ActionObject; use Magento\FunctionalTestingFramework\Test\Objects\TestObject; +use Magento\FunctionalTestingFramework\Util\Logger\LoggingUtil; use Magento\FunctionalTestingFramework\Util\ModulePathExtractor; use Magento\FunctionalTestingFramework\Util\Validation\NameValidationUtil; /** * Class TestObjectExtractor + * @SuppressWarnings(PHPMD) */ class TestObjectExtractor extends BaseObjectExtractor { @@ -95,7 +98,8 @@ public function extractTestData($testData) $fileNames = explode(",", $filename); $baseFileName = $fileNames[0]; $module = $this->modulePathExtractor->extractModuleName($baseFileName); - $testReference = $testData['extends'] ?? null; + $testReference = $testData['extends'] ?? null; + $deprecated = isset($testData[self::OBJ_DEPRECATED]) ? $testData[self::OBJ_DEPRECATED] : null; $testActions = $this->stripDescriptorTags( $testData, self::NODE_NAME, @@ -107,6 +111,7 @@ public function extractTestData($testData) self::TEST_INSERT_BEFORE, self::TEST_INSERT_AFTER, self::TEST_FILENAME, + self::OBJ_DEPRECATED, 'extends' ); @@ -122,12 +127,20 @@ public function extractTestData($testData) // when $fileNames is not available if (!isset($testAnnotations["description"])) { $testAnnotations["description"] = []; + } else { + $testAnnotations["description"]['main'] = $testAnnotations["description"][0]; + unset($testAnnotations["description"][0]); + } + $testAnnotations["description"]['test_files'] = $this->appendFileNamesInDescriptionAnnotation($fileNames); + + $testAnnotations["description"][self::OBJ_DEPRECATED] = []; + if ($deprecated !== null) { + $testAnnotations["description"][self::OBJ_DEPRECATED][] = $deprecated; + LoggingUtil::getInstance()->getLogger(TestObject::class)->deprecation( + $deprecated, + ["testName" => $filename, "deprecatedTest" => $deprecated] + ); } - $description = $testAnnotations["description"][0] ?? ''; - $testAnnotations["description"][0] = $this->appendFileNamesInDescriptionAnnotation( - $description, - $fileNames - ); // extract before if (array_key_exists(self::TEST_BEFORE_HOOK, $testData) && is_array($testData[self::TEST_BEFORE_HOOK])) { @@ -152,6 +165,10 @@ public function extractTestData($testData) ); } + if (!empty($testData[self::OBJ_DEPRECATED])) { + $testAnnotations[self::OBJ_DEPRECATED] = $testData[self::OBJ_DEPRECATED]; + } + // TODO extract filename info and store try { return new TestObject( @@ -170,14 +187,13 @@ public function extractTestData($testData) /** * Append names of test files, including merge files, in description annotation * - * @param string $description - * @param array $fileNames + * @param array $fileNames * * @return string */ - private function appendFileNamesInDescriptionAnnotation($description, $fileNames) + private function appendFileNamesInDescriptionAnnotation(array $fileNames) { - $description .= '<br><br><b><font size=+0.9>Test files</font></b><br><br>'; + $filePaths = '<h3>Test files</h3>'; foreach ($fileNames as $fileName) { if (!empty($fileName) && realpath($fileName) !== false) { @@ -187,11 +203,11 @@ private function appendFileNamesInDescriptionAnnotation($description, $fileNames DIRECTORY_SEPARATOR ); if (!empty($relativeFileName)) { - $description .= $relativeFileName . '<br>'; + $filePaths .= $relativeFileName . '<br>'; } } } - return $description; + return $filePaths; } } diff --git a/src/Magento/FunctionalTestingFramework/Test/etc/Actions/customActions.xsd b/src/Magento/FunctionalTestingFramework/Test/etc/Actions/customActions.xsd index 2424d0d31..8670d9885 100644 --- a/src/Magento/FunctionalTestingFramework/Test/etc/Actions/customActions.xsd +++ b/src/Magento/FunctionalTestingFramework/Test/etc/Actions/customActions.xsd @@ -12,6 +12,7 @@ <xs:group name="customTags"> <xs:choice> <xs:element type="magentoCLIType" name="magentoCLI" minOccurs="0" maxOccurs="unbounded"/> + <xs:element type="magentoCronType" name="magentoCron" minOccurs="0" maxOccurs="unbounded"/> <xs:element type="closeAdminNotificationType" name="closeAdminNotification" minOccurs="0" maxOccurs="unbounded"/> <xs:element type="searchAndMultiSelectOptionType" name="searchAndMultiSelectOption" minOccurs="0" maxOccurs="unbounded"/> <xs:element type="selectMultipleOptionsType" name="selectMultipleOptions" minOccurs="0" maxOccurs="unbounded"/> @@ -62,6 +63,40 @@ </xs:simpleContent> </xs:complexType> + <xs:complexType name="magentoCronType"> + <xs:annotation> + <xs:documentation> + Executes Magento Cron Jobs (selected groups) + </xs:documentation> + </xs:annotation> + <xs:simpleContent> + <xs:extension base="xs:string"> + <xs:attribute type="xs:string" name="groups"> + <xs:annotation> + <xs:documentation> + Cron groups to be executed (separated by space) + </xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attribute type="xs:string" name="arguments"> + <xs:annotation> + <xs:documentation> + Arguments for Magento CLI command, will not be escaped. + </xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attribute type="xs:string" name="timeout"> + <xs:annotation> + <xs:documentation> + Idle timeout in seconds, defaulted to 60s when not specified. + </xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attributeGroup ref="commonActionAttributes"/> + </xs:extension> + </xs:simpleContent> + </xs:complexType> + <xs:complexType name="closeAdminNotificationType"> <xs:annotation> <xs:documentation> @@ -285,4 +320,4 @@ <xs:enumeration value="desc"/> </xs:restriction> </xs:simpleType> -</xs:schema> \ No newline at end of file +</xs:schema> diff --git a/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd b/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd index 6e9e798f1..bd32ba879 100644 --- a/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd +++ b/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd @@ -43,6 +43,13 @@ <xs:attribute type="xs:string" name="insertBefore"/> <xs:attribute type="xs:string" name="insertAfter"/> <xs:attribute type="xs:string" name="extends"/> + <xs:attribute type="xs:string" name="deprecated"> + <xs:annotation> + <xs:documentation> + Message and flag which shows that entity is deprecated. + </xs:documentation> + </xs:annotation> + </xs:attribute> </xs:complexType> <xs:simpleType name="dataTypeEnum" final="restriction"> <xs:restriction base="xs:string"> diff --git a/src/Magento/FunctionalTestingFramework/Test/etc/mergedTestSchema.xsd b/src/Magento/FunctionalTestingFramework/Test/etc/mergedTestSchema.xsd index fe45945ee..6ded366bb 100644 --- a/src/Magento/FunctionalTestingFramework/Test/etc/mergedTestSchema.xsd +++ b/src/Magento/FunctionalTestingFramework/Test/etc/mergedTestSchema.xsd @@ -86,6 +86,13 @@ <xs:attribute type="xs:string" name="insertBefore"/> <xs:attribute type="xs:string" name="insertAfter"/> <xs:attribute type="xs:string" name="extends"/> + <xs:attribute type="xs:string" name="deprecated"> + <xs:annotation> + <xs:documentation> + Message and flag which shows that entity is deprecated. + </xs:documentation> + </xs:annotation> + </xs:attribute> </xs:complexType> <xs:group name="testTypeTags"> <xs:choice> diff --git a/src/Magento/FunctionalTestingFramework/Util/Logger/MftfLogger.php b/src/Magento/FunctionalTestingFramework/Util/Logger/MftfLogger.php index 0a52f6b6b..3983a755a 100644 --- a/src/Magento/FunctionalTestingFramework/Util/Logger/MftfLogger.php +++ b/src/Magento/FunctionalTestingFramework/Util/Logger/MftfLogger.php @@ -15,15 +15,17 @@ class MftfLogger extends Logger /** * Prints a deprecation warning, as well as adds a log at the WARNING level. * - * @param string $message The log message. - * @param array $context The log context. + * @param string $message The log message. + * @param array $context The log context. + * @param boolean $verbose * @return void + * @throws \Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException */ - public function deprecation($message, array $context = []) + public function deprecation($message, array $context = [], $verbose = false) { $message = "DEPRECATION: " . $message; // Suppress print during unit testing - if (MftfApplicationConfig::getConfig()->getPhase() !== MftfApplicationConfig::UNIT_TEST_PHASE) { + if (MftfApplicationConfig::getConfig()->getPhase() !== MftfApplicationConfig::UNIT_TEST_PHASE && $verbose) { print ($message . json_encode($context) . "\n"); } parent::warning($message, $context); @@ -32,15 +34,17 @@ public function deprecation($message, array $context = []) /** * Prints a critical failure, as well as adds a log at the CRITICAL level. * - * @param string $message The log message. - * @param array $context The log context. + * @param string $message The log message. + * @param array $context The log context. + * @param boolean $verbose * @return void + * @throws \Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException */ - public function criticalFailure($message, array $context = []) + public function criticalFailure($message, array $context = [], $verbose = false) { $message = "FAILURE: " . $message; // Suppress print during unit testing - if (MftfApplicationConfig::getConfig()->getPhase() !== MftfApplicationConfig::UNIT_TEST_PHASE) { + if (MftfApplicationConfig::getConfig()->getPhase() !== MftfApplicationConfig::UNIT_TEST_PHASE && $verbose) { print ($message . implode("\n", $context) . "\n"); } parent::critical($message, $context); diff --git a/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php b/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php index 09c353029..e40f60dcf 100644 --- a/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php +++ b/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php @@ -7,7 +7,6 @@ namespace Magento\FunctionalTestingFramework\Util; use Magento\FunctionalTestingFramework\Config\MftfApplicationConfig; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\CredentialStore; use Magento\FunctionalTestingFramework\DataGenerator\Handlers\PersistedObjectHandler; use Magento\FunctionalTestingFramework\DataGenerator\Objects\EntityDataObject; use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; @@ -17,14 +16,11 @@ use Magento\FunctionalTestingFramework\Test\Handlers\TestObjectHandler; use Magento\FunctionalTestingFramework\Test\Objects\ActionGroupObject; use Magento\FunctionalTestingFramework\Test\Objects\ActionObject; -use Magento\FunctionalTestingFramework\DataGenerator\Handlers\DataObjectHandler; use Magento\FunctionalTestingFramework\Test\Objects\TestHookObject; use Magento\FunctionalTestingFramework\Test\Objects\TestObject; -use Magento\FunctionalTestingFramework\Util\Logger\LoggingUtil; +use Magento\FunctionalTestingFramework\Test\Util\BaseObjectExtractor; use Magento\FunctionalTestingFramework\Util\Manifest\BaseTestManifest; -use Magento\FunctionalTestingFramework\Util\Manifest\TestManifestFactory; use Magento\FunctionalTestingFramework\Test\Util\ActionObjectExtractor; -use Magento\FunctionalTestingFramework\Test\Util\TestObjectExtractor; use Magento\FunctionalTestingFramework\Util\Filesystem\DirSetupUtil; use Magento\FunctionalTestingFramework\Test\Util\ActionMergeUtil; use Magento\FunctionalTestingFramework\Util\Path\FilePathFormatter; @@ -40,9 +36,11 @@ class TestGenerator const REQUIRED_ENTITY_REFERENCE = 'createDataKey'; const GENERATED_DIR = '_generated'; const DEFAULT_DIR = 'default'; + const TEST_SCOPE = 'test'; const HOOK_SCOPE = 'hook'; const SUITE_SCOPE = 'suite'; + const PRESSKEY_ARRAY_ANCHOR_KEY = '987654321098765432109876543210'; const PERSISTED_OBJECT_NOTATION_REGEX = '/\${1,2}[\w.\[\]]+\${1,2}/'; const NO_STEPKEY_ACTIONS = [ @@ -50,10 +48,16 @@ class TestGenerator 'retrieveEntityField', 'getSecret', 'magentoCLI', + 'magentoCron', 'generateDate', 'field' ]; + const RULE_ERROR = 'On step with stepKey "%s", only one of the attributes: "%s" can be use for action "%s"'; + const STEP_KEY_ANNOTATION = " // stepKey: %s"; + const CRON_INTERVAL = 60; + const ARRAY_WRAP_OPEN = '['; + const ARRAY_WRAP_CLOSE = ']'; /** * Actor name for AcceptanceTest @@ -105,7 +109,14 @@ class TestGenerator private $currentGenerationScope = TestGenerator::TEST_SCOPE; /** - * TestGenerator constructor. + * Test deprecation messages. + * + * @var array + */ + private $deprecationMessages = []; + + /** + * Private constructor for Factory * * @param string $exportDir * @param array $tests @@ -114,13 +125,11 @@ class TestGenerator */ private function __construct($exportDir, $tests, $debug = false) { - // private constructor for factory $this->exportDirName = $exportDir ?? self::DEFAULT_DIR; - $exportDir = $exportDir ?? self::DEFAULT_DIR; $this->exportDirectory = FilePathFormatter::format(TESTS_MODULE_PATH) . self::GENERATED_DIR . DIRECTORY_SEPARATOR - . $exportDir; + . $this->exportDirName; $this->tests = $tests; $this->consoleOutput = new \Symfony\Component\Console\Output\ConsoleOutput(); $this->debug = $debug; @@ -185,13 +194,13 @@ private function loadAllTestObjects($testsToIgnore) * @return void * @throws \Exception */ - private function createCestFile($testPhp, $filename) + private function createCestFile(string $testPhp, string $filename) { $exportFilePath = $this->exportDirectory . DIRECTORY_SEPARATOR . $filename . ".php"; $file = fopen($exportFilePath, 'w'); if (!$file) { - throw new \Exception("Could not open the file."); + throw new \Exception(sprintf('Could not open test file: "%s"', $exportFilePath)); } fwrite($file, $testPhp); @@ -238,7 +247,6 @@ public function createAllTestFiles($testManifest = null, $testsToIgnore = null) public function assembleTestPhp($testObject) { $usePhp = $this->generateUseStatementsPhp(); - $classAnnotationsPhp = $this->generateAnnotationsPhp($testObject->getAnnotations()); $className = $testObject->getCodeceptionName(); try { @@ -251,6 +259,7 @@ public function assembleTestPhp($testObject) } catch (TestReferenceException $e) { throw new TestReferenceException($e->getMessage() . "\n" . $testObject->getFilename()); } + $classAnnotationsPhp = $this->generateAnnotationsPhp($testObject->getAnnotations()); $cestPhp = "<?php\n"; $cestPhp .= "namespace Magento\AcceptanceTest\\_" . $this->exportDirName . "\Backend;\n\n"; @@ -450,7 +459,7 @@ private function generateMethodAnnotations($annotationType = null, $annotationNa * Method which return formatted class level annotations based on type and name(s). * * @param string $annotationType - * @param string $annotationName + * @param array $annotationName * @return null|string * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ @@ -464,7 +473,8 @@ private function generateClassAnnotations($annotationType, $annotationName) break; case "description": - $annotationToAppend = sprintf(" * @Description(\"%s\")\n", $annotationName[0]); + $template = " * @Description(\"%s\")\n"; + $annotationToAppend = sprintf($template, $this->generateDescriptionAnnotation($annotationName)); break; case "testCaseId": @@ -485,6 +495,36 @@ private function generateClassAnnotations($annotationType, $annotationName) return $annotationToAppend; } + /** + * Generates Description + * + * @param array $descriptions + * @return string + */ + private function generateDescriptionAnnotation(array $descriptions) + { + $descriptionText = ""; + + $descriptionText .= $descriptions["main"] ?? ''; + if (!empty($descriptions[BaseObjectExtractor::OBJ_DEPRECATED]) || !empty($this->deprecationMessages)) { + $deprecatedMessages = array_merge( + $descriptions[BaseObjectExtractor::OBJ_DEPRECATED], + $this->deprecationMessages + ); + + $descriptionText .= "<h3 class='y-label y-label_status_broken'>Deprecated Notice(s):</h3>"; + $descriptionText .= "<ul>"; + + foreach ($deprecatedMessages as $deprecatedMessage) { + $descriptionText .= "<li>" . $deprecatedMessage . "</li>"; + } + $descriptionText .= "</ul>"; + } + $descriptionText .= $descriptions["test_files"]; + + return $descriptionText; + } + /** * Creates a PHP string for the actions contained withing a <test> block. * Since nearly half of all Codeception methods don't share the same signature I had to setup a massive Case @@ -505,8 +545,10 @@ public function generateStepsPhp($actionObjects, $generationScope = TestGenerato $testSteps = ''; $this->actor = $actor; $this->currentGenerationScope = $generationScope; + $this->deprecationMessages = []; foreach ($actionObjects as $actionObject) { + $this->deprecationMessages = array_merge($this->deprecationMessages, $actionObject->getDeprecatedUsages()); $stepKey = $actionObject->getStepKey(); $customActionAttributes = $actionObject->getCustomActionAttributes(); $attribute = null; @@ -534,6 +576,7 @@ public function generateStepsPhp($actionObjects, $generationScope = TestGenerato $dependentSelector = null; $visible = null; $command = null; + $cronGroups = ''; $arguments = null; $sortOrder = null; $storeCode = null; @@ -551,6 +594,9 @@ public function generateStepsPhp($actionObjects, $generationScope = TestGenerato if (isset($customActionAttributes['command'])) { $command = $this->addUniquenessFunctionCall($customActionAttributes['command']); } + if (isset($customActionAttributes['groups'])) { + $cronGroups = $this->addUniquenessFunctionCall($customActionAttributes['groups']); + } if (isset($customActionAttributes['arguments'])) { $arguments = $this->addUniquenessFunctionCall($customActionAttributes['arguments']); } @@ -628,9 +674,9 @@ public function generateStepsPhp($actionObjects, $generationScope = TestGenerato // validate the param array is in the correct format $this->validateParameterArray($customActionAttributes['parameterArray']); - $parameterArray = "["; - $parameterArray .= $this->addUniquenessToParamArray($customActionAttributes['parameterArray']); - $parameterArray .= "]"; + $parameterArray = $this->wrapParameterArray( + $this->addUniquenessToParamArray($customActionAttributes['parameterArray']) + ); } if (isset($customActionAttributes['requiredAction'])) { @@ -748,13 +794,8 @@ public function generateStepsPhp($actionObjects, $generationScope = TestGenerato if (!empty($requiredEntityKeys)) { $requiredEntityKeysArray = '"' . implode('", "', $requiredEntityKeys) . '"'; } - //Determine Scope - $scope = PersistedObjectHandler::TEST_SCOPE; - if ($generationScope == TestGenerator::HOOK_SCOPE) { - $scope = PersistedObjectHandler::HOOK_SCOPE; - } elseif ($generationScope == TestGenerator::SUITE_SCOPE) { - $scope = PersistedObjectHandler::SUITE_SCOPE; - } + + $scope = $this->getObjectScope($generationScope); $createEntityFunctionCall = "\t\t\${$actor}->createEntity("; $createEntityFunctionCall .= "\"{$stepKey}\","; @@ -782,13 +823,7 @@ public function generateStepsPhp($actionObjects, $generationScope = TestGenerato $actionGroup = $actionObject->getCustomActionAttributes()['actionGroup'] ?? null; $key .= $actionGroup; - //Determine Scope - $scope = PersistedObjectHandler::TEST_SCOPE; - if ($generationScope == TestGenerator::HOOK_SCOPE) { - $scope = PersistedObjectHandler::HOOK_SCOPE; - } elseif ($generationScope == TestGenerator::SUITE_SCOPE) { - $scope = PersistedObjectHandler::SUITE_SCOPE; - } + $scope = $this->getObjectScope($generationScope); $deleteEntityFunctionCall = "\t\t\${$actor}->deleteEntity("; $deleteEntityFunctionCall .= "\"{$key}\","; @@ -831,12 +866,7 @@ public function generateStepsPhp($actionObjects, $generationScope = TestGenerato $requiredEntityKeysArray = '"' . implode('", "', $requiredEntityKeys) . '"'; } - $scope = PersistedObjectHandler::TEST_SCOPE; - if ($generationScope == TestGenerator::HOOK_SCOPE) { - $scope = PersistedObjectHandler::HOOK_SCOPE; - } elseif ($generationScope == TestGenerator::SUITE_SCOPE) { - $scope = PersistedObjectHandler::SUITE_SCOPE; - } + $scope = $this->getObjectScope($generationScope); $updateEntityFunctionCall = "\t\t\${$actor}->updateEntity("; $updateEntityFunctionCall .= "\"{$key}\","; @@ -870,13 +900,7 @@ public function generateStepsPhp($actionObjects, $generationScope = TestGenerato $requiredEntityKeysArray = '"' . implode('", "', $requiredEntityKeys) . '"'; } - //Determine Scope - $scope = PersistedObjectHandler::TEST_SCOPE; - if ($generationScope == TestGenerator::HOOK_SCOPE) { - $scope = PersistedObjectHandler::HOOK_SCOPE; - } elseif ($generationScope == TestGenerator::SUITE_SCOPE) { - $scope = PersistedObjectHandler::SUITE_SCOPE; - } + $scope = $this->getObjectScope($generationScope); //Create Function $getEntityFunctionCall = "\t\t\${$actor}->getEntity("; @@ -1270,6 +1294,22 @@ public function generateStepsPhp($actionObjects, $generationScope = TestGenerato $stepKey ); break; + case 'magentoCron': + $testSteps .= $this->wrapFunctionCallWithReturnValue( + $stepKey, + $actor, + $actionObject, + $cronGroups, + self::CRON_INTERVAL + $time, + $arguments + ); + $testSteps .= sprintf(self::STEP_KEY_ANNOTATION, $stepKey) . PHP_EOL; + $testSteps .= sprintf( + "\t\t$%s->comment(\$%s);", + $actor, + $stepKey + ); + break; case "field": $fieldKey = $actionObject->getCustomActionAttributes()['key']; $input = $this->resolveStepKeyReferences($input, $actionObject->getActionOrigin()); @@ -1303,7 +1343,7 @@ public function generateStepsPhp($actionObjects, $generationScope = TestGenerato break; case "comment": $input = $input === null ? strtr($value, ['$' => '\$', '{' => '\{', '}' => '\}']) : $input; - // Combining userInput from native XML comment and <comment/> action to fall-through 'default' case + // Combining userInput from native XML comment and <comment/> action to fall-through 'default' case default: $testSteps .= $this->wrapFunctionCall( $actor, @@ -1380,9 +1420,9 @@ private function trimVariableIfNeeded($input) preg_match('/"{\$[a-z][a-zA-Z\d]+}"/', $input, $match); if (isset($match[0])) { return trim($input, '{}"'); - } else { - return $input; } + + return $input; } /** @@ -1403,7 +1443,7 @@ private function replaceMatchesIntoArg($matches, &$outputArg) $variable = $this->stripAndSplitReference($match, $delimiter); if (count($variable) != 2) { throw new \Exception( - "Invalid Persisted Entity Reference: {$match}. + "Invalid Persisted Entity Reference: {$match}. Test persisted entity references must follow {$delimiter}entityStepKey.field{$delimiter} format." ); } @@ -1480,7 +1520,7 @@ private function resolveStepKeyReferences($input, $actionGroupOrigin, $matchAll // only replace when whole word matches exactly // e.g. testVar => $testVar but not $testVar2 if (strpos($output, $stepKeyVarRef) !== false) { - $output = preg_replace('/\B\\' .$stepKeyVarRef. '\b/', $stepKeyVarRef . $testInvocationKey, $output); + $output = preg_replace('/\B\\' . $stepKeyVarRef . '\b/', $stepKeyVarRef . $testInvocationKey, $output); } if (strpos($output, $persistedVarRef) !== false) { @@ -1515,8 +1555,8 @@ private function wrapFunctionArgsWithQuotes($functionRegex, $input) foreach ($allArguments as $argument) { $argument = trim($argument); - if ($argument[0] == "[") { - $replacement = "[" . $this->addUniquenessToParamArray($argument) . "]"; + if ($argument[0] == self::ARRAY_WRAP_OPEN) { + $replacement = $this->wrapParameterArray($this->addUniquenessToParamArray($argument)); } elseif (is_numeric($argument)) { $replacement = $argument; } else { @@ -1685,8 +1725,9 @@ private function processPressKey($input) preg_match_all('/[\[][^\]]*?[\]]/', $input, $paramInput); if (!empty($paramInput)) { foreach ($paramInput[0] as $param) { - $arrayResult[self::PRESSKEY_ARRAY_ANCHOR_KEY . $count] = - '[' . trim($this->addUniquenessToParamArray($param)) . ']'; + $arrayResult[self::PRESSKEY_ARRAY_ANCHOR_KEY . $count] = $this->wrapParameterArray( + trim($this->addUniquenessToParamArray($param)) + ); $input = str_replace($param, self::PRESSKEY_ARRAY_ANCHOR_KEY . $count, $input); $count++; } @@ -1780,13 +1821,8 @@ private function stripWrappedQuotes($input) if (empty($input)) { return ''; } - if (substr($input, 0, 1) === '"') { - $input = substr($input, 1); - } - if (substr($input, -1, 1) === '"') { - $input = substr($input, 0, -1); - } - return $input; + + return trim($input, '"'); } /** @@ -1800,15 +1836,12 @@ private function addDollarSign($input) return sprintf("$%s", ltrim($this->stripQuotes($input), '$')); } - // @codingStandardsIgnoreStart - /** * Wrap parameters into a function call. * - * @param string $actor + * @param string $actor * @param actionObject $action - * @param string $scope - * @param array ...$args + * @param array ...$args * @return string * @throws \Exception */ @@ -1821,7 +1854,7 @@ private function wrapFunctionCall($actor, $action, ...$args) continue; } if ($args[$i] === "") { - $args[$i] = '"' . $args[$i] . '"'; + $args[$i] = '""'; } } if (!is_array($args)) { @@ -1829,7 +1862,7 @@ private function wrapFunctionCall($actor, $action, ...$args) } $args = $this->resolveAllRuntimeReferences($args); $args = $this->resolveTestVariable($args, $action->getActionOrigin()); - $output .= implode(", ", array_filter($args, function($value) { return $value !== null; })) . ");"; + $output .= implode(", ", array_filter($args, $this->filterNullCallback())) . ");"; return $output; } @@ -1839,8 +1872,7 @@ private function wrapFunctionCall($actor, $action, ...$args) * @param string $returnVariable * @param string $actor * @param string $action - * @param string $scope - * @param array ...$args + * @param array ...$args * @return string * @throws \Exception */ @@ -1853,7 +1885,7 @@ private function wrapFunctionCallWithReturnValue($returnVariable, $actor, $actio continue; } if ($args[$i] === "") { - $args[$i] = '"' . $args[$i] . '"'; + $args[$i] = '""'; } } if (!is_array($args)) { @@ -1861,14 +1893,25 @@ private function wrapFunctionCallWithReturnValue($returnVariable, $actor, $actio } $args = $this->resolveAllRuntimeReferences($args); $args = $this->resolveTestVariable($args, $action->getActionOrigin()); - $output .= implode(", ", array_filter($args, function($value) { return $value !== null; })) . ");"; + $output .= implode(", ", array_filter($args, $this->filterNullCallback())) . ");"; return $output; } - // @codingStandardsIgnoreEnd + /** + * Closure returned is used as a callable for array_filter to remove null values from array + * + * @return callable + */ + private function filterNullCallback() + { + return function ($value) { + return $value !== null; + }; + } /** * Resolves {{_ENV.variable}} into getenv("variable") for test-runtime ENV referencing. + * * @param array $args * @param string $regex * @param string $func @@ -1928,43 +1971,79 @@ private function resolveAllRuntimeReferences($args) */ private function validateParameterArray($paramArray) { - if (substr($paramArray, 0, 1) != "[" || substr($paramArray, strlen($paramArray) - 1, 1) != "]") { - throw new TestReferenceException("parameterArray must begin with `[` and end with `]"); + if (!$this->isWrappedArray($paramArray)) { + throw new TestReferenceException(sprintf( + "parameterArray must begin with `%s` and end with `%s`", + self::ARRAY_WRAP_OPEN, + self::ARRAY_WRAP_CLOSE + )); } } + /** + * Verifies whether we have correctly wrapped array syntax + * + * @param string $paramArray + * @return boolean + */ + private function isWrappedArray(string $paramArray) + { + return 0 === strpos($paramArray, self::ARRAY_WRAP_OPEN) + && substr($paramArray, -1) === self::ARRAY_WRAP_CLOSE; + } + /** * Resolve value based on type. * - * @param string $value - * @param string $type - * @return string + * @param string|null $value + * @param string|null $type + * @return string|null * @throws TestReferenceException - * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ - private function resolveValueByType($value, $type) + private function resolveValueByType($value = null, $type = null) { - //TODO: Refactor to deal with PHPMD.CyclomaticComplexity, and remove @SuppressWarnings if (null === $value) { return null; } + if (null === $type) { $type = 'const'; } - if ($type == "string") { - return $this->addUniquenessFunctionCall($value); - } elseif ($type == "bool") { - return $this->toBoolean($value) ? "true" : "false"; - } elseif ($type == "int" || $type == "float") { - return $this->toNumber($value); - } elseif ($type == "array") { - $this->validateParameterArray($value); - return "[" . $this->addUniquenessToParamArray($value) . "]"; - } elseif ($type == "variable") { - return $this->addDollarSign($value); - } else { - return $value; + + switch ($type) { + case 'string': + return $this->addUniquenessFunctionCall($value); + case 'bool': + return $this->toBoolean($value) ? "true" : "false"; + case 'int': + case 'float': + return $this->toNumber($value); + case 'array': + $this->validateParameterArray($value); + return $this->wrapParameterArray($this->addUniquenessToParamArray($value)); + case 'variable': + return $this->addDollarSign($value); } + + return $value; + } + + /** + * Determines correct scope based on parameter + * + * @param string $generationScope + * @return string + */ + private function getObjectScope(string $generationScope): string + { + switch ($generationScope) { + case TestGenerator::SUITE_SCOPE: + return PersistedObjectHandler::SUITE_SCOPE; + case TestGenerator::HOOK_SCOPE: + return PersistedObjectHandler::HOOK_SCOPE; + } + + return PersistedObjectHandler::TEST_SCOPE; } /** @@ -1987,11 +2066,11 @@ private function toBoolean($inStr) private function toNumber($inStr) { $outStr = $this->stripQuotes($inStr); - if (strpos($outStr, localeconv()['decimal_point']) === false) { - return intval($outStr); - } else { + if ($this->hasDecimalPoint($outStr)) { return floatval($outStr); } + + return intval($outStr); } /** @@ -2078,9 +2157,29 @@ private function printRuleErrorToConsole($key, $tagName, $attributes) if (empty($tagName) || empty($attributes)) { return; } - $message = 'On step with stepKey "' . $key . '", only one of the attributes: "'; - $message .= implode('", "', $attributes); - $message .= '" can be use for action "' . $tagName . "\".\n"; - print $message; + + printf(self::RULE_ERROR, $key, implode('", "', $attributes), $tagName); + } + + /** + * Wraps parameters array with opening and closing symbol. + * + * @param string $value + * @return string + */ + private function wrapParameterArray(string $value): string + { + return sprintf('%s%s%s', self::ARRAY_WRAP_OPEN, $value, self::ARRAY_WRAP_CLOSE); + } + + /** + * Determines whether string provided contains decimal point characteristic for current locale + * + * @param string $outStr + * @return boolean + */ + private function hasDecimalPoint(string $outStr) + { + return strpos($outStr, localeconv()['decimal_point']) === false; } }