Handler
You can use a handler to set up preconditions and prepare an initial testing environment for particular tests. For example, your scenario requires a particular widget that must be implicitly created before the test is started. You need a fixture, a data set, and a handler. The handler transfers data to the application being tested. The data is a list of fields from a fixture and values from data sets.
This topic focuses on handlers, and we’ll discuss types of handlers as well as how to create and use one.
Types of handlers
The FTF enables you to use any type of handler.
Magento uses the following handlers:
Type of handler | Mechanism | Example | Tip |
---|---|---|---|
UI | Drives the web browser. | Set of scripts for Selenium that simulate user actions to create a widget through a web browser. | The UI handler is much slower then the other handlers. When the test execution time is critical, you should avoid use of the UI handler. The UI handler code is very similar to the code of the test that doesn’t contain constraints. If you have a test for widget creation, you can re-use the code, because the code of UI handler that creates widget is very similar. |
cURL | Sends POST or PUT requests to the server hosting the application that is being tested. | HTTP POST request to the application server, that transfers Widget fixture fields and corresponding values from the data set. | Browser is not involved, that’s why the cURL handler works much faster than the UI handler. |
WebAPI | Sends a POST request using the REST API. See REST API reference documentation. | Similar to cURL but uses the REST API entry point. | Has the advantage of testing the API, faster than cURL. |
Furthermore, you can create your own handlers, such as Direct, which is very fast because the Direct handler sends a direct call to the Magento application using Magento models. The Direct handler requires deep understanding of the Magento application, and also requires access to the Magento code and the database. Difficulties can be caused when the Magento code and Magento tests are run on different hosts.
Configuration
One fixture can have various handlers.
When we create an entity in the test we do not indicate which handler to use.
This work is delegated to a fallback, which is a queue of handlers in the priority order specified in config.xml
.
config.xml
The default configuration for handlers is set in <magento2>/dev/tests/functional/etc/config.xml.dist
.
Create a duplicate of the file, and keep both, but make changes to the new one, which is called config.xml
:
1
cp config.xml.dist config.xml
The following nodes influence handlers:
Node | Semantics | Example |
---|---|---|
<backendLoginUrl> | Reference to the login form of the [Admin](https://glossary.magento.com/admin). | <backendLoginUrl>admin/auth/login</backendLoginUrl> |
<backendLogin> | A username to access the Admin as a Magento administrator. | <backendLogin>admin</backendLogin> |
<backendPassword> | A password to access the Admin as a Magento administrator. | <backendPassword>pas$worD</backendPassword> |
<handler> | Specifies priorities for different types of handler. The less the value, the higher the priority. The highest priority has value 0 . token contains access token (used by WebAPI handlers only). |
<handler> <webapi priority="0"> <token>integration_token</token> </webapi> <curl priority="1" /> <ui priority="2" /> </handler> |
Handler components
Handler interface
Each handler must implement a handler interface.
You should mention in a fixture the handler_interface
attribute with a reference to the PHP class: Magento\[module_name]\Test\Handler\[object_name]\[object_name]Interface
(example for the Widget: Magento\Widget\Test\Handler\Widget\WidgetInterface
).
Example of WidgetInterface.php
(should be placed in <magento2_root_dir>/dev/tests/functional/tests/app/Magento/Widget/Test/Handler/Widget
):
1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
namespace Magento\Widget\Test\Handler\Widget;
use Magento\Mtf\Handler\HandlerInterface;
/**
* Interface WidgetInterface
*/
interface WidgetInterface extends HandlerInterface
{
//
}
Handler class
To use the handler class, create an interface, declare a fallback in the config.xml
, and declare interface/class relationships in the di.xml
.
When this class is created, you can call the persist()
method to create Magento entity (for example, widget).
The method returns data that are matched with fixture fields.
All fixture fields that are matched are assigned values from the handler.
The persist()
method is declared in the InjectableFixture
class by path <magento2_root_dir>/dev/tests/functional/vendor/magento/mtf/Magento/Mtf/Fixture/InjectableFixture.php
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
/**
* Persists Fixture Data into application.
*
* @return void
*/
public function persist()
{
$this->eventManager->dispatchEvent(['persist_before'], [get_class($this)]);
if (!empty($this->handlerInterface)) {
$result = $this->handlerFactory->create($this->handlerInterface)->persist($this);
if (!empty($result)) {
foreach ($result as $key => $value) {
$this->data[$key] = $value;
}
}
}
$this->eventManager->dispatchEvent(['persist_after'], [get_class($this)]);
}
Create the handler in the same directory where the interface is stored: <magento2_root_dir>/dev/tests/functional/tests/app/Magento/[module_name]/Test/Handler/[object_name]/[type_of_handler].php
di.xml
The di.xml
file declares relationship between the interface and the handler class.
The file must be placed in <magento2_root_dir>/dev/tests/functional/tests/app/Magento/[module_name]/Test/etc/[handler_type]
.
See an example for the Widget cURL handler (<magento2_root_dir>/dev/tests/functional/tests/app/Magento/Widget/Test/etc/curl/di.xml
):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?xml version="1.0" ?>
<!--
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
<preference for="Magento\Widget\Test\Handler\Widget\WidgetInterface" type="\Magento\Widget\Test\Handler\Widget\Curl" />
<type name="Magento\Widget\Test\Constraint\AssertProductInCatalogNewProductsList">
<arguments>
<argument name="severity" xsi:type="string">S2</argument>
</arguments>
</type>
</config>
In this example, the di.xml
file causes the Curl
class to replace the WidgetInterface
.
See the directory structure mentioned for the case with the Widget cURL handler:
How to create a cURL handler
Let’s create a cURL handler that creates a new widget.
- Create a directory with the name
Widget
in theHandler
directory of the Magento_Widget module -<magento2_root_dir>/dev/tests/functional/tests/app/Magento/Widget/Test/Handler/Widget
. -
In the same directory, create the interface for the cURL handler, and call the file
WidgetInterface.php
. Our new interface extendsHandlerInterface
class.1 2 3 4 5 6 7 8 9 10 11 12 13
<?php namespace Magento\Widget\Test\Handler\Widget; use Magento\Mtf\Handler\HandlerInterface; /** * Interface WidgetInterface */ interface WidgetInterface extends HandlerInterface { // }
-
Create
Curl.php
in the same directory. This file contains a handler class, which defines preparation of a data to create a new widget.The following code includes detailed comments for better understanding.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80
<?php namespace Magento\Widget\Test\Handler\Widget; use Magento\Mtf\Fixture\FixtureInterface; use Magento\Mtf\Handler\Curl as AbstractCurl; use Magento\Mtf\Util\Protocol\CurlTransport; use Magento\Mtf\Util\Protocol\CurlTransport\BackendDecorator; /** * Curl handler for creating widgetInstance/frontendApp. */ class Curl extends AbstractCurl { /** * Mapping values for data. * * @var array */ protected $mappingData = [ 'code' => [ 'CMS Page Link' => 'cms_page_link', ], 'block' => [ 'Main Content Area' => 'content', 'Sidebar Additional' => 'sidebar.additional', 'Sidebar Main' => 'sidebar.main', ] ]; /** * Post request for creating widget instance. * * @param FixtureInterface $fixture [optional] * @throws \Exception * @return null|array instance id */ public function persist(FixtureInterface $fixture = null) { // Prepare data to send it using cURL. $data = $this->prepareData($fixture); // Build url to send post request to create widget. $url = $_ENV['app_backend_url'] . 'admin/widget_instance/save/code/' . $data['code'] . '/theme_id/' . $data['theme_id']; // Create CurlTransport instance to operate with cURL. BackendDecorator is used to log in to Magento backend. $curl = new BackendDecorator(new CurlTransport(), $this->_configuration); // Send request to url with prepared data. $curl->write($url, $data); // Read response. $response = $curl->read(); // Close connection to server. $curl->close(); // Verify whether request has been successful (check if success message is present). if (!strpos($response, 'data-ui-id="messages-message-success"')) { throw new \Exception("Widget instance creation by curl handler was not successful! Response: $response"); } // Get id of created widget in order to use in other tests. $id = null; if (preg_match_all('/\/widget_instance\/edit\/instance_id\/(\d+)/', $response, $matches)) { $id = $matches[1][count($matches[1]) - 1]; } return ['id' => $id]; } /** * Prepare data to create widget. * * @param FixtureInterface $widget * @return array */ protected function prepareData(FixtureInterface $widget) { // Replace UI fixture values with values that are applicable for cURL. Property $mappingData is used. $data = $this->replaceMappingData($widget->getData()); // Perform data manipulations to prepare the cURL request based on input data. ... return $data; } // Additional methods. }
- Create
di.xml
in theetc/curl
directory of the Magento_Widget module.
1
2
3
4
5
6
7
8
9
10
11
<?xml version="1.0" ?>
<!--
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
<preference for="Magento\Widget\Test\Handler\Widget\WidgetInterface" type="Magento\Widget\Test\Handler\Widget\Curl" />
</config>
cURL authentication classes
In the previously mentioned example of the Curl.php code, authentication in the Admin is realized using the BackendDecorator
class.
The FrontendDecorator class manages authentication in the storefront.
BackendDecorator class
BackendDecorator
manages authentication in Admin and saves the Admin’s session.
Full class name is Mtf\Util\Protocol\CurlTransport\BackendDecorator
.
Add to the Curl.php
the following code:
1
$curl = new BackendDecorator(new CurlTransport(), new Config());
Config()
takes Admin’s configuration from config.xml, where the username and the password are stored.
FrontendDecorator class
FrontendDecorator
helps to authorize the customer and saves his session.
Full class name is Mtf\Util\Protocol\CurlTransport\FrontendDecorator
.
Use the following code in the Curl.php
file:
1
$curl = new FrontendDecorator(new CurlTransport(), $this->customer);
How to create a UI handler
Let’s create a UI handler that creates a new widget.
- Create a directory with the name
Widget
in theHandler
directory of the Magento_Widget module -<magento2_root_dir>/dev/tests/functional/tests/app/Magento/Widget/Test/Handler/Widget
. -
In the same directory, create interface for the UI handler, and call the file
WidgetInterface.php
. Our new interface extendsHandlerInterface
class.1 2 3 4 5 6 7 8 9 10 11 12 13
<?php namespace Magento\Widget\Test\Handler\Widget; use Magento\Mtf\Handler\HandlerInterface; /** * Interface WidgetInterface */ interface WidgetInterface extends HandlerInterface { // }
-
Create
Ui.php
in the same directory. This file contains a handler class, which defines preparation of a data to create a new widget.The code has detailed comments for better understanding.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80
<?php namespace Magento\Widget\Test\Handler\Widget; use Magento\Mtf\Fixture\FixtureInterface; use Magento\Mtf\Handler\Curl as AbstractCurl; use Magento\Mtf\Util\Protocol\CurlTransport; use Magento\Mtf\Util\Protocol\CurlTransport\BackendDecorator; /** * Curl handler for creating widgetInstance/frontendApp. */ class Curl extends AbstractCurl { /** * Mapping values for data. * * @var array */ protected $mappingData = [ 'code' => [ 'CMS Page Link' => 'cms_page_link', ], 'block' => [ 'Main Content Area' => 'content', 'Sidebar Additional' => 'sidebar.additional', 'Sidebar Main' => 'sidebar.main', ] ]; /** * Post request for creating widget instance. * * @param FixtureInterface $fixture [optional] * @throws \Exception * @return null|array instance id */ public function persist(FixtureInterface $fixture = null) { // Prepare data to send it using cURL. $data = $this->prepareData($fixture); // Build url to send post request to create widget. $url = $_ENV['app_backend_url'] . 'admin/widget_instance/save/code/' . $data['code'] . '/theme_id/' . $data['theme_id']; // Create CurlTransport instance to operate with cURL. BackendDecorator is used to log in to Magento backend. $curl = new BackendDecorator(new CurlTransport(), $this->_configuration); // Send request to url with prepared data. $curl->write($url, $data); // Read response. $response = $curl->read(); // Close connection to server. $curl->close(); // Verify whether request has been successful (check if success message is present). if (!strpos($response, 'data-ui-id="messages-message-success"')) { throw new \Exception("Widget instance creation by curl handler was not successful! Response: $response"); } // Get id of created widget in order to use in other tests. $id = null; if (preg_match_all('/\/widget_instance\/edit\/instance_id\/(\d+)/', $response, $matches)) { $id = $matches[1][count($matches[1]) - 1]; } return ['id' => $id]; } /** * Prepare data to create widget. * * @param FixtureInterface $widget * @return array */ protected function prepareData(FixtureInterface $widget) { // Replace UI fixture values with values that are applicable for cURL. Property $mappingData is used. $data = $this->replaceMappingData($widget->getData()); // Perform data manipulations to prepare the cURL request based on input data. ... return $data; } // Additional methods. }
-
Create
di.xml
in theetc/ui
directory of the Magento_Widget module.1 2 3 4 5 6 7
<?xml version="1.0" ?> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"> <preference for="Magento\Widget\Test\Handler\Widget\WidgetInterface" type="\Magento\Widget\Test\Handler\Widget\Ui" /> </config>
How to create a WebAPI handler
Let’s create a WebAPI handler that creates a new tax rule.
- Create a directory with the name
TaxRule
in theHandler
directory of the Magento_Tax module -<magento2_root_dir>/dev/tests/functional/tests/app/Magento/Tax/Test/Handler/TaxRule
. -
In the same directory, create interface for the WebAPI handler, and call the file
TaxRuleInterface.php
. Our new interface extendsHandlerInterface
class.1 2 3 4 5 6 7 8 9 10 11 12
namespace Magento\Tax\Test\Handler\TaxRule; use Magento\Mtf\Handler\HandlerInterface; /** * Interface TaxRuleInterface */ interface TaxRuleInterface extends HandlerInterface { // }
-
Create
Webapi.php
in the same directory. The file contains a handler class. In the following example WebAPI handler uses some cURL handler methods to prepare data.1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82
<?php namespace Magento\Tax\Test\Handler\TaxRule; use Magento\Tax\Test\Fixture\TaxRule; use Magento\Mtf\Config\DataInterface; use Magento\Mtf\Fixture\FixtureInterface; use Magento\Mtf\Util\Protocol\CurlTransport; use Magento\Mtf\Handler\Webapi as AbstractWebapi; use Magento\Mtf\System\Event\EventManagerInterface; use Magento\Mtf\Util\Protocol\CurlTransport\WebapiDecorator; /** * Create Tax Rule via Web API handler. */ class Webapi extends AbstractWebapi implements TaxRuleInterface { /** * Tax Rule cUrl handler. * * @var Curl */ protected $taxRuleCurl; /** * @constructor * @param DataInterface $configuration * @param EventManagerInterface $eventManager * @param WebapiDecorator $webapiTransport * @param Curl $taxRuleCurl */ public function __construct( DataInterface $configuration, EventManagerInterface $eventManager, WebapiDecorator $webapiTransport, Curl $taxRuleCurl ) { parent::__construct($configuration, $eventManager, $webapiTransport); $this->taxRuleCurl = $taxRuleCurl; } /** * Web API request for creating Tax Rule. * * @param FixtureInterface $fixture * @return array * @throws \Exception */ public function persist(FixtureInterface $fixture = null) { /** @var TaxRule $fixture */ $data = $this->prepareData($fixture); $url = $_ENV['app_frontend_url'] . 'rest/V1/taxRules'; $this->webapiTransport->write($url, $data); $response = json_decode($this->webapiTransport->read(), true); $this->webapiTransport->close(); if (empty($response['id'])) { $this->eventManager->dispatchEvent(['webapi_failed'], [$response]); throw new \Exception('Tax rule creation by Web API handler was not successful!'); } return ['id' => $response['id']]; } /** * Returns data for Web API params. * * @param TaxRule $fixture * @return array */ protected function prepareData(TaxRule $fixture) { $data = $fixture->getData(); $data = $this->taxRuleCurl->prepareFieldData($fixture, $data, 'tax_rate', 'tax_rate_ids'); $data = $this->taxRuleCurl->prepareFieldData($fixture, $data, 'tax_product_class', 'product_tax_class_ids'); $data = $this->taxRuleCurl->prepareFieldData($fixture, $data, 'tax_customer_class', 'customer_tax_class_ids'); return ['rule' => $data]; } }
-
Create
di.xml
in theetc/webapi
directory of the Magento_Tax module.1 2 3 4 5 6 7
<?xml version="1.0" ?> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"> <preference for="Magento\Tax\Test\Handler\TaxRule\TaxRuleInterface" type="\Magento\Tax\Test\Handler\TaxRule\Webapi" /> </config>