MFTF

MFTF drives a real browser from test steps written in XML; it is the slowest suite, so automate only the journeys that matter.

Syntaxvendor/bin/mftf run:test <TestName> [-k|--skip-generate]

MFTF, the Magento Functional Testing Framework, clicks through the real storefront and admin in a real browser. Tests are XML, not PHP: each step is an element such as amOnPage, fillField, click or see. Magento 2.4.9 requires MFTF 6, installed as a dev dependency and invoked as vendor/bin/mftf from the project root.

The files

Everything sits under Vendor/Module/Test/Mftf, split by purpose: Test for the tests, Section for named CSS or XPath selectors, Page for URLs, Data and Metadata for records the test creates over the API, ActionGroup for reusable step sequences, and Suite for grouping. Selectors belong in a section file, never inline in a test, so one theme change is one edit.

Action groups

An action group is a named list of steps with declared arguments. Calling it with <actionGroup ref="..." stepKey="..."/> is shorthand for pasting those steps into your test, and MFTF expands it exactly that way when it generates PHP. Core ships hundreds, including AdminLoginActionGroup and LoginToStorefrontActionGroup; use them instead of retyping a login.

What it costs

Each test needs a Selenium server, a Chrome instance and a store that MFTF can log into: set Admin Account Sharing to Yes and Add Secret Key to URLs to No under Stores > Configuration > Advanced > Admin > Security, then bin/magento cache:flush. Expect a minute or more per test and occasional timing failures. Automate guest checkout, logged-in checkout, and the one admin form your module owns. Nothing else.

Example

Example · xml
<!-- app/code/Vendor/Loyalty/Test/Mftf/Test/StorefrontLoyaltyPointsTest.xml -->
<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd">
    <test name="StorefrontLoyaltyPointsTest">
        <annotations>
            <features value="Loyalty"/>
            <stories value="Points balance in the customer account"/>
            <title value="A new customer sees a zero points balance"/>
            <description value="Creates a customer, signs in and checks the balance in My Account"/>
            <severity value="AVERAGE"/>
            <group value="Loyalty"/>
        </annotations>
        <before>
            <createData entity="LoyaltyCustomer" stepKey="createCustomer"/>
        </before>
        <after>
            <deleteData createDataKey="createCustomer" stepKey="deleteCustomer"/>
        </after>

        <actionGroup ref="LoginToStorefrontActionGroup" stepKey="signIn">
            <argument name="Customer" value="$$createCustomer$$"/>
        </actionGroup>
        <amOnPage url="{{StorefrontLoyaltyPointsPage.url}}" stepKey="openPointsPage"/>
        <waitForPageLoad stepKey="waitForPointsPage"/>
        <see selector="{{StorefrontLoyaltyPointsSection.balance}}"
             userInput="0"
             stepKey="seeZeroBalance"/>
    </test>
</tests>

When to use it

  • Automating guest checkout so a broken payment step cannot ship unnoticed.
  • Covering the one admin form your module adds, including its validation messages.
  • Reproducing a bug that only appears after a real JavaScript-driven step.
  • Deciding which three of twenty proposed browser tests are worth their runtime.

More examples

An action group, and the steps it stands for

The two forms are the same thing: when MFTF generates PHP it inlines the group's steps and appends the calling stepKey to each of the group's own stepKeys, which is why generated step names look like fillRateSetRate. Define a group as soon as two tests need the same sequence. Regenerate after editing it with vendor/bin/mftf generate:tests, or go straight to vendor/bin/mftf run:test, which generates first.

Example · xml
<!-- app/code/Vendor/Loyalty/Test/Mftf/ActionGroup/AdminSetPointsRateActionGroup.xml -->
<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
              xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd">
    <actionGroup name="AdminSetPointsRateActionGroup">
        <annotations>
            <description>Sets the loyalty points rate in the admin configuration and saves.</description>
        </annotations>
        <arguments>
            <argument name="rate" type="string"/>
        </arguments>
        <fillField selector="{{AdminLoyaltyConfigSection.perUnit}}"
                   userInput="{{rate}}"
                   stepKey="fillRate"/>
        <click selector="{{AdminLoyaltyConfigSection.save}}" stepKey="clickSave"/>
        <waitForPageLoad stepKey="waitForSave"/>
    </actionGroup>
</actionGroups>

<!-- Shorthand, in a test: -->
<!-- <actionGroup ref="AdminSetPointsRateActionGroup" stepKey="setRate">     -->
<!--     <argument name="rate" value="2"/>                                   -->
<!-- </actionGroup>                                                          -->
<!--                                                                         -->
<!-- Longhand, the same three steps written inline:                          -->
<!-- <fillField selector="{{AdminLoyaltyConfigSection.perUnit}}"             -->
<!--            userInput="2" stepKey="setRateFillRate"/>                    -->
<!-- <click selector="{{AdminLoyaltyConfigSection.save}}"                    -->
<!--        stepKey="setRateClickSave"/>                                     -->
<!-- <waitForPageLoad stepKey="setRateWaitForSave"/>                         -->

Selectors live in a section file

Tests reference these as {{StorefrontLoyaltyPointsSection.balance}}, so a template change is one edit here rather than a search across test files. parameterized="true" lets a test pass a value into the selector, as in {{StorefrontLoyaltyPointsSection.historyRow('2')}}. The timeout attribute makes MFTF wait for that element after a click, which removes most of the flakiness people blame on Selenium.

Example · xml
<!-- app/code/Vendor/Loyalty/Test/Mftf/Section/StorefrontLoyaltyPointsSection.xml -->
<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd">
    <section name="StorefrontLoyaltyPointsSection">
        <element name="balance" type="text" selector=".loyalty-points .balance"/>
        <element name="historyRow"
                 type="text"
                 selector=".loyalty-points tbody tr:nth-of-type({{row}})"
                 parameterized="true"/>
        <element name="redeemButton" type="button" selector="#loyalty-redeem" timeout="30"/>
    </section>
</sections>

The data the test creates before it starts

createData in a test's <before> block sends this entity to the REST API rather than clicking through a form, which is both faster and less fragile. unique="suffix" appends a generated suffix so repeated runs do not collide on the email. The matching deleteData in <after> removes it; without that pair a failed run leaves records behind and the next run fails for the wrong reason.

Example · xml
<!-- app/code/Vendor/Loyalty/Test/Mftf/Data/LoyaltyCustomerData.xml -->
<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd">
    <entity name="LoyaltyCustomer" type="customer">
        <data key="email" unique="suffix">[email protected]</data>
        <data key="firstname">Loyalty</data>
        <data key="lastname">Tester</data>
        <data key="password">Password123!</data>
        <data key="group_id">1</data>
        <data key="store_id">1</data>
        <data key="website_id">1</data>
    </entity>
</entities>

Discussion

  • Be the first to comment on this lesson.