Microsoft Dynamics 365 Testing: Tools, Strategies, and Best Practices

Microsoft Dynamics 365 Testing: Tools, Strategies, and Best Practices

Microsoft Dynamics 365 is one of the most widely deployed enterprise platforms, spanning CRM (Customer Engagement), ERP (Finance and Operations), and a growing suite of industry-specific applications. Testing Dynamics 365 effectively requires understanding the significant technical differences between its two main application families and choosing the right tools for each.

This guide covers the complete D365 testing landscape: the EasyRepro framework for Customer Engagement, Power Apps Test Studio for model-driven apps, Selenium and Playwright for UI automation, integration testing with the Dataverse API, and how to tie it all together in Azure DevOps pipelines.

D365 CE vs. D365 F&O: Why They Need Different Testing Approaches

The most important thing to understand about Dynamics 365 testing is that "D365" encompasses two fundamentally different technical architectures:

Dynamics 365 Customer Engagement (CE) — includes Sales, Customer Service, Field Service, and Marketing. Built on the Power Platform / Dataverse. Modern web architecture, with React-based UI components. Extensible through Power Apps (model-driven apps), Power Automate flows, and plugins written in C#.

Dynamics 365 Finance and Operations (F&O) — includes Finance, Supply Chain Management, Human Resources, and Commerce. A separate, more complex application stack based on .NET and X++ (Microsoft's ERP-specific language). Different URL structure, different rendering engine, different automation challenges.

These two families share the "Dynamics 365" brand but are technically quite different. Test automation approaches, tools, and maintenance strategies differ significantly between them.

EasyRepro: The Official C# Testing Framework for D365 CE

EasyRepro is Microsoft's open-source test automation framework for Dynamics 365 Customer Engagement. Built on top of Selenium WebDriver, it provides a fluent API for interacting with D365 CE UI elements without writing raw CSS selectors or XPath for every interaction.

Why EasyRepro Exists

Dynamics 365 CE generates element IDs dynamically, and the HTML structure changes between releases. Microsoft's solution was to create EasyRepro — a maintained library that knows the current DOM structure of D365 CE and provides stable, named methods for interacting with forms, lists, subgrids, and dialogs. When D365 CE updates change the DOM structure, Microsoft updates EasyRepro.

Getting Started with EasyRepro

Add EasyRepro to your .NET test project via NuGet:

dotnet add package Microsoft.Dynamics365.UIAutomation.Api
dotnet add package Microsoft.Dynamics365.UIAutomation.Browser

A basic EasyRepro test that creates a D365 contact:

[TestClass]
public class ContactTests
{
    private XrmApp _xrmApp;
    private BrowserOptions _options;

    [TestInitialize]
    public void Setup()
    {
        _options = new BrowserOptions
        {
            BrowserType = BrowserType.Chromium,
            PrivateMode = true,
            FireEvents = true
        };
        _xrmApp = new XrmApp(_options);
    }

    [TestMethod]
    public void CreateContact_WithValidData_ShouldSaveSuccessfully()
    {
        // Navigate to D365 CE
        _xrmApp.OnlineLogin.Login(
            new Uri("https://yourorg.crm.dynamics.com"),
            "testuser@yourorg.onmicrosoft.com",
            "SecurePassword123!"
        );

        // Navigate to Contacts
        _xrmApp.Navigation.OpenApp(UCIAppName.Sales);
        _xrmApp.Navigation.OpenSubArea("Sales", "Contacts");

        // Create new contact
        _xrmApp.CommandBar.ClickCommand("New");

        // Fill in form fields
        _xrmApp.Entity.SetValue("firstname", "Jane");
        _xrmApp.Entity.SetValue("lastname", $"TestContact_{DateTime.Now:yyyyMMddHHmmss}");
        _xrmApp.Entity.SetValue("emailaddress1", "jane.test@example.com");
        _xrmApp.Entity.SetValue("mobilephone", "555-0199");

        // Set lookup field
        _xrmApp.Entity.SetValue(new LookupItem
        {
            Name = "parentcustomerid",
            Value = "Acme Corporation"
        });

        // Save
        _xrmApp.Entity.Save();

        // Assert record was saved
        var header = _xrmApp.Entity.GetHeaderTitle();
        Assert.IsTrue(header.Contains("Jane"), $"Header should contain name: {header}");
    }

    [TestCleanup]
    public void Cleanup()
    {
        _xrmApp?.Dispose();
    }
}

EasyRepro Coverage: What It Handles Well

EasyRepro provides specialized methods for D365-specific UI patterns:

Form interactions:

  • SetValue / GetValue for standard fields
  • SetValue overloads for lookup fields, option sets, date fields, and two-option fields
  • Save, SaveAndClose, Cancel for form lifecycle

Grid interactions:

  • OpenRecord — open a record from a list
  • Search — search the grid
  • SortColumn — sort by column
  • SelectAllRecords — bulk selection

Business process flows:

  • SelectStage — navigate BPF stages
  • SetActive — set stage as active
  • GetActiveStageName — assert current stage

Subgrids:

  • AddRelatedRecord — add related record via subgrid
  • GetSubGridItems — retrieve subgrid contents for assertion

Power Apps Test Studio

Power Apps Test Studio is Microsoft's low-code testing tool embedded in the Power Apps maker portal. It's designed for model-driven and canvas apps built on the Power Platform.

When to Use Test Studio vs. EasyRepro

Use Test Studio when:

  • Testing canvas apps (EasyRepro only supports model-driven apps)
  • QA analysts without coding backgrounds need to write and maintain tests
  • Tests need to be stored and run from within the Power Platform
  • You're testing Power Apps specifically rather than the full D365 CE experience

Use EasyRepro when:

  • Testing full D365 CE functionality (sales processes, service cases, etc.)
  • You need advanced test logic, data-driven tests, or complex assertions
  • Tests need to integrate with external CI/CD systems
  • The team is comfortable with C# test code

Creating Tests in Power Apps Test Studio

In the Power Apps maker portal, open your app and select Test from the top menu. The Test Studio interface shows:

  • Test suites: Groups of related test cases
  • Test cases: Individual test scenarios
  • Test steps: Recorded or manually authored actions

Test Studio uses a formula-based syntax similar to Power Fx:

// Navigate to a specific screen
Navigate(AccountDetailScreen);

// Assert a label shows the expected value
Assert(AccountNameLabel.Text = "Acme Corporation", "Account name should match");

// Set an input field value
Set(varSearchTerm, "Jane Smith");
SearchBox.Text = varSearchTerm;

// Assert a gallery has items
Assert(CountRows(ContactGallery.AllItems) > 0, "Gallery should have contacts");

Test Studio can record interactions and generate steps automatically, but recorded tests often need manual cleanup — particularly for dynamic content and conditional logic.

Selenium and Playwright for D365 F&O

Dynamics 365 Finance and Operations uses a different UI architecture than CE. D365 F&O is a .NET application with a browser-rendered UI that uses a custom component framework. Standard Selenium and Playwright work with F&O, but require D365-specific selector strategies.

Stable Selectors for D365 F&O

D365 F&O generates some dynamic IDs but also provides stable data attributes that can anchor your tests:

# D365 F&O uses data-dyn-controlname attributes for form controls
VENDOR_ACCOUNT_INPUT = '[data-dyn-controlname="VendAccount_AccountNum"] input'
INVOICE_DATE_INPUT = '[data-dyn-controlname="TransDate"] input'
INVOICE_AMOUNT_INPUT = '[data-dyn-controlname="AmountMST"] input'

# Tab names are stable
LINES_TAB = '[data-tab-name="Lines"]'

A Playwright test for D365 F&O vendor invoice entry:

import { test, expect } from '@playwright/test';

test('Create vendor invoice in AP module', async ({ page }) => {
    // Navigate to D365 F&O
    await page.goto('https://yourenv.operations.dynamics.com');

    // Wait for D365 shell to load
    await page.waitForSelector('.NavBar', { timeout: 30000 });

    // Navigate to AP > Invoices > Pending vendor invoices
    await page.click('[data-dyn-role="MenuItem"][title="Accounts payable"]');
    await page.click('[data-dyn-role="MenuItem"][title="Invoices"]');
    await page.click('[data-dyn-role="MenuItem"][title="Pending vendor invoices"]');

    // Click New
    await page.click('[data-dyn-controlname="SystemDefinedNewButton"]');
    await page.waitForSelector('[data-dyn-controlname="VendAccount_AccountNum"]');

    // Fill in invoice details
    await page.fill('[data-dyn-controlname="VendAccount_AccountNum"] input', 'US-001');
    await page.keyboard.press('Tab'); // Trigger lookup resolve
    await page.waitForTimeout(1000); // Wait for async validation

    await page.fill('[data-dyn-controlname="Num"] input', `ATF-INV-${Date.now()}`);
    await page.fill('[data-dyn-controlname="TransDate"] input', '1/15/2026');

    // Click Lines tab
    await page.click('[data-tab-name="Lines"]');

    // Add invoice line
    await page.click('[data-dyn-controlname="AddLine"]');
    await page.fill('[data-dyn-controlname="LedgerDimensionAccount"] input', '200150');
    await page.fill('[data-dyn-controlname="AmountMST"] input', '1500.00');

    // Save
    await page.keyboard.press('F12'); // D365 save shortcut

    // Assert saved successfully
    const saveIndicator = page.locator('.dyn-form-status-bar.saved');
    await expect(saveIndicator).toBeVisible({ timeout: 10000 });
});

D365 F&O Task Recorder as a Baseline

D365 Finance and Operations includes a built-in Task Recorder that captures user actions and exports them as developer recordings (XML) or Lifecycle Services task guides. These recordings can be converted to Selenium test scripts as a starting point, which is faster than writing tests from scratch.

In Lifecycle Services (LCS), the Task Recorder integration with the Business Process Modeler (BPM) library lets you organize recordings by business process hierarchy and generate test scripts for Regression Suite Automation Tool (RSAT).

RSAT: Regression Suite Automation Tool

RSAT is Microsoft's official test automation tool for D365 Finance and Operations. It works by:

  1. Playing back Task Recorder recordings against a D365 F&O instance
  2. Validating expected values using Excel parameter files
  3. Integrating with Azure DevOps Test Plans for test management

RSAT doesn't require any coding — testers record processes with Task Recorder, export recordings to LCS, and RSAT plays them back. The Excel parameter files control test data, making data-driven testing accessible without code.

RSAT Workflow:
1. Business analyst records process with Task Recorder
2. Recording uploaded to LCS Business Process Modeler
3. RSAT downloads recording and generates Excel parameter file
4. Test data entered in Excel (one row per test case)
5. RSAT executes tests against D365 F&O environment
6. Results reported to Azure DevOps Test Plans

RSAT is the recommended approach for D365 F&O UI regression testing in Microsoft's own guidance. Its main limitation is that it's Windows-only and requires RSAT client installation on each tester's machine.

Integration Testing: Dataverse API and OData

For D365 CE and Power Platform, the Dataverse Web API provides comprehensive access to all application data. Integration tests that operate through the API are faster and more reliable than UI tests, making them the right choice for validating business logic.

import requests
from msal import ConfidentialClientApplication

class DataverseAPI:
    def __init__(self, org_url, tenant_id, client_id, client_secret):
        self.org_url = org_url
        app = ConfidentialClientApplication(
            client_id,
            authority=f"https://login.microsoftonline.com/{tenant_id}",
            client_credential=client_secret
        )
        token = app.acquire_token_for_client(
            scopes=[f"{org_url}/.default"]
        )
        self.headers = {
            "Authorization": f"Bearer {token['access_token']}",
            "OData-MaxVersion": "4.0",
            "OData-Version": "4.0",
            "Content-Type": "application/json"
        }

    def create_contact(self, first_name, last_name, email):
        payload = {
            "firstname": first_name,
            "lastname": last_name,
            "emailaddress1": email
        }
        response = requests.post(
            f"{self.org_url}/api/data/v9.2/contacts",
            json=payload,
            headers=self.headers
        )
        response.raise_for_status()
        # Extract created record ID from Location header
        location = response.headers.get("OData-EntityId", "")
        contact_id = location.split("(")[1].rstrip(")")
        return contact_id

    def get_contact(self, contact_id):
        response = requests.get(
            f"{self.org_url}/api/data/v9.2/contacts({contact_id})",
            headers=self.headers
        )
        response.raise_for_status()
        return response.json()

    def delete_contact(self, contact_id):
        requests.delete(
            f"{self.org_url}/api/data/v9.2/contacts({contact_id})",
            headers=self.headers
        )

Azure DevOps Integration

Dynamics 365 environments are typically deployed and managed through Azure DevOps, making it the natural CI/CD platform for D365 testing.

D365 CE/Power Platform Pipeline

# azure-pipelines.yml — D365 CE regression testing
trigger:
  - main

pool:
  vmImage: 'windows-latest'

steps:
  - task: NuGetToolInstaller@1

  - task: NuGetCommand@2
    inputs:
      command: 'restore'
      restoreSolution: '**/*.sln'

  - task: VSBuild@1
    inputs:
      solution: '**/*.sln'
      configuration: 'Release'

  - task: VSTest@2
    displayName: 'Run EasyRepro D365 Tests'
    inputs:
      testSelector: 'testAssemblies'
      testAssemblyVer2: '**\*Tests*.dll'
      searchFolder: '$(System.DefaultWorkingDirectory)'
      testRunTitle: 'D365 CE Regression'
      runSettingsFile: '$(Build.SourcesDirectory)/test.runsettings'
    env:
      D365_URL: $(D365_TEST_URL)
      D365_USERNAME: $(D365_TEST_USER)
      D365_PASSWORD: $(D365_TEST_PASSWORD)

  - task: PublishTestResults@2
    condition: always()
    inputs:
      testResultsFormat: 'VSTest'
      testResultsFiles: '**/*.trx'

D365 F&O Pipeline with RSAT

For Finance and Operations, integrate RSAT execution into Azure DevOps using the RSAT command-line interface:

- script: |
    cd "C:\Program Files (x86)\Regression Suite Automation Tool"
    .\Microsoft.Dynamics.RegressionSuite.ConsoleApp.exe playback ^
      /testplanid $(TEST_PLAN_ID) ^
      /environment $(D365_FO_URL) ^
      /outputpath $(Build.ArtifactStagingDirectory)\rsat-results
  displayName: 'Run RSAT Tests'

- task: PublishTestResults@2
  inputs:
    testResultsFormat: 'JUnit'
    testResultsFiles: '$(Build.ArtifactStagingDirectory)/rsat-results/*.xml'

Monitoring D365 in Production

Post-deployment testing shouldn't end when the pipeline passes. D365 integrations, scheduled workflows, and background processes can fail silently in production — Power Automate flows reach their rate limit, Dataverse plugins throw unhandled exceptions, or scheduled batch jobs time out.

Setting up continuous monitoring for critical D365 processes ensures failures surface quickly rather than being discovered by affected users. Cloud-based testing platforms like HelpMeTest can run automated checks against your D365 production instance on a schedule — verifying that key user journeys (creating a sales order, submitting an expense report, processing an invoice) work end-to-end, 24/7. With a $100/month Pro plan, even smaller D365 deployments can afford continuous monitoring that catches regressions before they impact business.

Best Practices Summary

Separate CE and F&O test suites. Don't try to use the same tools and approaches for both D365 CE and F&O. Their architectures are different enough to warrant distinct testing strategies.

API-first for business logic. Every test scenario that can be validated through the Dataverse API or F&O OData endpoint should be. Reserve UI tests for end-to-end user journey validation.

Keep EasyRepro up to date. Microsoft updates EasyRepro regularly to keep pace with D365 CE changes. Running an outdated version of EasyRepro against a current D365 CE instance is a common source of test failures.

Use RSAT for F&O regression. Microsoft's own tool is the most maintainable path for D365 F&O UI regression. Invest in building a comprehensive BPM library in LCS before the team's task recorder knowledge walks out the door.

Test customizations separately from core. D365's configuration and customization layer — plugins, workflows, Power Automate flows, custom entities — should have dedicated test coverage separate from core functionality tests. Configuration-level defects are common and easy to miss if you only test end-to-end scenarios.

Microsoft Dynamics 365 testing is maturing as an ecosystem. The combination of EasyRepro, Power Apps Test Studio, RSAT, and Azure DevOps gives D365 teams a complete toolkit for building test coverage across the full application landscape.

Read more