From 098bc0dcf98f9b380c3293cc590958177e5678b7 Mon Sep 17 00:00:00 2001 From: Simon Sprankel Date: Tue, 25 Jun 2019 09:29:29 +0200 Subject: [PATCH 01/15] added file name suffix principle for data --- docs/data.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/data.md b/docs/data.md index 42736a6de..875df0198 100644 --- a/docs/data.md +++ b/docs/data.md @@ -121,6 +121,7 @@ The following conventions apply to MFTF ``: * A `` file may contain multiple data entities. * Camel case is used for `` elements. The name represents the `` type. For example, a file with customer data is `CustomerData.xml`. A file for simple product would be `SimpleProductData.xml`. * Camel case is used for the entity name. +* The file name must have the suffix `Data.xml`. ## Example @@ -263,4 +264,4 @@ Attributes|Type|Use|Description [Actions]: ./test/actions.md [category creation]: http://docs.magento.com/m2/ce/user_guide/catalog/category-create.html [Credentials]: ./credentials.md -[test actions]: ./test/actions.md#actions-returning-a-variable \ No newline at end of file +[test actions]: ./test/actions.md#actions-returning-a-variable From e791d011fe16c8b316fe06c57e4036f24cdc95bb Mon Sep 17 00:00:00 2001 From: Donald Booth Date: Mon, 29 Jul 2019 10:45:42 -0500 Subject: [PATCH 02/15] Adding testfile to repo. --- docs/mftf-tests.md | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 docs/mftf-tests.md diff --git a/docs/mftf-tests.md b/docs/mftf-tests.md new file mode 100644 index 000000000..6eee50b0d --- /dev/null +++ b/docs/mftf-tests.md @@ -0,0 +1,27 @@ +--- +layout: full-width +title: MFTF Tests +--- + +The Magento Functional Testing Framework runs tests on every Module within Magento. These files are stored within each Module folder in the Magento repo. +This page lists all those tests so that developers have a good sense of what is covered. + +{% assign mftf = site.data.mftf | group_by: "module" %} +{% assign sorted = mftf | sort: "name" %} + +{% for module in sorted %} + +### {{ module.name }} Module + +{% assign tests = module.items | sort: "filename" %} +{% for item in tests %} + + **{{ item.testname }}** + +- File name: [{{ item.filename }}.xml]({{ item.link }}) +- Story: {{ item.stories }} + +{{ item.description}} + +{% endfor %} +{% endfor %} \ No newline at end of file From 7d925afc490ee1f10710309d6748896ebd77c475 Mon Sep 17 00:00:00 2001 From: Donald Booth Date: Mon, 29 Jul 2019 16:13:54 -0500 Subject: [PATCH 03/15] new layout. handle multiple tests. --- docs/mftf-tests.md | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/docs/mftf-tests.md b/docs/mftf-tests.md index 6eee50b0d..fd6283c30 100644 --- a/docs/mftf-tests.md +++ b/docs/mftf-tests.md @@ -4,24 +4,20 @@ title: MFTF Tests --- The Magento Functional Testing Framework runs tests on every Module within Magento. These files are stored within each Module folder in the Magento repo. -This page lists all those tests so that developers have a good sense of what is covered. +This page lists all those tests so that developers can have a good sense of what is covered. -{% assign mftf = site.data.mftf | group_by: "module" %} -{% assign sorted = mftf | sort: "name" %} +{% assign mftf = site.data.mftf | group_by: "module" | sort: "name" %} -{% for module in sorted %} +{% for item in mftf %} -### {{ module.name }} Module +### {{ item.name }} +{% for file in item.items %} +#### [{{ file.filename }}]({{file.repo}}) -{% assign tests = module.items | sort: "filename" %} -{% for item in tests %} - - **{{ item.testname }}** - -- File name: [{{ item.filename }}.xml]({{ item.link }}) -- Story: {{ item.stories }} - -{{ item.description}} +{% for test in file.tests %} +{{test.testname}} + : {{test.description}} {% endfor %} -{% endfor %} \ No newline at end of file +{% endfor %} +{% endfor %} From 235affdc02931b869ecbc1abecedfec3adb7db91 Mon Sep 17 00:00:00 2001 From: Raul E Watson Date: Tue, 30 Jul 2019 23:16:02 +0100 Subject: [PATCH 04/15] SMALL_CHANGE: Minor change suggested --- docs/getting-started.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/getting-started.md b/docs/getting-started.md index 2d3a6b8d9..6b884b6e2 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -100,7 +100,7 @@ location ~* ^/dev/tests/acceptance/utils($|/) { ## Set up an embedded MFTF {#setup-framework} -This is a default setup that you would need to start using the MFTF to cover your Magento project with functional testing. +This is a default setup of the MFTF that you would need to start using to cover your Magento project with functional testing. It installs the framework using an existing Composer dependency such as `magento/magento2-functional-testing-framework`. If you want to set up the MFTF as a standalone tool, refer to [Set up a standalone MFTF][]. From 2ca3d952a91296e7942bc7f3b0e205c68f2814eb Mon Sep 17 00:00:00 2001 From: Raul E Watson Date: Tue, 30 Jul 2019 23:23:17 +0100 Subject: [PATCH 05/15] SMALL_CHANGE: Minor change suggested --- docs/getting-started.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/getting-started.md b/docs/getting-started.md index 6b884b6e2..e3f68ad2e 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -100,7 +100,7 @@ location ~* ^/dev/tests/acceptance/utils($|/) { ## Set up an embedded MFTF {#setup-framework} -This is a default setup of the MFTF that you would need to start using to cover your Magento project with functional testing. +This is the default setup of the MFTF that you would need to start using to cover your Magento project with functional tests. It installs the framework using an existing Composer dependency such as `magento/magento2-functional-testing-framework`. If you want to set up the MFTF as a standalone tool, refer to [Set up a standalone MFTF][]. From 658dc225d9ff7b6b222bc5ac221abc207e2e93bb Mon Sep 17 00:00:00 2001 From: Donald Booth Date: Wed, 31 Jul 2019 11:44:21 -0500 Subject: [PATCH 06/15] Small grammar fixup. --- docs/getting-started.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/getting-started.md b/docs/getting-started.md index e3f68ad2e..84e19af2e 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -100,7 +100,7 @@ location ~* ^/dev/tests/acceptance/utils($|/) { ## Set up an embedded MFTF {#setup-framework} -This is the default setup of the MFTF that you would need to start using to cover your Magento project with functional tests. +This is the default setup of the MFTF that you would need to cover your Magento project with functional tests. It installs the framework using an existing Composer dependency such as `magento/magento2-functional-testing-framework`. If you want to set up the MFTF as a standalone tool, refer to [Set up a standalone MFTF][]. From af5fe4f3daf7f31f4cbe502cb04b7e95573e3295 Mon Sep 17 00:00:00 2001 From: Donald Booth Date: Wed, 31 Jul 2019 16:07:26 -0500 Subject: [PATCH 07/15] adding test reference file. --- docs/mftf-tests.md | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/docs/mftf-tests.md b/docs/mftf-tests.md index fd6283c30..718806aa5 100644 --- a/docs/mftf-tests.md +++ b/docs/mftf-tests.md @@ -2,7 +2,17 @@ layout: full-width title: MFTF Tests --- - + The Magento Functional Testing Framework runs tests on every Module within Magento. These files are stored within each Module folder in the Magento repo. This page lists all those tests so that developers can have a good sense of what is covered. @@ -10,14 +20,15 @@ This page lists all those tests so that developers can have a good sense of what {% for item in mftf %} -### {{ item.name }} +### {{ item.name }} {% for file in item.items %} #### [{{ file.filename }}]({{file.repo}}) +{: .mftf-test-link} {% for test in file.tests %} {{test.testname}} : {{test.description}} - +{: .mftf-dl} {% endfor %} {% endfor %} {% endfor %} From a3b50117ecec9d54d527c731cda0e322c64610d7 Mon Sep 17 00:00:00 2001 From: Donald Booth Date: Fri, 2 Aug 2019 08:56:43 -0500 Subject: [PATCH 08/15] Temp removal of files to fix build. --- docs/mftf-tests.md | 34 ---------------------------------- docs/selectors.md | 35 ----------------------------------- 2 files changed, 69 deletions(-) delete mode 100644 docs/mftf-tests.md delete mode 100644 docs/selectors.md diff --git a/docs/mftf-tests.md b/docs/mftf-tests.md deleted file mode 100644 index 718806aa5..000000000 --- a/docs/mftf-tests.md +++ /dev/null @@ -1,34 +0,0 @@ ---- -layout: full-width -title: MFTF Tests ---- - -The Magento Functional Testing Framework runs tests on every Module within Magento. These files are stored within each Module folder in the Magento repo. -This page lists all those tests so that developers can have a good sense of what is covered. - -{% assign mftf = site.data.mftf | group_by: "module" | sort: "name" %} - -{% for item in mftf %} - -### {{ item.name }} -{% for file in item.items %} -#### [{{ file.filename }}]({{file.repo}}) -{: .mftf-test-link} - -{% for test in file.tests %} -{{test.testname}} - : {{test.description}} -{: .mftf-dl} -{% endfor %} -{% endfor %} -{% endfor %} diff --git a/docs/selectors.md b/docs/selectors.md deleted file mode 100644 index 870072e15..000000000 --- a/docs/selectors.md +++ /dev/null @@ -1,35 +0,0 @@ -## Selectors - -These guidelines should help you to write high quality selectors. - -### Selectors SHOULD be written in CSS instead of XPath whenever possible - -CSS is generally easier to read than XPath. For example, `//*[@id="foo"]` in XPath can be expressed as simply as `#foo` in CSS. -See this [XPath Cheatsheet](https://devhints.io/xpath) for more examples. - -### XPath selectors SHOULD NOT use `@attribute="foo"`. - -This would fail if the attribute was `attribute="foo bar"`. -Instead you SHOULD use `contains(@attribute, "foo")` where `@attribute` is any valid attribute such as `@text` or `@class`. - -### CSS and XPath selectors SHOULD be implemented in their most simple form - -* GOOD: `#foo` -* BAD: `button[contains(@id, "foo")]` - -### CSS and XPath selectors SHOULD avoid making use of hardcoded indices - -Instead you SHOULD parameterize the selector. - -* GOOD: `.foo:nth-of-type({{index}})` -* BAD: `.foo:nth-of-type(1)` - -* GOOD: `button[contains(@id, "foo")][{{index}}]` -* BAD: `button[contains(@id, "foo")][1]` - -* GOOD: `#actions__{{index}}__aggregator` -* BAD: `#actions__1__aggregator` - -### CSS and XPath selectors MUST NOT reference the `@data-bind` attribute - -The `@data-bind` attribute is used by KnockoutJS, a framework Magento uses to create dynamic Javascript pages. Since this `@data-bind` attribute is tied to a specific framework, it should not be used for selectors. If Magento decides to use a different framework then these `@data-bind` selectors would break. From c5083b1b8a0aca4e48ba9f3fea29d8a7bf44e802 Mon Sep 17 00:00:00 2001 From: Soumya Unnikrishnan Date: Fri, 9 Aug 2019 09:16:40 -0500 Subject: [PATCH 09/15] MQE-1691: CREDS resolution does not occur from Suite --- dev/tests/verification/Resources/functionalSuiteHooks.txt | 1 + dev/tests/verification/Resources/functionalSuiteWithComments.txt | 1 + .../FunctionalTestingFramework/Suite/views/SuiteClass.mustache | 1 + 3 files changed, 3 insertions(+) diff --git a/dev/tests/verification/Resources/functionalSuiteHooks.txt b/dev/tests/verification/Resources/functionalSuiteHooks.txt index 4df805329..05ada44f1 100644 --- a/dev/tests/verification/Resources/functionalSuiteHooks.txt +++ b/dev/tests/verification/Resources/functionalSuiteHooks.txt @@ -3,6 +3,7 @@ namespace Group; use Magento\FunctionalTestingFramework\DataGenerator\Handlers\PersistedObjectHandler; +use Magento\FunctionalTestingFramework\DataGenerator\Handlers\CredentialStore; /** * Group class is Codeception Extension which is allowed to handle to all internal events. diff --git a/dev/tests/verification/Resources/functionalSuiteWithComments.txt b/dev/tests/verification/Resources/functionalSuiteWithComments.txt index d0e059aab..0e15b7909 100644 --- a/dev/tests/verification/Resources/functionalSuiteWithComments.txt +++ b/dev/tests/verification/Resources/functionalSuiteWithComments.txt @@ -3,6 +3,7 @@ namespace Group; use Magento\FunctionalTestingFramework\DataGenerator\Handlers\PersistedObjectHandler; +use Magento\FunctionalTestingFramework\DataGenerator\Handlers\CredentialStore; /** * Group class is Codeception Extension which is allowed to handle to all internal events. diff --git a/src/Magento/FunctionalTestingFramework/Suite/views/SuiteClass.mustache b/src/Magento/FunctionalTestingFramework/Suite/views/SuiteClass.mustache index 58c0cbf0f..b98405c03 100644 --- a/src/Magento/FunctionalTestingFramework/Suite/views/SuiteClass.mustache +++ b/src/Magento/FunctionalTestingFramework/Suite/views/SuiteClass.mustache @@ -3,6 +3,7 @@ namespace Group; use Magento\FunctionalTestingFramework\DataGenerator\Handlers\PersistedObjectHandler; +use Magento\FunctionalTestingFramework\DataGenerator\Handlers\CredentialStore; /** * Group class is Codeception Extension which is allowed to handle to all internal events. From af1d4588f048565628a6aeb261a5bfaa738d871f Mon Sep 17 00:00:00 2001 From: Donald Booth Date: Fri, 9 Aug 2019 09:22:43 -0500 Subject: [PATCH 10/15] Rolling up doc updates from other PRs --- docs/mftf_tests.md | 31 ++++ docs/test-prep.md | 412 +++++++++++++++++++++++++++++++++++++++++++++ docs/versioning.md | 8 +- 3 files changed, 447 insertions(+), 4 deletions(-) create mode 100644 docs/mftf_tests.md create mode 100644 docs/test-prep.md diff --git a/docs/mftf_tests.md b/docs/mftf_tests.md new file mode 100644 index 000000000..3c4043d5b --- /dev/null +++ b/docs/mftf_tests.md @@ -0,0 +1,31 @@ + + +# MFTF functional test reference + +The Magento Functional Testing Framework runs tests on every Module within Magento. These files are stored within each Module folder in the Magento repo. +This page lists all those tests so that developers can have a good sense of what is covered. + +{% include mftf/mftf_data.md %} + +{% for item in mftf %} + +### {{ item.name }} +{% for file in item.items %} +#### [{{ file.filename }}]({{file.repo}}) +{: .mftf-test-link} + +{% for test in file.tests %} +{{test.testname}} + : {{test.description}} +{: .mftf-dl} +{% endfor %} +{% endfor %} +{% endfor %} \ No newline at end of file diff --git a/docs/test-prep.md b/docs/test-prep.md new file mode 100644 index 000000000..35d54d3e9 --- /dev/null +++ b/docs/test-prep.md @@ -0,0 +1,412 @@ +# Preparing a test for MFTF + +This tutorial demonstrates the process of converting a raw functional test into a properly abstracted test file, ready for publishing. + +## The abstraction process + +When first writing a test for a new piece of code such as a custom extension, it is likely that values are hardcoded for the specific testing environment while in development. To make the test more generic and easier for others to update and use, we need to abstract the test. +The general process: + +1. Convert the manual test to a working, hard-coded test. +1. Replace hardcoded selectors to a more flexible format such as [parameterized selectors][]. +1. Convert hardcoded form values and other data to data entities. +1. Convert [actions][] into [action groups][]. + +## The manual test + +Manual tests are just that: A series of manual steps to be run. + +```xml + + + + + + + + + + + + + + + +``` + +## The raw test + +This test works just fine. But it will only work if everything referenced in the test stays exactly the same. This neither reusable nor extensible. +Hardcoded selectors make it impossible to reuse sections in other action groups and tasks. They can also be brittle. If Magento happens to change a `class` or `id` on an element, the test will fail. + +Some data, like the SKU in this example, must be unique for every test run. Hardcoded values will fail here. [Data entities][] allow for `suffix` and `prefix` for ensuring unique data values. + +For our example, we have a test that creates a simple product. Note the hardcoded selectors, data values and lack of action groups. We will focus on the "product name". + +```xml + + + + + + + + + + <description value="Admin should be able to create simple product."/> + <severity value="MAJOR"/> + <group value="Catalog"/> + <group value="alex" /> + </annotations> + <before> + <!-- Login to Admin panel --> + <amOnPage url="admin" stepKey="openAdminPanelPage" /> + <fillField selector="#username" userInput="admin" stepKey="fillLoginField" /> + <fillField selector="#login" userInput="123123q" stepKey="fillPasswordField" /> + <click selector="#login-form .action-login" stepKey="clickLoginButton" /> + </before> + <after> + <!-- Logout from Admin panel --> + </after> + + <!-- Navigate to Catalog -> Products page (or just open by link) --> + <amOnPage url="admin/catalog/product/index" stepKey="openProductGridPage" /> + + <!-- Click "Add Product" button --> + <click selector="#add_new_product-button" stepKey="clickAddProductButton" /> + <waitForPageLoad stepKey="waitForNewProductPageOpened" /> + + <!-- Fill field "Name" with "Simple Product %unique_value%" --> + ---> <fillField selector="input[name='product[name]']" userInput="Simple Product 12412431" stepKey="fillNameField" /> + + <!-- Fill field "SKU" with "simple_product_%unique_value%" --> + <fillField selector="input[name='product[sku]']" userInput="simple-product-12412431" stepKey="fillSKUField" /> + + <!-- Fill field "Price" with "500.00" --> + <fillField selector="input[name='product[price]']" userInput="500.00" stepKey="fillPriceField" /> + + <!-- Fill field "Quantity" with "100" --> + <fillField selector="input[name='product[quantity_and_stock_status][qty]']" userInput="100" stepKey="fillQtyField" /> + + <!-- Fill field "Weight" with "100" --> + <fillField selector="input[name='product[weight]']" userInput="100" stepKey="fillWeightField" /> + + ... + </test> +</tests> +``` + +## Extract the CSS selectors + +First we will extract the hardcoded CSS selector values into variables. +For instance: `input[name='product[name]']` becomes `{{AdminProductFormSection.productName}}`. +In this example `AdminProductFormSection` refers to the `<section>` in the XML file which contains an `<element>` node named `productName`. This element contains the value of the selector that was previously hardcoded: `input[name='product[name]']` + +```xml +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="CreateSimpleProductTest"> + <annotations> + <features value="Catalog"/> + <stories value="Create Product"/> + <title value="Admin should be able to create simple product."/> + <description value="Admin should be able to create simple product."/> + <severity value="MAJOR"/> + <group value="Catalog"/> + <group value="alex" /> + </annotations> + <before> + <before> + <!-- Login to Admin panel --> + <amOnPage url="admin" stepKey="openAdminPanelPage" /> + <fillField selector="#username" userInput="admin" stepKey="fillLoginField" /> + <fillField selector="#login" userInput="123123q" stepKey="fillPasswordField" /> + <click selector="#login-form .action-login" stepKey="clickLoginButton" /> + </before> + <after> + <!-- Logout from Admin panel --> + </after> + + <!-- Navigate to Catalog -> Products page (or just open by link) --> + <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="openProductGridPage" /> + + <!-- Click "Add Product" button --> + <click selector="{{AdminProductGridActionSection.addProductBtn}}" stepKey="clickAddProductButton" /> + <waitForPageLoad stepKey="waitForNewProductPageOpened" /> + + <!-- Fill field "Name" with "Simple Product %unique_value%" --> + ```diff + -<fillField selector="input[name='product[name]']" userInput="Simple Product 12412431" stepKey="fillNameField" /> + +<fillField selector="{{AdminProductFormSection.productName}}" userInput="Simple Product 12412431" stepKey="fillNameField" /> + ``` + <!-- Fill field "SKU" with "simple_product_%unique_value%" --> + <fillField selector="{{AdminProductFormSection.productSku}}" userInput="simple-product-12412431" stepKey="fillSKUField" /> + + <!-- Fill field "Price" with "500.00" --> + <fillField selector="{{AdminProductFormSection.productPrice}}" userInput="500.00" stepKey="fillPriceField" /> + + <!-- Fill field "Quantity" with "100" --> + <fillField selector="{{AdminProductFormSection.productQuantity}}" userInput="100" stepKey="fillQtyField" /> + + <!-- Fill field "Weight" with "100" --> + <fillField selector="{{AdminProductFormSection.productWeight}}" userInput="100" stepKey="fillWeightField" /> + ... + </test> +</tests> +``` + +## The section file + +We abstract these selector values to a file named `AdminProductFormSection.xml` which is kept in the `Section` folder. +Within this file, there can be multiple `<section>` nodes which contains data for different parts of the test. +Here we are interested in `<section name="AdminProductFormSection">`, where we are keeping our extracted values from above. + +```xml +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> +--> <section name="AdminProductFormSection"> + <element name="attributeSet" type="select" selector="div[data-index='attribute_set_id'] .admin__field-control"/> + <element name="attributeSetFilter" type="input" selector="div[data-index='attribute_set_id'] .admin__field-control input" timeout="30"/> + <element name="attributeSetFilterResult" type="input" selector="div[data-index='attribute_set_id'] .action-menu-item._last" timeout="30"/> + <element name="attributeSetFilterResultByName" type="text" selector="//label/span[text() = '{{var}}']" timeout="30" parameterized="true"/> + ---> <element name="productName" type="input" selector="input[name='product[name]']"/> + <element name="RequiredNameIndicator" type="text" selector=" return window.getComputedStyle(document.querySelector('._required[data-index=name]>.admin__field-label span'), ':after').getPropertyValue('content');"/> + <element name="RequiredSkuIndicator" type="text" selector=" return window.getComputedStyle(document.querySelector('._required[data-index=sku]>.admin__field-label span'), ':after').getPropertyValue('content');"/> + <element name="productSku" type="input" selector="input[name='product[sku]']"/> + <element name="enableProductAttributeLabel" type="text" selector="//span[text()='Enable Product']/parent::label"/> + <element name="enableProductAttributeLabelWrapper" type="text" selector="//span[text()='Enable Product']/parent::label/parent::div"/> + <element name="productStatus" type="checkbox" selector="input[name='product[status]']"/> + ... + </section> + <section name="ProductInWebsitesSection"> + <element name="sectionHeader" type="button" selector="div[data-index='websites']" timeout="30"/> + <element name="website" type="checkbox" selector="//label[contains(text(), '{{var1}}')]/parent::div//input[@type='checkbox']" parameterized="true"/> + </section> +``` + +## Data entities + +The hardcoded values of these form elements are abstracted to a "data entity" XML file. +We replace the hardcoded values with variables and the MFTF will do the variable substitution. + +```xml +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="CreateSimpleProductTest"> + <annotations> + <features value="Catalog"/> + <stories value="Create Product"/> + <title value="Admin should be able to create simple product."/> + <description value="Admin should be able to create simple product."/> + <severity value="MAJOR"/> + <group value="Catalog"/> + <group value="alex" /> + </annotations> + <before> + <!-- Login to Admin panel --> + <amOnPage url="admin" stepKey="openAdminPanelPage" /> + <fillField selector="#username" userInput="admin" stepKey="fillLoginField" /> + <fillField selector="#login" userInput="123123q" stepKey="fillPasswordField" /> + <click selector="#login-form .action-login" stepKey="clickLoginButton" /> + </before> + <after> + <!-- Logout from Admin panel --> + </after> + + <!-- Navigate to Catalog -> Products page (or just open by link) --> + <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="openProductGridPage" /> + + <!-- Click "Add Product" button --> + <click selector="{{AdminProductGridActionSection.addProductBtn}}" stepKey="clickAddProductButton" /> + <waitForPageLoad stepKey="waitForNewProductPageOpened" /> + + <!-- Fill field "Name" with "Simple Product %unique_value%" --> + -<fillField selector="{{AdminProductFormSection.productName}}" userInput="Simple Product 12412431" stepKey="fillNameField" /> + +<fillField selector="{{AdminProductFormSection.productName}}" userInput="{{_defaultProduct.name}}" stepKey="fillNameField" /> + + <!-- Fill field "SKU" with "simple_product_%unique_value%" --> + <fillField selector="{{AdminProductFormSection.productSku}}" userInput="{{_defaultProduct.sku}}" stepKey="fillSKUField" /> + + <!-- Fill field "Price" with "500.00" --> + <fillField selector="{{AdminProductFormSection.productPrice}}" userInput="{{_defaultProduct.price}}" stepKey="fillPriceField" /> + + <!-- Fill field "Quantity" with "100" --> + <fillField selector="{{AdminProductFormSection.productQuantity}}" userInput="{{_defaultProduct.quantity}}" stepKey="fillQtyField" /> + + <!-- Fill field "Weight" with "100" --> + <fillField selector="{{AdminProductFormSection.productWeight}}" userInput="{{_defaultProduct.weight}}" stepKey="fillWeightField" /> + + ... + </test> +</tests> +``` + +One of the reasons that we abstract things is so that they are more flexible and reusable. In this case, we can leverage this flexibility and use an existing data file. For this scenario, we are using [this file](https://raw.githubusercontent.com/magento-pangolin/magento2/MageTestFest/app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml). + +Data entities are important because this is where a `suffix` or `prefix` can be defined. This ensures that data values can be unique for every test run. + +Notice that the `<entity>` name is `_defaultProduct` as referenced above. Within this entity is the `name` value. + +```xml +<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="_defaultProduct" type="product"> + <data key="sku" unique="suffix">testSku</data> + <data key="type_id">simple</data> + <data key="attribute_set_id">4</data> + <data key="visibility">4</data> + ---> <data key="name" unique="suffix">testProductName</data> + <data key="price">123.00</data> + <data key="urlKey" unique="suffix">testurlkey</data> + <data key="status">1</data> + <data key="quantity">100</data> + <data key="weight">1</data> + <requiredEntity type="product_extension_attribute">EavStockItem</requiredEntity> + <requiredEntity type="custom_attribute_array">CustomAttributeCategoryIds</requiredEntity> + </entity> +``` + +The `unique="suffix"` attribute appends a random numeric string to the end of the actual data string. This ensures that unique values are used for each test run. +See [Input testing data][] for more information. + +## Convert actions to action groups + +Action groups are sets of steps that are run together. Action groups are designed to break up multiple individual steps into logical groups. For example: logging into the admin panel requires ensuring the login form exists, filling in two form fields and clicking the **Submit** button. These can be bundled into a single, reusable "LoginAsAdmin" action group that can be applied to any other test. This leverages existing code and prevents duplication of effort. We recommend that all steps in a test be within an action group. + +Using action groups can be very useful when testing extensions. +Extending the example above, assume the first extension adds a new field to the admin log in, a Captcha for example. +The second extension we are testing needs to log in AND get past the Captcha. + +1. The admin login is encapsulated in an action group. +2. The Captcha extension properly extends the `LoginAsAdmin` capture group using the `merge` functionality. +3. Now the second extension can call the `LoginAsAdmin` action group and because of the `merge`, it will automatically include the Captcha field. + +In this case, the action group is both reusable and extensible! + +We further abstract the test by putting these action groups in their own file: ['app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductActionGroup.xml'](https://raw.githubusercontent.com/magento-pangolin/magento2/e5671d84aa63cad772fbba757005b3d89ddb79d9/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductActionGroup.xml) + +To create an action group, take the steps and put them within an `<actionGroup>` element. Note that the `<argument>` node defines the `_defaultProduct` data entity that is required for the action group. + +```xml +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> +<!--Fill main fields in create product form--> + <actionGroup name="fillMainProductForm"> + <arguments> + <argument name="product" defaultValue="_defaultProduct"/> + </arguments> + -<fillField selector="{{AdminProductFormSection.productName}}" userInput="{{_defaultProduct.name}}" stepKey="fillNameField" /> + +<fillField selector="{{AdminProductFormSection.productName}}" userInput="{{product.name}}" stepKey="fillProductName"/> + <fillField selector="{{AdminProductFormSection.productSku}}" userInput="{{product.sku}}" stepKey="fillProductSku"/> + <fillField selector="{{AdminProductFormSection.productPrice}}" userInput="{{product.price}}" stepKey="fillProductPrice"/> + <fillField selector="{{AdminProductFormSection.productQuantity}}" userInput="{{product.quantity}}" stepKey="fillProductQty"/> + <selectOption selector="{{AdminProductFormSection.productStockStatus}}" userInput="{{product.status}}" stepKey="selectStockStatus"/> + <selectOption selector="{{AdminProductFormSection.productWeightSelect}}" userInput="This item has weight" stepKey="selectWeight"/> + <fillField selector="{{AdminProductFormSection.productWeight}}" userInput="{{product.weight}}" stepKey="fillProductWeight"/> + <click selector="{{AdminProductSEOSection.sectionHeader}}" stepKey="openSeoSection"/> + <fillField userInput="{{product.urlKey}}" selector="{{AdminProductSEOSection.urlKeyInput}}" stepKey="fillUrlKey"/> + </actionGroup> +``` + +Note how the `<argument>` node takes in the `_defaultProduct` data entity and renames it to `product`, which is then used for the `userInput` values. + +Now we can reference this action group within our test (and any other test). + +```xml +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="CreateSimpleProductTest"> + <annotations> + <features value="Catalog"/> + <stories value="Create Product"/> + <title value="Admin should be able to create simple product."/> + <description value="Admin should be able to create simple product."/> + <severity value="MAJOR"/> + <group value="Catalog"/> + <group value="alex" /> + </annotations> + <before> + <!-- Login to Admin panel --> + <actionGroup ref="LoginAsAdmin" stepKey="loginToAdminPanel" /> + </before> + <after> + <!-- Logout from Admin panel --> + </after> + + <!-- Navigate to Catalog -> Products page (or just open by link) --> + <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="openProductGridPage" /> + <waitForPageLoad stepKey="waitForProductGridPageLoaded" /> + + <!-- Click "Add Product" button --> + <actionGroup ref="goToCreateProductPage" stepKey="goToCreateProductPage" /> + -<fillField selector="{{AdminProductFormSection.productName}}" userInput="{{product.name}}" stepKey="fillProductName"/> + +<actionGroup ref="fillMainProductForm" stepKey="fillProductForm"> + <argument name="product" value="_defaultProduct" /> + </actionGroup> + + <!-- See success save message "You saved the product." --> + <actionGroup ref="saveProductForm" stepKey="clickSaveOnProductForm" /> + + <actionGroup ref="AssertProductInGridActionGroup" stepKey="assertProductInGrid" /> + + <!-- Open Storefront Product Page and verify "Name", "SKU", "Price" --> + <actionGroup ref="AssertProductInStorefrontProductPage" stepKey="assertProductInStorefrontProductPage"> + <argument name="product" value="_defaultProduct" /> + </actionGroup> + </test> +</tests> +``` + +A well written test will end up being a set of action groups. +The finished test is fully abstracted in such a way that it is short and readable and importantly, the abstracted data and action groups can be used again. + +<!-- Link Definitions --> +[actions]: https://devdocs.magento.com/mftf/docs/test/actions.html +[action groups]: https://devdocs.magento.com/mftf/docs/test/action-groups.html +[Data entities]: https://devdocs.magento.com/mftf/docs/data.html +[Input testing data]: https://devdocs.magento.com/mftf/docs/data.html +[parameterized selectors]: https://devdocs.magento.com/mftf/docs/section/parameterized-selectors.html diff --git a/docs/versioning.md b/docs/versioning.md index 660b88a40..d018058d5 100644 --- a/docs/versioning.md +++ b/docs/versioning.md @@ -1,8 +1,8 @@ -# Versioning +# MFTF versioning schema -This documemt describes the versioning policy for the Magento Functional Testing Framework (MFTF), including the version numbering schema. +This document describes the versioning policy for the Magento Functional Testing Framework (MFTF), including the version numbering schema. -## Backward Compatibility +## Backward compatibility In this context, backward compatibility means that when changes are made to the MFTF, all existing tests still run normally. If a modification to MFTF forces tests to be changed, this is a backward incompatible change. @@ -62,7 +62,7 @@ This table lists the version of the MFTF that was released with a particular ver |Magento version| MFTF version| |---|---| +| 2.3.2 | 2.3.14 | | 2.3.1 | 2.3.13 | | 2.3.0 | 2.3.9 | | 2.2.8 | 2.3.13 | -| 2.2.7 | 2.3.8 | From 88019622b8dbded2708ff16774bf4c33776b0659 Mon Sep 17 00:00:00 2001 From: Donald Booth <dobooth@adobe.com> Date: Fri, 9 Aug 2019 09:31:17 -0500 Subject: [PATCH 11/15] file name change --- docs/{mftf_tests.md => mftf-tests.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename docs/{mftf_tests.md => mftf-tests.md} (100%) diff --git a/docs/mftf_tests.md b/docs/mftf-tests.md similarity index 100% rename from docs/mftf_tests.md rename to docs/mftf-tests.md From c9ddf7996e668d2fdebdc07d2f3beebd3d9d2eaa Mon Sep 17 00:00:00 2001 From: Donald Booth <dobooth@adobe.com> Date: Fri, 9 Aug 2019 09:38:06 -0500 Subject: [PATCH 12/15] Fix pointers --- docs/test-prep.md | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/docs/test-prep.md b/docs/test-prep.md index 35d54d3e9..b344fcf9f 100644 --- a/docs/test-prep.md +++ b/docs/test-prep.md @@ -83,7 +83,7 @@ For our example, we have a test that creates a simple product. Note the hardcode <waitForPageLoad stepKey="waitForNewProductPageOpened" /> <!-- Fill field "Name" with "Simple Product %unique_value%" --> - ---> <fillField selector="input[name='product[name]']" userInput="Simple Product 12412431" stepKey="fillNameField" /> + -----><fillField selector="input[name='product[name]']" userInput="Simple Product 12412431" stepKey="fillNameField" /> <!-- Fill field "SKU" with "simple_product_%unique_value%" --> <fillField selector="input[name='product[sku]']" userInput="simple-product-12412431" stepKey="fillSKUField" /> @@ -149,10 +149,7 @@ In this example `AdminProductFormSection` refers to the `<section>` in the XML f <waitForPageLoad stepKey="waitForNewProductPageOpened" /> <!-- Fill field "Name" with "Simple Product %unique_value%" --> - ```diff - -<fillField selector="input[name='product[name]']" userInput="Simple Product 12412431" stepKey="fillNameField" /> - +<fillField selector="{{AdminProductFormSection.productName}}" userInput="Simple Product 12412431" stepKey="fillNameField" /> - ``` + -----><fillField selector="{{AdminProductFormSection.productName}}" userInput="Simple Product 12412431" stepKey="fillNameField" /> <!-- Fill field "SKU" with "simple_product_%unique_value%" --> <fillField selector="{{AdminProductFormSection.productSku}}" userInput="simple-product-12412431" stepKey="fillSKUField" /> @@ -190,7 +187,7 @@ Here we are interested in `<section name="AdminProductFormSection">`, where we a <element name="attributeSetFilter" type="input" selector="div[data-index='attribute_set_id'] .admin__field-control input" timeout="30"/> <element name="attributeSetFilterResult" type="input" selector="div[data-index='attribute_set_id'] .action-menu-item._last" timeout="30"/> <element name="attributeSetFilterResultByName" type="text" selector="//label/span[text() = '{{var}}']" timeout="30" parameterized="true"/> - ---> <element name="productName" type="input" selector="input[name='product[name]']"/> + -----><element name="productName" type="input" selector="input[name='product[name]']"/> <element name="RequiredNameIndicator" type="text" selector=" return window.getComputedStyle(document.querySelector('._required[data-index=name]>.admin__field-label span'), ':after').getPropertyValue('content');"/> <element name="RequiredSkuIndicator" type="text" selector=" return window.getComputedStyle(document.querySelector('._required[data-index=sku]>.admin__field-label span'), ':after').getPropertyValue('content');"/> <element name="productSku" type="input" selector="input[name='product[sku]']"/> @@ -250,8 +247,7 @@ We replace the hardcoded values with variables and the MFTF will do the variable <waitForPageLoad stepKey="waitForNewProductPageOpened" /> <!-- Fill field "Name" with "Simple Product %unique_value%" --> - -<fillField selector="{{AdminProductFormSection.productName}}" userInput="Simple Product 12412431" stepKey="fillNameField" /> - +<fillField selector="{{AdminProductFormSection.productName}}" userInput="{{_defaultProduct.name}}" stepKey="fillNameField" /> + ----><fillField selector="{{AdminProductFormSection.productName}}" userInput="{{_defaultProduct.name}}" stepKey="fillNameField" /> <!-- Fill field "SKU" with "simple_product_%unique_value%" --> <fillField selector="{{AdminProductFormSection.productSku}}" userInput="{{_defaultProduct.sku}}" stepKey="fillSKUField" /> @@ -331,8 +327,7 @@ To create an action group, take the steps and put them within an `<actionGroup>` <arguments> <argument name="product" defaultValue="_defaultProduct"/> </arguments> - -<fillField selector="{{AdminProductFormSection.productName}}" userInput="{{_defaultProduct.name}}" stepKey="fillNameField" /> - +<fillField selector="{{AdminProductFormSection.productName}}" userInput="{{product.name}}" stepKey="fillProductName"/> + -----><fillField selector="{{AdminProductFormSection.productName}}" userInput="{{product.name}}" stepKey="fillProductName"/> <fillField selector="{{AdminProductFormSection.productSku}}" userInput="{{product.sku}}" stepKey="fillProductSku"/> <fillField selector="{{AdminProductFormSection.productPrice}}" userInput="{{product.price}}" stepKey="fillProductPrice"/> <fillField selector="{{AdminProductFormSection.productQuantity}}" userInput="{{product.quantity}}" stepKey="fillProductQty"/> @@ -383,8 +378,8 @@ Now we can reference this action group within our test (and any other test). <!-- Click "Add Product" button --> <actionGroup ref="goToCreateProductPage" stepKey="goToCreateProductPage" /> - -<fillField selector="{{AdminProductFormSection.productName}}" userInput="{{product.name}}" stepKey="fillProductName"/> - +<actionGroup ref="fillMainProductForm" stepKey="fillProductForm"> + -----><fillField selector="{{AdminProductFormSection.productName}}" userInput="{{product.name}}" stepKey="fillProductName"/> + <actionGroup ref="fillMainProductForm" stepKey="fillProductForm"> <argument name="product" value="_defaultProduct" /> </actionGroup> From e202f076873c610bb3cb7d296c942a924f4a6547 Mon Sep 17 00:00:00 2001 From: Donald Booth <dobooth@adobe.com> Date: Mon, 12 Aug 2019 13:26:48 -0500 Subject: [PATCH 13/15] Removed duplicate ID --- docs/getting-started.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/getting-started.md b/docs/getting-started.md index 84e19af2e..450dbc2a6 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -257,7 +257,7 @@ cd magento2-functional-testing-framework composer install ``` -### Step 3. Build the project {#build-project} +### Step 3. Build the project ```bash bin/mftf build:project @@ -309,7 +309,6 @@ allure serve dev/tests/_output/allure-results/ [allure docs]: https://docs.qameta.io/allure/ [Allure Framework]: http://allure.qatools.ru/ [basic configuration]: configuration.html#basic-configuration -[build]: #build-project [chrome driver]: https://sites.google.com/a/chromium.org/chromedriver/downloads [Codeception Test execution]: https://blog.jetbrains.com/phpstorm/2017/03/codeception-support-comes-to-phpstorm-2017-1/ [composer]: https://getcomposer.org/download/ From ca130c3495d1d45d60460b89842c3de0da79370e Mon Sep 17 00:00:00 2001 From: Kevin Kozan <kkozan@magento.com> Date: Wed, 14 Aug 2019 09:10:26 -0500 Subject: [PATCH 14/15] MQE-1510 --- etc/config/command.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/etc/config/command.php b/etc/config/command.php index 047af324a..f018e3014 100644 --- a/etc/config/command.php +++ b/etc/config/command.php @@ -12,8 +12,8 @@ $tokenModel = $magentoObjectManager->get(\Magento\Integration\Model\Oauth\Token::class); $tokenPassedIn = urldecode($_POST['token']); - $command = urldecode($_POST['command']); - $arguments = urldecode($_POST['arguments']); + $command = str_replace([';', '&', '|'], '', urldecode($_POST['command'])); + $arguments = str_replace([';', '&', '|'], '', urldecode($_POST['arguments'])); // Token returned will be null if the token we passed in is invalid $tokenFromMagento = $tokenModel->loadByToken($tokenPassedIn)->getToken(); From 88d64831e2563ba7a584ed92619c583df46026d6 Mon Sep 17 00:00:00 2001 From: Kevin Kozan <kkozan@magento.com> Date: Wed, 14 Aug 2019 10:13:34 -0500 Subject: [PATCH 15/15] MQE-1699: CHANGELOG.MD and Composer version bump - Version bump and changelog addition. --- CHANGELOG.md | 5 +++++ bin/mftf | 2 +- composer.json | 2 +- composer.lock | 2 +- 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c0a74be6f..d3f82a2f9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ Magento Functional Testing Framework Changelog ================================================ +2.4.4 +----- +### Fixes +* Fixed an issue where `_CREDS` could not be resolved when used in a suite. + 2.4.3 ----- * Customizability diff --git a/bin/mftf b/bin/mftf index e45835696..0f2bf274d 100755 --- a/bin/mftf +++ b/bin/mftf @@ -29,7 +29,7 @@ try { try { $application = new Symfony\Component\Console\Application(); $application->setName('Magento Functional Testing Framework CLI'); - $application->setVersion('2.4.3'); + $application->setVersion('2.4.4'); /** @var \Magento\FunctionalTestingFramework\Console\CommandListInterface $commandList */ $commandList = new \Magento\FunctionalTestingFramework\Console\CommandList; foreach ($commandList->getCommands() as $command) { diff --git a/composer.json b/composer.json index 39d206ee8..bc36fb60e 100755 --- a/composer.json +++ b/composer.json @@ -2,7 +2,7 @@ "name": "magento/magento2-functional-testing-framework", "description": "Magento2 Functional Testing Framework", "type": "library", - "version": "2.4.3", + "version": "2.4.4", "license": "AGPL-3.0", "keywords": ["magento", "automation", "functional", "testing"], "config": { diff --git a/composer.lock b/composer.lock index 7875ec7e1..d415acc0a 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": "8c865770654a7053c62a505c2ca31942", + "content-hash": "b1795ca4e2c9a15582db44baf54962ad", "packages": [ { "name": "allure-framework/allure-codeception",