Get Started with Playwright for Model Driven Apps
Table of Contents
What You’ll Learn Today
Today we’re learning browser automation, not testing. We’ll understand:
- Browser lifecycle management
- JavaScript execution bridge between Node.js and browser
- Model Driven App integration basics
- Why testing frameworks exist
In the next lesson we’ll start to learn testing with @playwright/test.
Understanding Playwright’s Architecture
Your Computer (Node.js) Browser (Chrome/Edge/Firefox)
┌─────────────────┐ ┌─────────────────┐
│ │ │ │
│ Your Script │ Controls │ Web Page │
│ (TypeScript) ├────────────►│ (MDA) │
│ │ │ │
│ playwright │ │ window.Xrm │
│ - browser │ │ DOM │
│ - page │ │ JavaScript │
│ │ │ │
└─────────────────┘ └─────────────────┘
Node.js Browser
Key Insight: Your test code runs in Node.js, but page.evaluate() lets you run code inside the browser.
The Code
// day1-concepts.ts
import { chromium, Browser, Page, BrowserContext } from 'playwright';
declare global {
interface Window {
Xrm: any;
}
}
/**
* Core Playwright Concepts with Model Driven Apps
*/
// Your MDA URL - MUST include /main.aspx to load the actual app
const MDA_URL = 'https://YOUR-ORG.crm.dynamics.com/main.aspx?appid=YOUR-APP-ID';
async function learnPlaywright() {
let browser: Browser | null = null;
let context: BrowserContext | null = null;
let page: Page | null = null;
try {
// 1. BROWSER LIFECYCLE: Launch → Context → Page
browser = await chromium.launch({
headless: false,
slowMo: 100,
});
context = await browser.newContext({
viewport: { width: 1920, height: 1080 }
});
page = await context.newPage();
// 2. NAVIGATION
await page.goto(D365_URL);
console.log('Navigated to:', page.url());
// 3. WAITING for JavaScript conditions
console.log('Please login manually...');
await page.waitForFunction(
() => typeof window.Xrm !== 'undefined',
{ timeout: 120000 }
);
console.log('Login successful - Xrm found');
// 4. EXECUTING JAVASCRIPT - the bridge between Node.js and browser
const pageInfo = await page.evaluate(() => {
return {
title: document.title,
url: window.location.href,
hasXrm: typeof window.Xrm !== 'undefined'
};
});
console.log('Page info:', pageInfo);
// 5. INTERACTING WITH MDA
const userInfo = await page.evaluate(() => {
const xrm = window.Xrm;
const context = xrm.Utility.getGlobalContext();
return {
userName: context.userSettings.userName,
orgName: context.organizationSettings.uniqueName,
};
});
console.log('User:', userInfo.userName);
console.log('Org:', userInfo.orgName);
// Navigate using Xrm Web API
console.log('Opening Account form...');
await page.evaluate(() => {
return window.Xrm.Navigation.openForm({
entityName: "account",
useQuickCreateForm: false
});
});
// Wait for form to load
await page.waitForFunction(
() => window.Xrm?.Page?.data !== undefined,
{ timeout: 10000 }
);
// Check what form we're on
const formInfo = await page.evaluate(() => {
const xrm = window.Xrm;
return {
entityName: xrm.Page.data.entity.getEntityName(),
formType: xrm.Page.ui.getFormType(),
};
});
console.log('Opened form:', formInfo);
// 6. SCREENSHOT
await page.screenshot({ path: 'day1-screenshot.png' });
console.log('Screenshot saved');
} finally {
// CLEANUP: Always close resources in reverse order
if (page) await page.close();
if (context) await context.close();
if (browser) await browser.close();
}
}
// Run it
learnPlaywright()
.then(() => console.log('✅ Complete!'))
.catch(console.error);
Core Concepts Explained
1. Browser Lifecycle
browser → context → page → close()
- Browser: The actual browser process (Chrome, Firefox, etc.)
- Context: Isolated browser session (cookies, storage)
- Page: A tab within the context
2. The page.evaluate() Bridge
// Your Node.js code
const result = await page.evaluate(() => {
// This runs in the browser
return window.someValue;
});
This is THE most important concept in Playwright!
3. Waiting Strategies
- ❌
waitForTimeout()- Brittle, avoid - ✅
waitForSelector()- Better, waits for elements - ✅
waitForFunction()- Best, waits for any condition
4. Everything is Async
await page.goto(); // Async
await page.click(); // Async
await page.evaluate(); // Async
Always use await with Playwright methods!
5. Model Driven App Integration
A Model Driven App that is launched in a browser is just website with a window.Xrm JavaScript object. Once typeof window.Xrm !== 'undefined', you can use the Xrm Web API through Playwright.
What We’re Missing
- Assertions: We’re just logging, not testing
- Organization: Everything in one function
- Reusability: Copy-paste for multiple scenarios
- Standards: No structure or patterns
Setup & Run
npm init -y
npm install playwright typescript ts-node @types/node
npx playwright install
Update the D365 URL with your organization:
const D365_URL = 'https://YOUR-ORG.crm.dynamics.com/main.aspx?appid=YOUR-APP-ID';
Run the script:
npx ts-node day1-concepts.ts
Next Step: Real Testing
The next post will introduce:
@playwright/testframework (automated browser management)expect()assertions (real testing)- Page Object Model (organization)
- AAA pattern (structure)
- Standards compliance (professional approach)
Key Takeaway
A Model Driven App that is launched in a browser is just website. Playwright is just Node.js controlling a browser. Everything else is patterns and frameworks!
You now understand what browser automation is. Next up we’ll learn how to test efficiently.