
In the previous blog, we tackled the hurdle of bypassing MFA using saved authentication states. While getting past the login screen is a victory, the real challenge in Dynamics 365 (D365) lies in the UI interaction with a few fields.
Unlike standard web forms, D365 relies heavily on asynchronous components. If your script doesn’t respect the interaction sequence of the CRM, your automation will be brittle and prone to false negatives.
In a recent project, we deployed a suite of 50 test cases to a CI/CD pipeline. Locally, everything passed. But in the cloud, nearly 30% failed. The culprit wasn’t the code logic; it was the Lookup fields.
The automation was “typing” data faster than the CRM’s background search could populate the suggestion menu. Because the script moved to the next step before the record was actually linked, the form was submitted with empty fields. This is a common pitfall: treating a complex CRM component like a simple text box. To fix this, we have to align our code with the component’s internal event triggers.
1. Handling Complex D365 Components
A. The Lookup Interaction Sequence
A Lookup field (like Account or Contact) is a multi-step process. You must click to focus, type to trigger the search event, and then select the result from the dynamically generated list explicitly.
Let us continue with the same example of creating a “Contact” through Playwright. Using Lookup and Option set fields. The handling of these fields is different as whenever you click on these fields the main form focus changes to the lookup panel or option set panel.
Use Case:
- Click on “Parent Customer” lookup field
- Change the focus to lookup panel
- Select the 1st value from the lookup list
TypeScript
const accountInput = page.locator('[data-id*="parentcustomerid"] input'); await accountInput.click(); // 1. Focus the elementawait accountInput.fill('Innovation'); // 2. Trigger the search event//Select the specific result from the floating menuawait page.locator('li[data-id*="parentcustomerid"]').first().click();
B. Interacting with Option Sets
Option Sets (Dropdowns) in D365 are often “lazy-loaded.” The options aren’t available in the DOM until the parent element is clicked. A direct .selectOption() call will usually fail here because the element is technically hidden.
Use Case:
- Click on “Preferred Contact Method” Option set field
- Change the focus to Option set panel
- Select the specific option set value.
TypeScript
// Example: Selecting Preferred Contact Methodawait page.locator('[data-id*="preferredcontactmethodcode"]').click();await page.getByRole('option', { name: 'Email' }).click();
2. Implementing a Video Audit Trail
When a test fails in a headless environment (like a GitHub Action or Azure DevOps), log files rarely tell the whole story. You need to see exactly what the UI looked like at the moment of failure.
When I was going through the video recordings, I noticed that the same video was getting overridden by next run recorded video. Hence it was not capturing the all versions of Video that recorded only latest could be seen. To enable the feature that let Playwright store the video recording evidences in a separate file for later tracking.
Make sure video capture node is available in Playwright “Config.ts” file.
TypeScript
use: {
video: 'on',
},
By default, Playwright may overwrite videos or delete them after a second run. Using an afterEach hook with a unique timestamp ensures you have a permanent audit trail for every execution.
TypeScript
import fs from 'fs';
import path from 'path';
test.afterEach(async ({ page }, testInfo) => {
const video = page.video();
if (video) {
// Format: Test_Name_YYYY-MM-DD.webm
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
const fileName = `${testInfo.title.replace(/\s+/g, '_')}_${timestamp}.webm`;
const historyFolder = './video-history';
if (!fs.existsSync(historyFolder)) {
fs.mkdirSync(historyFolder, { recursive: true });
}
const videoPath = path.join(historyFolder, fileName);
await video.saveAs(videoPath);
}
await page.close();
});
Using the above code existing Playwright uses existing build-in ‘fs’ (File System) Node.js module it checks if folder exist then create the file. After each test it runs get the video, add timestamps for each video to save separately. After test finishes you will find file created with unique filename and video saved separately.
Conclusion:
Enterprise-grade automation isn’t about how fast you can click buttons; it’s about how reliably you can handle the state of the application. By respecting the component sequence for Lookups and maintaining a strict video history, you reduce “flaky” tests and provide clear evidence when bugs actually occur.
FAQs
- How do you automate Microsoft Dynamics 365 UI using Playwright?
You can automate Dynamics 365 UI with Playwright by interacting with CRM elements using locators, handling asynchronous components properly, and following the correct interaction sequence for complex fields such as lookups and option sets. - Why do Playwright tests fail in CI/CD but pass locally in Dynamics 365?
Tests may fail in CI/CD environments because Dynamics 365 uses asynchronous UI components. If scripts run faster than the UI loads or background processes complete, the automation may miss elements or submit incomplete data. - How do you handle Lookup fields in Dynamics 365 using Playwright?
To handle lookup fields, the script should click the field, type the search value to trigger the lookup query, and explicitly select a result from the dynamically generated suggestion list. - How can you select Option Set values in Dynamics 365 automation?
Option Set values should be selected by first clicking the dropdown to load the options into the DOM and then selecting the desired option using Playwright locators.