Automated Testing for Canvas Apps with Playwright
How to set up and use the official Microsoft Power Platform Playwright toolkit for end-to-end testing of Canvas Apps at CMS, including GCC environment configuration, certificate auth, and Canvas-specific flakiness helpers.
#Overview
The Microsoft Power Platform Playwright Samples repo provides an official toolkit and sample tests for end-to-end testing of Canvas Apps, Model-Driven Apps, and Custom Pages. The core package — power-platform-playwright-toolkit — solves the hardest Canvas-specific automation problems and is the required starting point for any new Canvas App test project at CMS.
Use this guide to set up the toolkit in the CMS GCC environment. The upstream README covers general setup; this doc covers GCC-specific differences.
#Why Canvas Apps need special helpers
Standard Playwright operations silently fail inside Canvas Apps because the Power Fx engine runs in a sandboxed iframe and manages its own DOM:
| Problem | Symptom | Toolkit fix |
|---|---|---|
fill() bypasses Power Fx |
Input updates DOM but OnChange never fires |
fillCanvasInput(page, locator, value) — types via keyboard events |
| Virtualized galleries | Gallery rows outside the viewport are absent from DOM | scrollGalleryToItem(page, gallerySelector, itemLocator) — scrolls until item renders |
| Invisible overlays intercept clicks | Buttons appear enabled but clicks land on a hidden element | clickCanvasButton(locator) — retries until visible, uses force: true |
| Canvas engine initialization race | Controls briefly unresponsive after navigation | waitForCanvasReady(page, readinessSelector) — waits for a stable sentinel element |
| Custom confirm dialogs | Confirm() renders as browser dialog in GCC (see quick-fix-4) |
confirmCanvasDialog(page, options) — drives the custom overlay pattern |
All helpers take Locator objects (not string control names). Use CanvasAppRuntimePage.getCanvasFrame() to scope locators to the Canvas iframe. Never use bare fill(), click(), or waitForTimeout() on Canvas controls.
#GCC environment setup
The toolkit targets commercial Power Platform by default but is fully configurable for GCC. Set these values in your .env:
# GCC Maker Portal
POWER_APPS_BASE_URL=https://make.gov.powerapps.us
# GCC Canvas App play URL — use the full URL directly
CANVAS_APP_URL=https://apps.gov.powerapps.us/play/e/<env-id>/a/<app-id>?tenantId=<tenant-id>
# GCC Entra authentication endpoint
AUTH_ENDPOINT=https://login.microsoftonline.us
# Certificate auth — required for GCC CI (password auth blocked by Conditional Access)
MS_AUTH_CREDENTIAL_TYPE=certificate
MS_AUTH_CREDENTIAL_PROVIDER=local-file
MS_AUTH_LOCAL_FILE_PATH=./cert/test-service-principal.pfx
MS_AUTH_CERTIFICATE_PASSWORD=<vault-sourced>
Test account requirement: GCC Conditional Access blocks password-based auth for most accounts. Provision a dedicated test service principal with a certificate and exclude it from MFA Conditional Access policies — coordinate with the environment admin before starting a test project.
#Quick start
git clone https://github.com/microsoft/power-platform-playwright-samples.git
cd power-platform-playwright-samples
npm install && npm run build
cd packages/e2e-tests
cp .env.example .env
# Edit .env with GCC values above
npx playwright install msedge --with-deps
npm run auth:headful # authenticates and caches browser storage state (valid 24h)
npx playwright test --project=canvas-app
The Northwind sample tests target Microsoft's demo solution and won't run against CMS apps directly. Use them as patterns to copy into your own test project. canvas-app-crud.test.ts is the best starting template for a new app.
#Writing a test for a CMS Canvas App
Use CanvasAppRuntimePage for published apps running in Play mode. CanvasAppPage is for Power Apps Studio authoring only.
import { test, expect } from '@playwright/test';
import {
CanvasAppRuntimePage,
fillCanvasInput,
clickCanvasButton,
waitForCanvasReady,
scrollGalleryToItem,
} from 'power-platform-playwright-toolkit';
test('submit a new record and verify it appears in the gallery', async ({ page }) => {
await page.goto(process.env.CANVAS_APP_URL!);
const app = new CanvasAppRuntimePage(page);
const frame = app.getCanvasFrame(); // scoped to the Canvas iframe
// Wait for a stable element that signals the app is ready
await waitForCanvasReady(page, '[data-control-name="galSubmissions"]');
// Helpers take Locators — scope them through frame to cross the iframe boundary
await fillCanvasInput(page, frame.locator('[data-control-name="txtApplicantName"] input'), 'Jane Smith');
await fillCanvasInput(page, frame.locator('[data-control-name="txtDescription"] input'), 'Test submission');
await clickCanvasButton(frame.locator('[data-control-name="btnSubmit"] [role="button"]'));
// Gallery is virtualized — scroll until the new row enters the DOM
const row = frame.locator('[data-control-name="galSubmissions"] [data-row-key]').filter({ hasText: 'Jane Smith' });
await scrollGalleryToItem(page, '[data-control-name="galSubmissions"]', row);
await expect(row).toBeVisible();
});
#Adding Section 508 / accessibility tests
The toolkit's aria-label and data-control-name locator patterns are the correct foundation for accessibility assertions. Layer @axe-core/playwright on top to automate WCAG/508 checks:
npm install @axe-core/playwright --save-dev
import AxeBuilder from '@axe-core/playwright';
test('canvas app passes 508 scan on load', async ({ page }) => {
await page.goto(process.env.CANVAS_APP_URL!);
await waitForCanvasReady(page);
const results = await new AxeBuilder({ page })
.include('#canvas-app-iframe')
.withTags(['wcag2a', 'wcag2aa'])
.analyze();
expect(results.violations).toEqual([]);
});
Run axe scans after waitForCanvasReady() — scanning before the Canvas engine finishes initializing produces false positives from controls that aren't rendered yet.
#AI-assisted test generation
The repo ships .mcp.json wiring the Playwright MCP server (@playwright/mcp). Open the repo in Claude Code and it auto-connects. Prompt pattern:
"Write a Playwright test for the [App Name] Canvas App that creates a new record, verifies it appears in the gallery, and edits a field. Use the power-platform-playwright-toolkit helpers and our GCC
.envconfig."
Claude will use fillCanvasInput, scrollGalleryToItem, and waitForCanvasReady automatically when the toolkit is in scope.
#What's not covered by the toolkit
- Power Automate flows — no flow trigger/run helpers; test flow outcomes through Canvas App state changes
- 508 assertions — toolkit provides correct locators but not accessibility scanning; add
@axe-core/playwrightseparately (see above) - Model-Driven Apps — samples exist but CMS focus is Canvas Apps; MDA helpers are available if needed
- Performance testing — use Power Apps Monitor and Performance insights for Canvas performance; Playwright is for functional and 508 coverage