Puppeteer and Dynamics 365

Puppeteer is a Node API to drive Headless Chrome. I have used Selenium and DalekJS in the past to do some UI testing. I have been experimenting/learning puppeteer for the past few weeks and have found it to relatively easy to learn and use. It is still on alpha though and so there are some bugs.

In my sample repo (https://github.com/rajyraman/Puppeteer-Dynamics-365), I demonstrate:

  1. How to use puppeteer to login to ADFS OnPrem CRM
  2. How to use puppeteer to take full page screenshot
  3. Annotate the screenshot using imagemagick

I envision this repo to provide documentation assistance by capturing and annotating screenshots. Below are the steps to run this project:

    1. After cloning the github repo run the following command to download the npm packages: yarn
    2. Install imagemagick from https://www.imagemagick.org/script/download.php#windows
    3. Confirm that the path to magick.exe exists in PATHImagemagick path.png
    4. Create a new .env file in the root of the repo. Below is the .env file that I used: OnPrem
      env onprem
      Onlineenv online.png
    1. Change the USER_SELECTOR, PASSWORD_SELECTOR, LOGIN_SUBMIT_SELECTOR if they are different. These were the ids in the OnPrem ADFS login page
    2. Check the runsheet.csv file provided in the repo and change it to suit your screenshot requirements. The run sheet specifies the sequence of clicks. In this file, on line 2, I am specifying that I should first click Workspace group and then Clients subgroup. The screenshot should be annotated with text “Clients list”. On line 3, I am specifying that the “NEW” button should be clicked and the screenshot should be annotated as “New client form” and the file name should be “New Client Form.png”. The command bar clicks are always specified in a new line with blank group and subgroup.run sheet.png
    3. Run the node application using “node index.js”Run application.png

 

The screenshots will be captured with headless Chrome and annotated using imagemagick. Here is a sample screenshot:

Administration-Annotated.png

Possible future improvements:

  1. Build the exe using pkg and distribute the exe, .env and runsheet.csv. Building the exe using pkg requires a copy of the puppeteer folder from node_modules along side the exe
  2. Navigate to a record based on id
  3. Run workflow/dialogs
  4. Populate new entity form with data before command bar button click
  5. Automatically scroll if group is outside of viewport

Please submit your feedback/ideas/criticism on the comments area or as a issue in the repo.

Advertisements

Plugin Integration Tests using FakeXrmEasy

XrmUnitTest and FakeXrmEasy are two testing frameworks that are specifically targeted towards Dynamics CRM/Dynamics 365. It is possible to do both unit test as well as integration tests using both these frameworks. In this first post, I would like to cover FakeXrmEasy. Here is a sample plugin code that I would like to test:

using Microsoft.Xrm.Sdk;
using System;
using Microsoft.Crm.Sdk.Messages;

namespace SamplePlugin
{
    public class LastNameUpperCasePlugin : IPlugin
    {
        public void Execute(IServiceProvider serviceProvider)
        {
            ITracingService tracer = (ITracingService)serviceProvider.GetService(typeof(ITracingService));
            IPluginExecutionContext context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
            IOrganizationServiceFactory factory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
            IOrganizationService service = factory.CreateOrganizationService(context.UserId);

            try
            {
                Entity entity = (Entity)context.InputParameters["Target"];
                //not required. Just added to demonstrate that we are connecting to a real crm org service
                var response = service.Execute(new WhoAmIRequest());

                var lastName = entity.GetAttributeValue<string>("lastname");
                if (!string.IsNullOrEmpty(lastName))
                {
                    entity["lastname"] = lastName.ToUpper();
                }
            }
            catch (Exception e)
            {
                throw new InvalidPluginExecutionException(e.Message);
            }
        }
    }
}

In order to test this plugin, we have to create a unit test project. Here are the steps for it:

  1. Create a unit test  project from the existing plugin solution by right clicking and choose Add->New Project->Visual C#->Test->Unit Test Project
  2. Use nuget to add the correct FakeXrmEasy reference to the test project. In this example I am using “FakeXrmEasy.365” as I am connecting to a Dynamics 365 instance online. Refer https://www.nuget.org/profiles/jmontana to find out all the different versions that are relevant to your CRM version.
  3. Create a App.config file, if it doesn’t already exist. Here is my connection string. Refer https://msdn.microsoft.com/en-us/library/mt608573.aspx to find the correct format of the connection string for you crm instance.connection-string

Below is my test for the plugin

using System;
using System.Collections.Generic;
using System.Web.Configuration;
using FakeXrmEasy;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Microsoft.Xrm.Sdk;

namespace SamplePlugin.Test
{
	[TestClass]
	public class LastNamePluginUnitTests
	{
		[TestMethod]
		public void Should_Set_LastName_In_UpperCase()
		{
			var context = new XrmRealContext
			{
				ProxyTypesAssembly = typeof(LastNameUpperCasePlugin).Assembly,
				ConnectionStringName = "CRMOnline"
			};
			var executionContext = context.GetDefaultPluginContext();
			var target = new Entity("contact")
			{
				["lastname"] = "Power",
				Id = Guid.NewGuid()
			};
			executionContext.MessageName = "Create";
			executionContext.Stage = 20;
			executionContext.PrimaryEntityId = target.Id;
			executionContext.PrimaryEntityName = target.LogicalName;
			executionContext.InputParameters = new ParameterCollection
			{
				new KeyValuePair<string, object>("Target", target)
			};
			context.ExecutePluginWith<LastNameUpperCasePlugin>(executionContext);
			Assert.AreEqual("POWER",
				((Entity) executionContext.InputParameters["Target"]).GetAttributeValue<string>("lastname"));
		}
	}
}

In this test I am creating a scenario where the plugin is running pre-create. You can change to add pre/post entity images or target a different message and stage. If we debug our integration test, we can see that we are connected to the real org service.

Debugger.png

I have used latebound entity in this example, but you can use the same code with early bound entities generated using crmsvcutil or Early Bound Generator.

You can use a similar approach for testing custom workflow assemblies as well. In that case you would be using “GetDefaultWorkflowContext” instead of “GetDefaultPluginContext” to get the execution context.

Footnote: Technically this test could have been better written as an unit test instead of a integration test. But the purpose of this post is to demonstrate the points below:

  1. How to build a fake plugin execution context
  2. How to use connection strings to create a real OrganizationService instance
  3. How to use the real OrganizationService with the fake plugin execution context

EDIT (22/11/2016): Thank you Jonas (@rappen) for pointing out the plugin stage should be pre-create for this to work. Updated context stage to 20.

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.