power platform

Get Started with Playwright for Model Driven Apps

james

james

Share this post:
Get Started with Playwright for Model Driven Apps

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/test framework (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.

About the Author

james

james

Technical Consultant