Acceptance Testing XIV - XPath Examples

A variety of instructive XPath examples


In the previous article, we created the page object to hold the CSS and XPath locators for our acceptance test. In this one, we'll take a closer look at those locators.


MODX logo


The Locators

In code below, examples of the underlying HTML will be followed by the CSS or XPath locator that we used to reference the HTML element we want Codeception to act on.

Keep in mind that for any given HTML element, there are usually a large number of XPath locators that would point to it, so the examples below are just one way of doing it.


Finding Locators

Before listing the code, let's review some of the methods and issues involved in using XPath locators.

As we said in a previous article, Chrome dev. tools can be really useful in developing locators. (Some other browsers have similar capabilities.)

In Chrome, you can launch dev. tools (Ctrl-shift-i or F12). If you click on the little square with the arrow sticking in it at the left side of the dev. tools menu, you can then click on the element in the page above you need a locator for.

That will switch you to the "Elements" tab of dev. tools (if you're not there already) and expose the HTML surrounding the element you clicked on.

You can then right-click on an HTML element in the "Elements" tab at the bottom of the screen and select "Copy -> Copy XPath" to put the XPath in the clipboard.

In MODX, getting the XPaths is particularly challenging because many of the id attributes are generated on the fly by Ext and can't be used safely. This means that the XPath you get with the "Copy XPath" method above is often unusable, though it can get you started on creating a proper XPath for your element.

In Chrome dev. tools, you can press Ctrl-f while the on the "Elements" tab to open the "Find" input at the bottom of the screen. You can paste any CSS or XPath locator there and see if it works. It's important that the search only shows one result. Several simpler versions of the locators in the code below had to be made more complicated because the first versions pointed to multiple (sometimes hidden) elements, which made the acceptance test fail.


The Code

For each example below, a bit of HTML code containing the element be want to act on is followed by a CSS or XPath locator that will uniquely identify that element.

The first two are simple CSS locators. The rest are XPaths.

<?php

class ResourceTestPage
{

    /* <li id="limenu-admin" class="top"> */
    public static $systemMenu = '#limenu-admin';

    /* <li id="acls"> */
    public static $acl_option = '#acls';

    /* <span unselectable="on"
             id="extdd-21">PrivateUsers (7)</span> */
    public static $privateUsersGroup =
        "//span[starts-with(text(),'PrivateUsers')]";

    /* <button type="button" id="ext-gen102"
           class=" x-btn-text">
            Update User Group</button> */
    public static $updateUserGroupOption =
        "//button[contains(text(),
        'Update User Group')]";

    /* <span class="x-tab-strip-text ">Permissions</span> */
    public static $permissionsTab =
        "//span[starts-with(@class,'x-tab-strip-text')
         and text()='Permissions']";

    /* <span class="x-tab-strip-text ">
        Resource Group Access</span> */
    public static $resourceGroupAccessTab =
        "//span[contains(text(),'Resource Group Access')]";

    /* <button type="button" id="ext-gen342"
        class=" x-btn-text">Add Resource Group
        </button> */
    public static $addResourceGroupButton =
        "//button[contains(text(),'Add Resource Group')]";

    /* <input type="text" size="24" autocomplete="off"
        id="modx-crgactxext-gen377-resource-group"
        class=" x-form-text x-form-field modx-combo
        x-trigger-noedit x-form-focus" readonly=""
        style="width: 310px;">*/
    public static $resourceGroupInput =
        "//input[starts-with(@id,'modx-crgact')
        and contains(@id, 'resource-group')]";

    /* <div class="x-grid3-cell-inner
        x-grid3-col-1">PrivateResources</div> */
    public static $privateResourcesOption =
        "//div[text()='PrivateResources']";

    /* <input type="text" size="24" autocomplete="off"
        id="modx-crgactxext-gen377-context"
        class=" x-form-text x-form-field modx-combo
        x-trigger-noedit" readonly=""
        style="width: 310px;"> */
    public static $contextInput =
        "//input[starts-with(@id,'modx-crgact')
        and contains(@id, '-context')]";

    /* <span style="font-style: italic;
    font-size: small;">(mgr)</span> */
    public static $mgrOption =
        "//span[text()='(mgr)']";

    /* <input type="text" size="24" autocomplete="off"
        id="modx-crgactxext-gen377-authority"
        class="x-form-text x-form-field modx-combo
        x-trigger-noedit x-form-focus" title=""
        readonly="" style="width: 310px;"> */
    public static $authorityInput =
        "//input[starts-with(@id,'modx-crgact')
        and contains(@id, 'authority')]";

    /* <div class="x-combo-list-item
        x-combo-selected">TestUser - 15
       </div> */
    public static $testUserOption =
        "//div[text()='TestUser - 15'
        and contains(@class,'x-combo-list-item')]";

    /* <div class="x-form-element"
        id="x-form-el-modx-crgactxext-gen377-policy"
        style="padding-left:0;"> */
    public static $policyInput =
        "//*[starts-with(@id,'x-form-el-modx-crg')
        and contains(@id,'policy')]";

    /* <div class="x-combo-list-item">
            Resource
        </div> */
    public static $resourceOption =
        "//div[text()='Resource'
        and contains(@class,'x-combo-list-item')]";

    /* <span id="ext-comp-1244" class="x-btn
        x-btn-small x-btn-icon-small-left
        primary-button x-btn-noicon"
        unselectable="on" style="width: 45px;">
            <em class=""><button type="button"
             id="ext-gen398" class=" x-btn-text">
                Save</button></em></span>> */
    public static $addResourcePanelSaveButton =
        "//span[starts-with(@id,'ext-comp')]/em/button[text()='Save']";
}

A Closer Look

Many of the examples above are self-explanatory, but let's look at a few of them more closely.

/* <div class="x-combo-list-item
        x-combo-selected">TestUser - 15
</div> */
public static $testUserOption =
"//div[text()='TestUser - 15'
and contains(@class,'x-combo-list-item')]"

You'd think we could just use //div[text()='TestUser - 15'] but surprisingly, that locator actually matches three separate elements, and ours is not the first one, so the test fails.

Ours is the only one with that class name, so adding and contains(@class,'x-combo-list-item') finds only the one we want.

In the locator above, we could have used @class='x-combo-list-item' since that's the only class, but the test would break if another class name was added to the element, which often happens. Using contains() here makes the test more robust.

Notice that contains(a,b), like some of the other function-like expressions in an XPath, uses b and the search string and looks for it in whatever a specifies.

In our example above (contains(@class,'x-combo-list-item')), the @ is short for "attribute," so the process will look for x-combo-list-item in the class attribute of the element.

/* <span style="font-style: italic;
    font-size: small;">(mgr)</span> */
public static $mgrOption =
"//span[text()='(mgr)']";

This case was relatively easy, since it's the only span tag containing the string "(mgr)." There may be other instances of "mgr," but none of them are in parentheses. The text() function gets the inner text of the HTML element.

/*
<span id="ext-comp-1244" class="x-btn x-btn-small x-btn-icon-small-left primary-button x-btn-noicon" unselectable="on" style="width: 45px;"><em class=""><button type="button" id="ext-gen398" class=" x-btn-text">Save</button></em></span> */
public static $addResourcePanelSaveButton =
"//span[starts-with(@id,'ext-comp')]/em/button[text()='Save']";

This was a tricky one. You might think that starting off the XPath with //button[starts-with(@id,'ext-gen') would work, since we want the button, but that actually has 60 matches!

There are lots of save buttons in the MODX Manager, all of them have "Save" as the text and most of them are in button tags. There are also many span elements that have an id that starts with ext-comp.

The XPath above looks for a span where the id starts with ext-comp, then for an immediate child of the span that is an em element with an immediate child that's a button with "Save" as its inner text. It's a little surprising that there's only one element that matches this.

You might think that starting off the XPath with //button[starts-with(@id,'ext-gen') would work, but that actually has 60 matches!

Notice that starts-with() works just like contains(). In our example, it matches when the id attribute starts with ext-comp. The ends-with() function works the same way. Don't forget that both contain a hyphen in the name.


Greater Specificity

We could have made our locators more specific, and possibly a little faster, by starting our XPath expression with a reference to a containing element with a usable id attribute. like this:

"//div[@id='SomeId']//span[text()='(mgr)']"

The XPath above looks for a div with the id attribute set to 'SomeId and then under that div for a span where the inner text is "(mgr)." The trouble is that in MODX, the element with the usable id attribute may be a long way above the element we want to specify. It can often be faster to just write a very specific locator than to look through the code for a parent with a usable id.


Coming Up

Now that we have the code to create all the objects and have developed locators for all of them, it's time to create a skeleton version of our test. We'll do that in the next article.



For more information on how to use MODX to create a web site, see my web site Bob's Guides, or better yet, buy my book: MODX: The Official Guide.

Looking for high-quality, MODX-friendly hosting? As of May 2016, Bob's Guides is hosted at A2 hosting. (More information in the box below.)



Comments (0)


Please login to comment.

  (Login)