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.BrowserA 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/GetValuefor standard fieldsSetValueoverloads for lookup fields, option sets, date fields, and two-option fieldsSave,SaveAndClose,Cancelfor form lifecycle
Grid interactions:
OpenRecord— open a record from a listSearch— search the gridSortColumn— sort by columnSelectAllRecords— bulk selection
Business process flows:
SelectStage— navigate BPF stagesSetActive— set stage as activeGetActiveStageName— assert current stage
Subgrids:
AddRelatedRecord— add related record via subgridGetSubGridItems— 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:
- Playing back Task Recorder recordings against a D365 F&O instance
- Validating expected values using Excel parameter files
- 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 PlansRSAT 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.