GuideCanvas Apps

Testing Canvas Apps

This guide covers testing Canvas Apps in detail, including control interactions, navigation, and best practices.

Note: Canvas Apps are low-code applications with a visual interface. Each control has a unique name that you’ll use in your tests.

Canvas App Architecture

Canvas Apps are built with controls (buttons, text inputs, galleries, etc.) that have unique names. The toolkit provides methods to interact with these controls by name.

Basic Interactions

Launching a Canvas App

Tip: Always wait for the app to fully load before interacting with controls to avoid timing issues.

import { test } from '@playwright/test';
import { AppProvider } from 'playwright-power-platform-toolkit';
 
test('canvas app test', async ({ page }) => {
  const appProvider = new AppProvider(page);
  const canvasApp = await appProvider.launchApp({
    appUrl: 'https://apps.powerapps.com/play/...',
    appType: 'canvas'
  });
 
  await canvasApp.waitForAppToLoad();
});

Clicking Controls

// Click a button by its name
await canvasApp.clickControl('ButtonSubmit');
 
// Click with custom options
await canvasApp.clickControl('ButtonSave', { timeout: 5000 });

Working with Text Inputs

// Fill a text input
await canvasApp.fillTextInput('TextInputName', 'John Doe');
 
// Clear and fill
await canvasApp.fillTextInput('TextInputEmail', 'john@example.com', { clear: true });
 
// Get text input value
const name = await canvasApp.getTextInputValue('TextInputName');

Reading Control Text

// Get text from a label
const labelText = await canvasApp.getControlText('LabelWelcome');
 
// Get text from a button
const buttonText = await canvasApp.getControlText('ButtonSubmit');

Working with Screens

Canvas Apps are organized into screens. You can navigate between screens and verify which screen is active.

// Navigate to a specific screen
await canvasApp.navigateToScreen('DetailsScreen');
 
// Verify current screen
const isOnScreen = await canvasApp.isOnScreen('HomeScreen');
expect(isOnScreen).toBe(true);

Working with Galleries

Galleries display lists of items in Canvas Apps.

// Get gallery items
const items = await canvasApp.getGalleryItems('GalleryProducts');
 
// Click an item in a gallery
await canvasApp.clickGalleryItem('GalleryProducts', 2); // Click the 3rd item (0-indexed)
 
// Get gallery item count
const count = await canvasApp.getGalleryItemCount('GalleryProducts');

Custom Locators

For advanced scenarios, you can use custom locators:

import { getCanvasControlByName, getCanvasDataTestId } from 'playwright-power-platform-toolkit';
 
// Get a control by name
const button = page.locator(getCanvasControlByName('ButtonSubmit'));
 
// Get a control by data-testid (if you've added custom attributes)
const input = page.locator(getCanvasDataTestId('user-email'));
 
// Get a screen by name
const screen = page.locator(getCanvasScreenByName('HomeScreen'));

Best Practices

Important: Following these best practices will make your tests more reliable and maintainable.

1. Use Meaningful Control Names

When building Canvas Apps, use descriptive names for controls:

// Good
await canvasApp.clickControl('ButtonSubmitOrder');
await canvasApp.fillTextInput('TextInputCustomerEmail', email);
 
// Avoid
await canvasApp.clickControl('Button1');
await canvasApp.fillTextInput('TextInput3', email);

2. Wait for App Load

Always wait for the app to fully load before interacting:

const canvasApp = await appProvider.launchApp({
  appUrl: appUrl,
  appType: 'canvas'
});
 
// Wait for app to load
await canvasApp.waitForAppToLoad();
 
// Now safe to interact
await canvasApp.clickControl('ButtonStart');

3. Handle Dynamic Content

For controls that appear conditionally:

// Wait for a control to appear
await page.waitForSelector(getCanvasControlByName('ButtonNext'), {
  state: 'visible',
  timeout: 10000
});
 
// Check if a control exists before interacting
const isVisible = await canvasApp.isControlVisible('ButtonOptional');
if (isVisible) {
  await canvasApp.clickControl('ButtonOptional');
}

4. Use Data-Driven Tests

Create reusable test data:

const testCases = [
  { name: 'John Doe', email: 'john@example.com' },
  { name: 'Jane Smith', email: 'jane@example.com' },
];
 
for (const testCase of testCases) {
  test(`submit form for ${testCase.name}`, async ({ page }) => {
    // Test implementation
  });
}

Advanced Scenarios

Testing Complex Forms

test('complex form submission', async ({ page }) => {
  const appProvider = new AppProvider(page);
  const canvasApp = await appProvider.launchApp({
    appUrl: process.env.CANVAS_APP_URL!,
    appType: 'canvas'
  });
 
  await canvasApp.waitForAppToLoad();
 
  // Fill multiple fields
  await canvasApp.fillTextInput('TextInputFirstName', 'John');
  await canvasApp.fillTextInput('TextInputLastName', 'Doe');
  await canvasApp.fillTextInput('TextInputEmail', 'john.doe@example.com');
 
  // Select from dropdown
  await canvasApp.clickControl('DropdownCountry');
  await canvasApp.clickControl('DropdownCountryItem_USA');
 
  // Check checkbox
  await canvasApp.clickControl('CheckboxAgreeToTerms');
 
  // Submit
  await canvasApp.clickControl('ButtonSubmit');
 
  // Verify success message
  const successMessage = await canvasApp.getControlText('LabelSuccess');
  expect(successMessage).toContain('Successfully submitted');
});

Testing Navigation Flow

test('multi-screen navigation', async ({ page }) => {
  const appProvider = new AppProvider(page);
  const canvasApp = await appProvider.launchApp({
    appUrl: process.env.CANVAS_APP_URL!,
    appType: 'canvas'
  });
 
  await canvasApp.waitForAppToLoad();
 
  // Start on home screen
  expect(await canvasApp.isOnScreen('HomeScreen')).toBe(true);
 
  // Navigate to products
  await canvasApp.clickControl('ButtonViewProducts');
  await page.waitForTimeout(1000); // Wait for transition
  expect(await canvasApp.isOnScreen('ProductsScreen')).toBe(true);
 
  // Navigate to details
  await canvasApp.clickGalleryItem('GalleryProducts', 0);
  expect(await canvasApp.isOnScreen('DetailsScreen')).toBe(true);
 
  // Go back
  await canvasApp.clickControl('ButtonBack');
  expect(await canvasApp.isOnScreen('ProductsScreen')).toBe(true);
});

Debugging Tips

Taking Screenshots

// Take screenshot at specific point
await page.screenshot({ path: 'canvas-app-state.png' });
 
// Take screenshot on failure (automatic in Playwright config)

Logging

// Log control state
const text = await canvasApp.getControlText('LabelStatus');
console.log('Current status:', text);
 
// Log page URL
console.log('Current URL:', page.url());

Inspecting Controls

Use the browser DevTools to inspect Canvas App controls and understand their structure.

Next Steps