Metadata

In this topic we talk about handling entities that you need in your tests (such as categories, products, wish lists, and similar) using MFTF. Using data handling actions like [createData], [deleteData], [updateData], and [getData], you are able to create, delete, update, and read entities for your tests. The framework enables you to send HTTP requests with these statically defined data entities:

You have probably noticed that some modules in acceptance functional tests contain a directory, which is called Metadata.

Example of a module with Metadata:

Wishlist
├── Data
├── Metadata
├── Page
├── Section
└── Test

This directory contains XML files with metadata required to create a valid request to handle an entity defined in dataType. A metadata file contains a list of operations with different types (defined in type). Each operation includes:

When a test step requires handling the specified data entity, the Functional Testing Framework performs the following steps:

The following diagram demonstrates the XML structure of a metadata file: Structure of metadata

Format

<?xml version="1.0" encoding="UTF-8"?>
<operations xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataOperation.xsd">
    <operation name=""
               dataType=""
               type=""
               auth=""
               url=""
               method="">
        <contentType></contentType>
        <object key="" dataType="">
            <field key="">integer</field>
            <field key="">string</field>
            <field key="">boolean</field>
            <array key="">
                <value>string</value>
            </array>
        </object>
    </operation>
</operations>

Principles

  1. A dataType value must match the type value of the corresponding entity.
  2. A filename should be PascalCase and end with Meta.xml. Example: ProductAttributeMeta.xml.
  3. A metadata file may contain different types of operations (type) with the same data entity (dataType).

Example:

<operations>
    <operation type="create" dataType="category">
        ...
    </operation>
    <operation type="update" dataType="category">
        ...
    </operation>
    <operation type="delete" dataType="category">
        ...
    </operation>
    <operation type="get" dataType="category">
        ...
    </operation>
</operations>

Handling entities using REST API

Sending a REST API request

The Functional Testing Framework allows you to handle basic CRUD operations with an object using REST API requests. To convert a request to the Functional Testing Framework format, wrap the corresponding REST API request into XML tags according to the Reference documentation.

Category Example

This is an example of how to handle a category using REST API operations provided by the catalogCategoryRepositoryV1 service.

REST API operations provided by catalogCategoryRepositoryV1

The above screenshot from the REST API Reference demonstrates a list of available operations to:

We assume that our .env file sets MAGENTO_BASE_URL=https://example.com/ and MAGENTO_BACKEND_NAME=admin.

Create a simple category

Let's see what happens when you create a category:

<createData entity="_defaultCategory" stepKey="createPreReqCategory"/>

The Functional Testing Framework searches in the Data directory an entity with <entity name="_defaultCategory"> and reads type of the entity. If there are more than one entity with the same name, all of the entities are merged.

Catalog/Data/CategoryData.xml:

<entity name="_defaultCategory" type="category">
    <data key="name" unique="suffix">simpleCategory</data>
    <data key="name_lwr" unique="suffix">simplecategory</data>
    <data key="is_active">true</data>
</entity>

Here, type is equal to "category", which instructs the Functional Testing Framework to search an operation with dataType="category". Since the action is to create a category, the Functional Testing Framework will also search for operation with type="create" in Metadata for dataType="category".

Catalog/Metadata/CategoryMeta.xml:

<operation name="CreateCategory" dataType="category" type="create" auth="adminOauth" url="/V1/categories" method="POST">
    <contentType>application/json</contentType>
    <object key="category" dataType="category">
        <field key="parent_id">integer</field>
        <field key="name">string</field>
        <field key="is_active">boolean</field>
        <field key="position">integer</field>
        <field key="level">integer</field>
        <field key="children">string</field>
        <field key="created_at">string</field>
        <field key="updated_at">string</field>
        <field key="path">string</field>
        <field key="include_in_menu">boolean</field>
        <array key="available_sort_by">
            <value>string</value>
        </array>
        <field key="extension_attributes">empty_extension_attribute</field>
        <array key="custom_attributes">
            <value>custom_attribute</value>
        </array>
    </object>
</operation>

(The corresponding operation is provided by catalogCategoryRepositoryV1 in REST API.)

The following is encoded in <operation>:

<contentType>application/json</contentType> defines a content type of the REST API request, which is set as application/json here.

The parameter that declares a body of the request is catalogCategoryRepositoryV1SavePostBody. Using the Reference, we can trace how the JSON request was converted into XML representation.

data-variant=info
data-slots=text
Comments in the example below are used to demonstrate relation between JSON request and Functional Testing Framework metadata in XML. JSON does not support comments.

Model schema for catalogCategoryRepositoryV1SavePostBody with XML representation of Catalog/Metadata/CategoryMeta.xml in comments:

{                                           // XML representation in the Functional Testing Framework metadata format (see 'Catalog/Metadata/CategoryMeta.xml')
  "category": {                             // <object key="category" dataType="category">
    "id": 0,                                // Skipped, because Category ID is not available on UI when you create a new category.
    "parent_id": 0,                         // <field key="parent_id">integer</field>
    "name": "string",                       // <field key="name">string</field>
    "is_active": true,                      // <field key="is_active">boolean</field>
    "position": 0,                          // <field key="position">integer</field>
    "level": 0,                             // <field key="level">integer</field>
    "children": "string",                   // <field key="children">string</field>
    "created_at": "string",                 // <field key="created_at">string</field>
    "updated_at": "string",                 // <field key="updated_at">string</field>
    "path": "string",                       // <field key="path">string</field>
    "available_sort_by": [                  // <array key="available_sort_by">
      "string"                              // <value>string</value>
    ],                                      // </array>
    "include_in_menu": true,                // <field key="include_in_menu">boolean</field>
    "extension_attributes": {},             // <field key="extension_attributes">empty_extension_attribute</field>, where 'empty_extension_attribute' is a reference to operation with 'dataType="empty_extension_attribute"' (see 'Catalog/Metadata/EmptyExtensionAttributeMeta.xml')
    "custom_attributes": [                  // <array key="custom_attributes">
      {                                     // <value>custom_attribute</value>, where 'custom_attribute' is a reference to operation with 'dataType="custom_attribute"' (see 'Catalog/Metadata/CustomAttributeMeta.xml')
        "attribute_code": "string",
        "value": "string"
      }
    ]                                       // </array>
  }                                         // </object>
}

So, the body of a REST API request that creates a simple category is the following:

{                                             // XML representation of input data used to create a simple category ("Catalog/Data/CategoryData.xml")
  "category": {                               // <entity name="_defaultCategory" type="category">
    "name": "simpleCategory_0986576456",      // <data key="name" unique="suffix">simpleCategory</data>
    "is_active": true                         // <data key="is_active">true</data>
  }                                           // </entity>
}

Guest Cart Example

This is an example of how to handle a guest cart using REST API operations.

Create an object as a guest

The corresponding test step is:

<createData entity="guestCart" stepKey="createGuestCart"/>

The Functional Testing Framework searches in the Data directory an entity with <entity name="GuestCart"> and reads type.

Quote/Data/GuestCartData.xml:

<entity name="GuestCart" type="GuestCart">
</entity>

type="guestCart" points to the operation with dataType=GuestCart" and type="create" in the Metadata directory.

Quote/Metadata/GuestCartMeta.xml:

<operation name="CreateGuestCart" dataType="GuestCart" type="create" auth="anonymous" url="/V1/guest-carts" method="POST">
    <contentType>application/json</contentType>
</operation>

As a result, the Functional Testing Framework sends an unauthorized POST request with an empty body to the https://example.com/rest/V1/guest-carts and stores the single string response that the endpoint returns.

Company Relation Example

This example creates XML data files in the Data and Metadata directories that allow the Functional Testing Framework to process the REST API operations for creating and deleting a company relation. This functionality is available only to Adobe Commerce B2B customers.

REST API endpoints:

Create a company relation

The corresponding test step is:

First create two different companies of different types before creating the company relation.

<createData entity="Simple_US_Customer" stepKey="companyAdmin1"/>
<createData entity="ParentCompany" stepKey="parentCompany">
    <requiredEntity createDataKey="companyAdmin1"/>
</createData>

<createData entity="Simple_US_CA_Customer" stepKey="companyAdmin2"/>
<createData entity="ChildCompany" stepKey="childCompany">
    <requiredEntity createDataKey="companyAdmin2"/>
</createData>

<createData entity="OneCompanyRelation" stepKey="createCompanyRelation">
    <requiredEntity createDataKey="parentCompany"/>
    <requiredEntity createDataKey="childCompany"/>
</createData>

The Functional Testing Framework searches in the Data directory for the entities with <entity name="ParentCompany">, <entity name="ChildCompany">, and <entity name="OneCompanyRelation"> and reads the type.

CompanyRelation/Data/CompanyData.xml:

<!-- Data Type "company" -->
<entity name="ParentCompany" type="company" extends="Default_Company">
    <data key="company_name" unique="suffix">Parent Company </data>
</entity>
<!-- Data Type "company2" -->
<entity name="ChildCompany" type="company2">
    <data key="status">1</data>
    <data key="company_name" unique="suffix">Child Company </data>
    <array key="street">
        <item>7700 W Parmer Ln</item>
        <item>Bld D</item>
    </array>
    <data key="city">Culver City</data>
    <data key="country_id">US</data>
    <data key="region_id">57</data>
    <data key="postcode">90230</data>
    <data key="telephone">555-555-5555</data>
    <data key="customer_group_id">1</data>
    <data key="sales_representative_id">1</data>
    <var key="super_user_id" entityType="customer" entityKey="id"/>
    <var key="company_email" entityType="customer" entityKey="email"/>
</entity>

Notice that the two company entities have two different types type="company" and type="company2". These types are used later in Data fields and Metadata URLs for company relations.

CompanyRelation/Data/CompanyRelationData.xml:

<entity name="OneCompanyRelation" type="company_relation">
    <requiredEntity type="company_id">CompanyId</requiredEntity>
</entity>

CompanyRelations/Data/CompanyIdData.xml:

<entity name="CompanyId" type="company_id">
    <var key="company_id" entityType="company2" entityKey="id"/>
</entity>

CompanyRelation/Metadata/CompanyIdMeta.xml:

<operation name="CreateCompanyId" dataType="company_id" type="create">
    <field key="company_id">integer</field>
</operation>

<field key="company_id">integer</field> creates an object with a key company_id with a value of type integer.

{
  "company_id": 1
}

CompanyRelation/Metadata/CompanyRelationMeta.xml:

<operation name="CreateCompanyRelation" dataType="company_relation" type="create" auth="adminOauth" url="/V1/company/{company.id}/relations" method="POST">
    <contentType>application/json</contentType>
    <array key="relations">
        <value>company_id</value>
    </array>
</operation>

As a result, the Functional Testing Framework sends a POST request with an array of company_ids in the body to https://example.com/rest/V1/company/{company.id}/relations. Currently, the Functional Testing Framework supports sending only one company as a child company for a parent.

{
  "relations": [
    {
      "company_id":  1
    }
  ]
}
Delete a company relation

The corresponding test step is:

<deleteData createDataKey="createCompanyRelation" stepKey="deleteCompanyRelation"/>

createDataKey="createCompanyRelation" is the same as the createData entity with stepKey="createCompanyRelation" in the test. This step calls the delete operation that has the same dataType="company_relation" value as the createData entity.

CompanyRelation/Metadata/CompanyRelationMeta.xml:

<operation name="DeleteCompanyRelation" dataType="company_relation" type="delete" auth="adminOauth" url="/V1/company/{company.id}/relations/{company2.id}" method="DELETE">
    <contentType>application/json</contentType>
</operation>

As a result, the Functional Testing Framework sends a DELETE request to https://example.com/rest/V1/company/{company.id}/relations/{company2.id}.

Handling a REST API response

There are cases when you need to reuse the data that Adobe Commerce or Magento Open source returned in response to your POST request.

Let's see how to handle data after you created a category with custom attributes:

<createData entity="customizedCategory" stepKey="createPreReqCategory"/>

The Functional Testing Framework receives the corresponding JSON response and enables you to reference its data using a variable of format:

$__ stepKey . JsonKey __$

Example:

$createPreReqCategory.id$

And for a custom attribute:

$__ stepKey .custom_attributes[ attribute key ] __$

Example:

$createPreReqCategory.custom_attributes[is_anchor]$

The following example of response in JSON demonstrates how to reference data on the root level and as data in custom attributes:

{
    "id": 7,                                          //$createPreReqCategory.id$
    "parent_id": 2,                                   //$createPreReqCategory.parent_id$
    "name": "simpleCategory_0986576456",              //$createPreReqCategory.is_active$
    "is_active": true,
    "position": 5,
    "level": 2,
    "children": "",
    "created_at": "2018-05-08 14:27:18",
    "updated_at": "2018-05-08 14:27:18",
    "path": "1/2/7",
    "available_sort_by": [],
    "include_in_menu": true,
    "custom_attributes": [
        {
            "attribute_code": "is_anchor",
            "value": "1"                              //$createPreReqCategory.custom_attributes[is_anchor]$
        },
        {
            "attribute_code": "path",
            "value": "1/2/7"                          //$createPreReqCategory.custom_attributes[path]$
        },
        {
            "attribute_code": "children_count",
            "value": "0"
        },
        {
            "attribute_code": "url_key",
            "value": "simplecategory5af1b41cd58fb4"   //$createPreReqCategory.custom_attributes[url_key]$
        },
        {
            "attribute_code": "url_path",
            "value": "simplecategory5af1b41cd58fb4"
        }
    ],
}
}

Handling entities using HTML forms

For cases when REST API is not applicable, you may use HTML forms (when all object parameters are encoded in a URL as key=name attributes). There are two different attributes to split access to different areas:

You are able to create assurances with successRegex, and, optionally, return values with returnRegex. You can also use returnIndex when returnRegex matches multiple values.

Create an object in Admin

The CreateStoreGroup operation is used to persist a store group:

Source file is Store/Metadata/StoreGroupMeta.xml:

<operation name="CreateStoreGroup" dataType="group" type="create" auth="adminFormKey" url="/admin/system_store/save" method="POST" successRegex="/messages-message-success/" >
    <contentType>application/x-www-form-urlencoded</contentType>
    <object dataType="group" key="group">
        <field key="group_id">string</field>
        <field key="name">string</field>
        <field key="code">string</field>
        <field key="root_category_id">integer</field>
        <field key="website_id">integer</field>
    </object>
    <field key="store_action">string</field>
    <field key="store_type">string</field>
</operation>

The operation is called when <createData> (type="create") points to a data entity of type "group" (dataType="group"). It sends a POST request (method="POST") to http://example.com/admin/system_store/save (url="/admin/system_store/save") that is authorized for the Admin area (auth="adminFormKey"). The request contains HTML form data encoded in the application/x-www-form-urlencoded content type (<contentType>application/x-www-form-urlencoded</contentType>). If the returned HTML code contains the messages-message-success string, it is resolved as successful.

The operation enables you to assign the following form fields:

Create an object in storefront

The Functional Testing Framework uses the CreateWishlist operation to create a wish list on storefront:

Source file is Wishlist/Metadata/WishlistMeta.xml

<operation name="CreateWishlist" dataType="wishlist" type="create" auth="customerFormKey" url="/wishlist/index/add/" method="POST" successRegex="" returnRegex="~\/wishlist_id\/(\d*?)\/~" >
    <contentType>application/x-www-form-urlencoded</contentType>
    <field key="product">integer</field>
    <field key="customer_email">string</field>
    <field key="customer_password">string</field>
</operation>

The operation is used when <createData> (type="create") points to a data entity of type "wishlist" (dataType="wishlist"). It sends a POST request (method="POST") to http://example.com/wishlist/index/add/ (url="wishlist/index/add/") as a customer (auth="customerFormKey"). The request contains HTML form data encoded in the application/x-www-form-urlencoded content type (<contentType>application/x-www-form-urlencoded</contentType>). If the returned HTML code contains a string that matches the regular expression ~\/wishlist_id\/(\d*?)\/~, it returns the matching value.

The operation assigns three form fields:

Reference

operations

Root element that points to the corresponding XML Schema.

operation

Attribute
Type
Use
Description
name
string
optional
Name of the operation.
dataType
string
required
Data type of the operation. It refers to a type attribute of data entity that will be used as a source of input data.
type
Possible values: create, delete, update, get.
required
Type of operation.
*url
string
optional
A routing URL of the operation. Example value: "/V1/categories".
**auth
Possible values: adminOath, adminFormKey, customerFormKey, anonymous
optional
Type of authorization of the operation.
method
string
optional
HTTP method of the operation. Possible values: POST, DELETE, PUT, GET.
successRegex
string
optional
Determines if the operation was successful. Parses the HTML body in response and asserts if the value assigned to the successRegex exists.
returnRegex
string
optional
Determines if the response contains the matching value to return.
returnIndex
string
optional
Specifies index at which the value will be returned when returnRegex matches multiple values
removeBackend
boolean
optional
Removes backend name from requested URL. Applicable when auth="adminFormKey".
filename
string
optional
deprecated
string
optional
Used to warn about the future deprecation of the test. String will appear in Allure reports and console output at runtime.

contentType

Sets one of the following operation types:

object

Representation of a complex entity that may contain fields, arrays, and objects. An object must match the entity of the same type.

Attribute
Type
Use
Description
key
string
optional
Name of the object.
dataType
string
required
Type of the related entity.
required
boolean
optional
Determines if the object is required or not. It must match the REST API specification.

field

Representation of HTML form or REST API fields.

Attribute
Type
Use
Description
key
string
required
Name of the field. It must match the field name of the related entity.
type
string
optional
Type of the value. It may contain a primitive type or the type of another operation.
required
boolean
optional
Determines if the field is required or not. It must match the REST API specification.

array

Representation of an array.

Attribute
Type
Use
Description
key
string
required
Name of the array.

It contains one or more value elements.

value

Declares a data type for items within <array>.

Example of an array with value of a primitive data type

Metadata declaration of the operation schema:

<array key="tax_rate_ids">
    <value>integer</value>
</array>

The value can contain one or more items.

Data entity with the corresponding assignment:

<array key="tax_rate_ids">
    <item>1</item>
    <item>2</item>
</array>
"tax_rate_ids":
    [
        "item": 1,
        "item": 2
    ]

Example of an array containing data entities

<array key="product_options">
    <value>product_option</value>
</array>

The value declares the product_options array that contains one or more entities of the product_option data type.

Example of an array containing a particular data field

<array key="tax_rate_ids">
    <value>tax_rate.id</value>
</array>

The value declares the tax_rate_ids array that contains one or more id fields of the tax_rate data type entity.

An additional parameter in REST API request.

Attribute
Type
Use
Description
param
string
required
A REST API header parameter.
<header param="status">available</header>

param

An additional parameter in URL.

Attribute
Type
Use
Description
key
string
required
Name of the URL parameter.

Example:

<param key="status">someValue</param>