Merk
Tilgang til denne siden krever autorisasjon. Du kan prøve å logge på eller endre kataloger.
Tilgang til denne siden krever autorisasjon. Du kan prøve å endre kataloger.
Canvas apps run inside an iframe within the Power Apps player. This guide explains how to launch a canvas app, scope your selectors to the correct frame, and interact with controls by using data-control-name attributes.
How canvas app testing works
When a canvas app loads in play mode, the runtime hosts the app inside an iframe:
iframe[name="fullscreen-app-host"]
All controls inside the app have a data-control-name attribute that matches the control name you set in Power Apps Studio. Gallery items have data-control-part="gallery-item".
You scope all locators to this frame before interacting with controls:
const canvasFrame = page.frameLocator('iframe[name="fullscreen-app-host"]');
Launch a canvas app
Use AppProvider to launch the app and get the CanvasAppPage object:
import { test, expect } from '@playwright/test';
import {
AppProvider,
AppType,
AppLaunchMode,
buildCanvasAppUrlFromEnv,
} from 'power-platform-playwright-toolkit';
const CANVAS_APP_URL = buildCanvasAppUrlFromEnv();
test.beforeEach(async ({ page, context }) => {
const app = new AppProvider(page, context);
await app.launch({
app: 'Northwind Orders',
type: AppType.Canvas,
mode: AppLaunchMode.Play,
skipMakerPortal: true, // bypasses Power Apps navigation
directUrl: CANVAS_APP_URL,
});
});
Tip
Setting skipMakerPortal: true and providing a directUrl saves 10–20 seconds per test by bypassing Power Apps navigation.
Wait for the app to load
After launch, wait for a known control to appear before interacting:
const canvasFrame = page.frameLocator('iframe[name="fullscreen-app-host"]');
// Wait for gallery to confirm the app is loaded and data is present
await canvasFrame
.locator('[data-control-name="Gallery1"] [data-control-part="gallery-item"]')
.first()
.waitFor({ state: 'visible', timeout: 60000 });
Note
Canvas apps backed by Dataverse can take 30–60 seconds to load data into a gallery. Use a 60-second timeout for gallery selectors.
Interact with controls
The following examples show how to interact with common canvas app controls using frame-scoped locators.
Click a button
Locate a button by its data-control-name attribute, wait for it to be visible, and then click it.
const addButton = canvasFrame.locator('[data-control-name="icon3"]');
await addButton.waitFor({ state: 'visible', timeout: 10000 });
await addButton.click();
Fill a text input
Use the fill() method to set the value of a text input, targeting it by its aria-label.
const orderNumberInput = canvasFrame.locator('input[aria-label="Order Number"]');
await orderNumberInput.fill('ORD-12345');
Select a gallery item
Filter gallery items by their displayed text content to find and click a specific record.
const galleryItem = canvasFrame
.locator('[data-control-part="gallery-item"]')
.filter({ has: canvasFrame.locator('[data-control-name="Title1"]').getByText('Order 001') });
await galleryItem.waitFor({ state: 'visible' });
await galleryItem.click();
Count gallery items
Use the count() method to verify that the gallery contains the expected number of items.
const galleryItems = canvasFrame.locator('[data-control-name="Gallery1"] [data-control-part="gallery-item"]');
const count = await galleryItems.count();
expect(count).toBeGreaterThan(0);
Create a page object for your canvas app
For maintainability, encapsulate selectors and actions in a Page Object class:
// pages/my-app/MyCanvasAppPage.ts
import { Page, FrameLocator } from '@playwright/test';
export class MyCanvasAppPage {
private readonly frame: FrameLocator;
constructor(private readonly page: Page) {
this.frame = page.frameLocator('iframe[name="fullscreen-app-host"]');
}
get addButton() {
return this.frame.locator('[data-control-name="AddButton"]');
}
get gallery() {
return this.frame.locator('[data-control-name="Gallery1"]');
}
async waitForLoad(): Promise<void> {
await this.gallery
.locator('[data-control-part="gallery-item"]')
.first()
.waitFor({ state: 'visible', timeout: 60000 });
}
async clickAdd(): Promise<void> {
await this.addButton.click();
}
async getItemCount(): Promise<number> {
return this.gallery.locator('[data-control-part="gallery-item"]').count();
}
}
Full CRUD test example for canvas apps
This example combines app launch, gallery verification, and form interaction into a complete test suite.
import { test, expect, FrameLocator } from '@playwright/test';
import { AppProvider, AppType, AppLaunchMode, buildCanvasAppUrlFromEnv } from 'power-platform-playwright-toolkit';
const CANVAS_APP_URL = buildCanvasAppUrlFromEnv();
test.describe('Canvas App - Orders', () => {
let canvasFrame: FrameLocator;
test.beforeEach(async ({ page, context }) => {
const app = new AppProvider(page, context);
await app.launch({
app: 'Orders App',
type: AppType.Canvas,
mode: AppLaunchMode.Play,
skipMakerPortal: true,
directUrl: CANVAS_APP_URL,
});
canvasFrame = page.frameLocator('iframe[name="fullscreen-app-host"]');
await canvasFrame
.locator('[data-control-part="gallery-item"]')
.first()
.waitFor({ state: 'visible', timeout: 60000 });
});
test('should display orders in gallery', async () => {
const count = await canvasFrame
.locator('[data-control-part="gallery-item"]')
.count();
expect(count).toBeGreaterThan(0);
});
test('should click Add and show form', async ({ page }) => {
await canvasFrame.locator('[data-control-name="icon3"]').click();
await page.waitForTimeout(2000);
const input = canvasFrame.locator('input[type="text"]').first();
await expect(input).toBeVisible();
});
});
Discover control names in canvas apps
To find the data-control-name values in your app:
- Open the app in play mode in a browser.
- Open browser developer tools (F12).
- Use the Inspector to hover over controls and look for
data-control-nameattributes.
Alternatively, use the Playwright MCP server to ask an AI assistant to inspect the DOM and generate selectors for you. See AI-assisted testing.