Skip to content
Kevin Schroeder edited this page Dec 8, 2015 · 1 revision

Magium Magento

A PHPUnit/Webdriver (Selenium (there for SEO)) based test framework for streamlining browser testing with Magento. It basically boils down to building out reusable Web Driver commands for Magento allowing you to bypass much of the boilerplate code that browser/functional testing may require. This is done through utility classes.

(Getting Started is at towards the end of this document, Tutorials are on another Github project.)

The baseline classes are built to the Magento 1.9 CE theme and sample data. Refactoring may occur if it is necessary to make the code work with multiple themes. If you want to contribute (please do!!) please make sure that your contributions work with, at least, Magento 1.9 CE with sample data.

The codebase as a whole uses dependency injection and a dependency injection container as part of a PHPUnit test. Yes, that's correct. The tests use DI and a DIC. Why would I commit such a horrible crime against the software development gods? Well, because this is basically a glorified batch processing system. But there are features in a DiC that allow you to make a lot of customizations. Say you want to build a test that adds to the cart, but there is some kind of popup that is non-standard. Instead of building out all of this functionality yourself just extend the AddToCart action class, override the appropriate method and set a type preference in the di.php configuration file. Does another site do something different? Just extend AddToCart with a different class and re-use everything else.

Another benefit of using a DIC is that it allows you to easily modify test steps prior to their being executed. All objects are shared objects in the DIC and so if you wanted to use a generated email address, for example, you could do something like this.

class TestEmailAddress extends AbstractMagentoTestCase
{

    public function testGuestCheckout()
    {
        // Other stuff

        $customer = $this->get('Magium\Magento\Identities\CustomerIdentity');
        $customer->setEmailAddress('[email protected]');

        $guestCheckout = $this->get('Magium\Magento\Actions\Checkout\GuestCheckout');
        $guestCheckout->execute();
    }

}

Using this code the guest checkout will then use that generated email address instead of the default "[email protected]". This could be used to test the email that is sent out by having it sent to your GMail account.

Is this the best, most purest way of doing this? I don't know. But I was tasked with building out some very complex Selenium tests a while back and this is what I wish I had.

Why are you using PHPUnit?

Isn't PHPUnit for testing units of code? Isn't this done in blatant disregard to pure software development? Yes! But for several reasons.

  • You probably already know much of PHPUnit's syntax
  • PHPUnit is most likely already integrated with your existing IDE, meaning you can run tests from directly in there. It is also already integrated with a number of other development and testing tools.
  • It's mature, with many different options for customizing its behavior. There is some hard coded functionality (which is also a reason why I had to go with a DiC), but most of what you need is either already there or easily installed.
  • A Selenium test needs assertions. Was the product actually added to the cart? Was I actually logged in, etc.

Basic Concepts

The goal of Magium is to minimize boilerplate and maximize re-use. So if a pattern doesn't quite make sense try to apply it to those two conditions and hopefully it will be a little more clear.

There are several base concepts that you need to be familiar with

  • Commands - These tend to be basic WebDriver commands, such as open(). There aren't many of them.
  • Actions - These are sets of commands that do strings of things to get to a particular goal. One goal could be logging in. Another goal could be adding a product to the cart. Another could be checking out
  • Assertions - There are two types of assertions
    • Static assertions - These are the kinds of assertions that you find in your test case itself. assertEquals(), assertTrue(), etc.
    • Programmable assertions - These are more complicated assertions that may be programmable
  • Identities - Used to manage users, such as a customer or admin user.
  • Navigators - These classes will navigate to some place on the web site, but will not do any actions. They are intended to be read-only, in other words. Navigating to a product page. Navigate to a customer login page. Navigate to and admin System Configuration panel. And so on.
  • Themes - These are possibly mis-named. They are intended to contain configuration information about your specific Magento instance. So, if your customization has a different add-to-cart button you would either override these classes or provide configuration for internal use.
  • Extractors - These are classes that can be inserted in some execution flow to extract data from the page. Useful for assertions or for gathering data for further command execution.

As a matter of standardization Magium will use Xpath exclusively for internal functionality when the functionality is configurable So if you intend to modify how Magium selects elements you will need to use Xpath to select elements. If the item is not configurable (such as the selector IDs for payment methods) then whatever gives the most exact match will be used.

However, in your own tests, I would consider it a best practice to use the XML id as a first resort, CSS selectors second, and Xpath third. But I also expect that the use cases for CSS selectors will actually be a sliver of a window. They have the specificity of ID-based, but its a query, not an identifier, and so it is quite possible to fat-finger something in a way that leaves you wonder. CSS selectorss also have the ability for DOM traversal like Xpath, but its functionality significantly limited, particularly if you are looking for a specific element among many. As such, my personal preference, based off of my own experience, is to use XML IDs first, and then just use Xpath. Once you are comfortable with basic Xpath you will find that its wealth of functionality is incredibly useful.

Configuration

The system is intended to use as much configuration, instead of class overriding, as possible. Class overrides should really only be used for non-standard customizations.

Not all classes can be configured. But if they do they need to extend AbstractConfigurableElement. The configuration for the object should be placed in configuration/Namespace/Class.php. That file will be include()d during object instantialization and so any customizations should be done programmatically on $this. See example_configuration_not_used for examples on how this looks. Hint: it's not that hard.

But there is more configuration that can be done. As noted earlier, the entire implementation is dependent on dependency injection. So if there are any customized classes that the action classes (for example) need to use instead of the base ones you can configure the DIC to prefer one implementation over another in the configuration/di.php class. An example can be seen in example_configuration_not_used/di.php where the Stream log writer is set to be preferred over the standard Noop writer.

Basic Usage

Each test case class should extend Magium\AbstractTestCase. This class contains bootstrapping to load up WebDriver, configure the DIC and do a few other setup things. It also contains some additional helper functionality such as simplified element retrieval, Selenium level assertions (assertElementExists()) among other things.

Building a test case is quite simple.

class TestHomePageTitle extends Magium\Magento\AbstractMagentoTestCase
{
    public function testTitleExists()
    {
        $this->commandOpen($this->getTheme()->getBaseUrl());
        $this->assertElementExists('//title', self::BY_XPATH);
    }

}

Negative tests can be run too.

class TestHomePageTitle extends Magium\Magento\AbstractMagentoTestCase
{

    public function testBadTitleNotExists()
    {
        $this->commandOpen($this->getTheme()->getBaseUrl());
        // You shouldn't have a title that equals "boogers".  That's just rude.
        $this->assertElementNotExists('//title[.="boogers"]', self::BY_XPATH);
    }

}

Getting Started

The first thing you will need is the Selenium Server. You can get it from http://www.seleniumhq.org/download/. All tests are run using the Remote WebDriver functionality. The purpose of that is simply consistency across as many use cases as possible. So if you're using this for local development or Selenium grid, the goal is to re-use as much as possible. That means using the remote Web Driver implementation.

Out of the box Magium uses the Chrome Driver webdriver so you will need to download that from https://sites.google.com/a/chromium.org/chromedriver/downloads. Why not Firefox? Who actually uses Firefox for anything other than Selenium test building or building JMeter scripts? Nobody. That's right. Nobody.

So to get everything running you will need to download the Selenium Server jar and the Chrome Driver executable and put them in the same directory. Once you have done that you need to execute

java -Dwebdriver.chrome.driver=chromedriver.exe -jar selenium-server-standalone-2.48.2.jar

Once Selenium Server is up and running you will need to install Magium for your system. There will be more tutorials and some videos to help go through some of Magium's features as time goes on.

To require Magium execute

php composer.phar require magium/magium

Then build a test. The easiest test to get running is navigating your website.

namespace Tests\Magento\Navigation;

use Magium\Magento\AbstractMagentoTestCase;

class BaseNavigationTest extends AbstractMagentoTestCase
{

    public function testNavigateToJewelry()
    {

        $this->commandOpen('http://localhost/');
        $this->getNavigator()->navigateTo('Accessories/Jewelry');
        $this->assertPageHasText('Blue Horizons Bracelets');
    }
}

This test navigates to the Accessories / Jewelry page and asserts that a certain product is on that page.

But in truth, this test is not quite right. The goal is to make as absolutely much as possible configurable and, thus, repeatable. Following is an example.

namespace Tests\Magento\Navigation;

use Magium\Magento\AbstractMagentoTestCase;

class BaseNavigationTest extends AbstractMagentoTestCase
{

    public function testNavigateToJewelry()
    {
        $theme = $this->getTheme();
        $this->commandOpen($theme->getBaseUrl());
        $this->getNavigator()->navigateTo($theme->getNavigationPathToProductCategory());
        $this->assertPageHasText('Blue Horizons Bracelets');
    }
}

Anything repeatable, such as the base URL or the navigation to a standard category is intended to be configurable.

But how would you make that configuration work? If a class extends AbstractConfigurableElement (such as the theming class), you can place a file in the /configuration directory that corresponds to the name of the class being requested.

For example, the name of the basic theme class is Magium\Magento\Themes\ThemeConfiguration. So what you would do is create a file called /configuration/Magium/Themes/ThemeConfiguration.php with the following code (to change the base URL).

<?php

$this->baseUrl                      = 'http://magento19.loc/';

Many more details on this to come.

Rules for test utility classes

  • All calls will presume positive results. For example, it will presume that if you are intending to add something to the cart, you are intending to actually have it added to the cart. If you wish to test negative results you will need to build out that functionality in code yourself
    • But that said there are some classes, such as the checkout classes, which are intended to be integrated with or have their execution modified programmatically.
  • All calls will presume a pre-existing web page that is opened. They will expect you to "set the stage" for the functionality you intend to call. For example, if you want to navigate to a particular category page, the test is going to presume that the page it is on has that functionality on it.
  • Whenever possible (hopefully 100% of the time) the DI container in the test case itself should be used to retrieve test objects. Some commonly used functionality, such as opening a web page, will have pre-existent convenience methods defined.