CRM UI Testing using Dalek

When it comes to UI testing there are plenty of options available. Some of the popular ones that I have encountered are

  • PhantomJS
  • CasperJS
  • Nightwatch
  • Selenium
  • Visual Studio Coded UI

Each of these frameworks have their own sets of benefits/drawbacks. I have been trying out DalekJS for the past few weeks, and I am really impressed by the ease of setup. For someone who is not really that much of a commandline/config json person, I really like not having to spend hours setting up config files and typing up commands in the shell.

Prerequisites

  1. Install node – Head to https://nodejs.org/ and install the correct edition of node for your machine
  2. Have a look at DalekJS Getting Started page at http://dalekjs.com/pages/getStarted.html. This basically involves installing the dalek CLI and dalek driver for Chrome browser. These are the commands you’ll have to type
    1. npm install dalek-cli -g
    2. npm init (This command will create the package.json. Straightforward questions to be answered in a wizard driven setup)
    3. npm install dalekjs –save-dev (This will update the package.json and add dalek as a dev dependency)
    4. npm install dalek-browser-chrome –save-dev (By default dalek uses PhantomJS which is a headless browser. If you want run your tests in Chrome, install this driver)

I ran some tests in PhantomJS and had issues, so I prefer to test this in Chrome.

Code
test.js

module.exports = (function () {
    var configJSON = require('./config');                                                                                                              
    try {
        return {
            'Login' : function (test) {
                test
                .open(configJSON.baseUrl)
                .type('#cred_userid_inputtext', configJSON.userName)
                .type('#cred_password_inputtext', configJSON.password + '\ue007\ue007')
                .wait(1000)
                .type('#cred_password_inputtext', '\ue007')
                .wait(5000)
                .toFrame('#InlineDialog_Iframe')
                .click('#butBegin')
                .done();
                test.screenshot('results/Login.png');
            },
            'Contacts' : function (test) {
                test.open(configJSON.baseUrl + '/main.aspx?etc=2&id='+configJSON.contactid+'&pagetype=entityrecord')
                .toFrame('iframe[title="Content Area"]')
                .execute(function () {
                        this.assert.ok(Xrm.Page.getAttribute('fullname').getValue() === 'Max Power', 'Name is Max Power');
                    })
                .done();
                test.screenshot('results/ContactName.png');
            }
        };
    }
    catch (err) {
        console.log(err);
    }
})();

config.js

{ "baseUrl": "http://abc123.crm6.dynamics.com",
"userName": "admin@abc123.onmicrosoft.com",
"password": "pass@word1",
"contactid": "{1005D93B-A22E-E511-80E2-C4346BC5B290}"
}

Here is how the project look in VS Code

Test code in VS Code

I am running the tests in a CRMOnline org, hence I have included Login as a test and the username and password is retrieved from the config.js file. This test is required, as the subsequent tests require the user be logged into CRMOnline. If you are running the OnPrem with integrated authentication, this test is not required.

When it comes to testing the form behaviour, you can approach this two ways: by looking the the DOM elements or using CRM Client API to confirm what should have happened, has happened. For eg. if a field is supposed to be hidden after a picklist option is changed to a certain value, you can either confirm this either by

  1. Accessing the css display property of that field elements in the DOM
  2. Using Xrm.Page.getControl(..).getVisible().

If you are heading the option 2 way, that means that you’ll have to execute a script against that page to validate the test. This is precisely what the execute method does. I like this approach, as I don’t have to fiddle with fieldname_c and fieldname_d divs, access innerText or anything related to the DOM. All the test results are captured as screenshots, so that we can visually confirm the results.

Running the Tests

Inorder to run the tests, you simply type dalek test.js -b chrome, where test.js is the file containing your tests. Executing this command will open the Chrome browser and start executing the tests. If you leave out “-b chrome” part the tests will be run in PhantomJS. I was not able to complete the tests using PhantomJS, as I had some issues with the toFrame part.

Here is how the test result screen looks like.

TestResults

DalekJS is well documented. You can head over to http://dalekjs.com/pages/documentation.html and have a look at the options available. This is one more tool you at your disposal to test your client side code for CRM.

Webdriver spec is under consideration by W3C. Once this is standardised and implemented by all major browsers, UI Testing should get lot easier. Below are the links you can look into, if you are interested in the WebDriver specs and the implementation status (Thank you @jacobrossi for tweeting this).

  1. http://www.w3.org/TR/2013/WD-webdriver-20130117/
  2. http://dev.modern.ie/platform/status/webdriver/details/
  3. https://developer.mozilla.org/en-US/docs/Mozilla/QA/Marionette/WebDriver/status

EDIT (20/08/15): Updated script to switch to correct IFrame before using Xrm.Page.getAttribute, instead of using hacky frames[0] and frames[1]. I also added additional content about WebDriver spec.

Advertisements

Bookmarklet: Open view in new window

Drag the below bookmarklet to the favorites bar in your browser, to quickly open the current view being displayed, in a new window. You can then probably bookmark the view Url, to assist with your daily housekeeping tasks. The current way the get the view url is Email a Link -> Of Current view from the command bar and then copy the url from the resulting Outlook window.

ViewCommandbar

This bookmarket will save you that 3 seconds to do that.

javascript:if(frames&&frames[0].$P_CRM&&frames[0].$P_CRM('#crmGrid')){var grid=frames[0].$P_CRM('#crmGrid')[0].control;if(grid){window.open(frames[0].Xrm.Page.context.getClientUrl()+'/main.aspx?etc='+grid.GetParameter('otc')+'&pagetype=entitylist&viewid='+grid.GetParameter('viewid')+'&viewtype='+grid.GetParameter('viewtype'),'_blank');}else{alert('Unable to find grid');}}else{alert('Unable to find grid');} void 0;

The source for this is as below

if (frames && frames[0].$P_CRM && frames[0].$P_CRM('#crmGrid')) {
	var grid = frames[0].$P_CRM('#crmGrid')[0].control;
	if (grid) {
		window.open(frames[0].Xrm.Page.context.getClientUrl() + '/main.aspx?etc=' + grid.GetParameter('otc') + '&pagetype=entitylist&viewid=' + grid.GetParameter('viewid') + '&viewtype=' + grid.GetParameter('viewtype'), '_blank');
	} else {
		alert('Unable to find grid');
	}
} else {
	alert('Unable to find grid');
}

CRM2015 Update 1 – Grid methods

CRM2015 Update 1 has finally introduced the capability to manipulate/access grids in a supported way. The API methods are documented in https://msdn.microsoft.com/en-us/library/dn932126.aspx. However not all methods are documented. These are the additional methods that you can use, but not documented in msdn. The assumption is the subgrid is displaying the contact entity records.

Object GridRow Collection
Method get
Sample Usage
Xrm.Page
.getControl("GridControlName")
.getGrid()
.getRows()
.get(0)
Description Gets the row at the specified index in the grid
Object  GridRow Collection
Method  getByFilter
Sample Usage
Xrm.Page
.getControl("GridControlName")
.getGrid()
.getRows()
.getByFilter(function(r){
return r.getData()
.getEntity()
.getAttributes()
.getByName('fullname')
.getValue() === 'Max Power';
})
Description  Returns only the rows that have fullname attribute = Max Power
Object  GridRow Collection
Method  getFirst
Sample Usage
Xrm.Page
.getControl("GridControlName")
.getGrid().getRows()
.getByFilter(function(r){
return r.getData()
.getEntity()
.getAttributes()
.getByName('fullname')
.getValue() === 'Max Power';
})
Description  Returns only the 1st row that has fullname attribute = Max Power
Object  GridRow
Method  getKey
Sample Usage
Xrm.Page.getControl("GridControlName")
.getGrid()
.getRows()
.get(0)
.getKey()
Description  Return the primary key of the 1st row
Object  GridRow
Method  getId
Sample Usage
Xrm.Page
.getControl("GridControlName")
.getGrid()
.getRows()
.get(0)
.getData()
.getEntity()
.getId()
Description  Return the primary key of the 1st row
Object  Attribute
Method  getValue
Sample Usage
Xrm.Page.getControl("GridControlName")
.getGrid()
.getRows()
.get(0)
.getData()
.getEntity()
.getAttributes()
.getByName("fullname")
.getValue()
Description  Gets the value for the fullname attribute from the first row in the grid. If this attribute is not a grid column, this method will error as getByName will return null
Object  Grid
Method  openAssociatedGrid
Sample Usage
Xrm.Page.getControl("GridControlName")
.openAssociatedGrid()
Description  Opens the associated view, if it is in the navigation pane