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.
No comments yet