Tester des pages personnalisées dans des applications basées sur des modèles

Les pages personnalisées sont des applications de canevas incorporées à l’intérieur d’une application pilotée par modèle. Ils s’affichent dans un iframe dans l’interpréteur de commandes d’application piloté par modèle. Pour les tester, vous devez accéder à l’application pilotée par le modèle, sélectionner la page personnalisée à partir du sitemap, puis définir toutes les interactions de contrôle sur l’iframe interne.

Fonctionnement des tests de page personnalisés dans les applications basées sur des modèles

Lorsqu’une page personnalisée se charge, l’interpréteur de commandes d’application piloté par modèle reste sur le domaine Dynamics 365. Le runtime de canevas de page personnalisé s'exécute à l'intérieur :

iframe[name="fullscreen-app-host"]

Il s’agit du même iframe utilisé par les applications de canevas autonomes. Une fois que vous avez le localisateur de cadre, tous les modèles de test d'application canvas s'appliquent.

  1. Lancez l’application pilotée par modèle à l’aide de AppProvider.
  2. Sélectionnez l’élément de page personnalisé dans le plan du site.
  3. Attendez que l'environnement d'exécution du canevas s’initialise.
import { test, expect } from '@playwright/test';
import { AppProvider, AppType, AppLaunchMode } from 'power-platform-playwright-toolkit';

const MODEL_DRIVEN_APP_URL = process.env.MODEL_DRIVEN_APP_URL!;
const CUSTOM_PAGE_NAME = process.env.CUSTOM_PAGE_NAME ?? 'AccountsCustomPage';

test.beforeAll(async ({ browser }) => {
  const context = await browser.newContext({ storageState: mdaStorageStatePath });
  const page = await context.newPage();

  const app = new AppProvider(page, context);
  await app.launch({
    app: 'My App',
    type: AppType.ModelDriven,
    mode: AppLaunchMode.Play,
    skipMakerPortal: true,
    directUrl: MODEL_DRIVEN_APP_URL,
  });

  // Navigate to the custom page via the sitemap
  const sidebarItem = page
    .locator(`[role="presentation"][title="${CUSTOM_PAGE_NAME}"]`)
    .first();
  await sidebarItem.waitFor({ state: 'visible', timeout: 30000 });
  await sidebarItem.click();
  await page.waitForTimeout(3000);
});

Interagir avec les contrôles de page personnalisés

Après avoir navigué vers la page personnalisée, définissez les localisateurs sur l’iframe de la zone de dessin.

const canvasFrame = page.frameLocator('iframe[name="fullscreen-app-host"]');

// Wait for gallery to appear
await canvasFrame
  .locator('[data-control-part="gallery-item"]')
  .first()
  .waitFor({ state: 'visible', timeout: 30000 });

Cliquer sur des boutons dans des contrôles de page personnalisés

Utilisez l’attribut data-control-name pour cibler des contrôles de bouton spécifiques dans l’iframe de canevas, puis recherchez l’élément interne [role="button"] pour déclencher une action de clic.

await canvasFrame.locator('[data-control-name="IconButton_Accept1"] [role="button"]').click();
await canvasFrame.locator('[data-control-name="IconButton_Edit1"] [role="button"]').click();

Remplir des champs de formulaire dans une page personnalisée

Recherchez les champs d’entrée par leur aria-label attribut dans l’iframe de canevas et utilisez la fill méthode pour entrer des valeurs.

const accountNameInput = canvasFrame.locator('input[aria-label="Account Name"]');
await accountNameInput.fill('Contoso Ltd');

Pour rechercher un élément spécifique dans une galerie, filtrez la liste des éléments de la galerie en faisant correspondre le contenu textuel d’un contrôle enfant tel que Title1.

const galleryItem = canvasFrame
  .locator('[role="listitem"][data-control-part="gallery-item"]')
  .filter({
    has: canvasFrame
      .locator('[data-control-name="Title1"]')
      .getByText('Contoso Ltd', { exact: true }),
  });

await galleryItem.waitFor({ state: 'visible', timeout: 30000 });

Rafraîchir la page personnalisée après un enregistrement

Lorsque vous enregistrez un nouvel enregistrement dans une page personnalisée soutenue par Dataverse, la galerie ne s’actualise pas automatiquement, sauf si vous déclenchez un rechargement complet. L’approche recommandée consiste à accéder à la racine de l'application et de revenir :

// Navigate to app root to force gallery refresh
await page.goto(MODEL_DRIVEN_APP_URL, { waitUntil: 'load', timeout: 60000 });
await page.locator('[role="menuitem"]').first().waitFor({ state: 'visible', timeout: 30000 });

// Navigate back to the custom page
const sidebarItem = page.locator(`[role="presentation"][title="${CUSTOM_PAGE_NAME}"]`).first();
await sidebarItem.waitFor({ state: 'visible', timeout: 30000 });
await sidebarItem.click();

// Wait for the new record to appear in the gallery
const specificItem = page
  .locator('[data-control-part="gallery-item"]')
  .filter({ has: page.locator('[data-control-name="Title1"]').getByText(accountName, { exact: true }) });
await specificItem.waitFor({ state: 'visible', timeout: 60000 });

Exemple de test complet : créer et vérifier un enregistrement

L’exemple suivant combine la navigation, l’entrée de formulaire, l’enregistrement et la vérification de la galerie dans un test de bout en bout unique qui crée un enregistrement de compte et confirme qu’il apparaît dans la galerie de pages personnalisées.

test('should create an account and verify it in the gallery', async ({ page }) => {
  const ACCOUNT_NAME = `Test Account ${Date.now()}`;
  const canvasFrame = page.frameLocator('iframe[name="fullscreen-app-host"]');

  // Click New record
  await page.locator('[title="New record"]').click();

  // Fill the form
  await canvasFrame.locator('input[aria-label="Account Name"]').fill(ACCOUNT_NAME);
  await canvasFrame.locator('input[aria-label="Main Phone"]').fill('555-1234');

  // Save
  await canvasFrame
    .locator('[data-control-name="IconButton_Accept1"] [role="button"]')
    .click();

  // Refresh and verify
  await page.waitForTimeout(5000); // wait for Dataverse write
  await page.goto(MODEL_DRIVEN_APP_URL, { waitUntil: 'load', timeout: 60000 });
  await page.locator('[role="menuitem"]').first().waitFor({ timeout: 30000 });
  await page.locator(`[role="presentation"][title="${CUSTOM_PAGE_NAME}"]`).first().click();

  await expect(
    page
      .locator('[data-control-part="gallery-item"]')
      .filter({ has: page.locator('[data-control-name="Title1"]').getByText(ACCOUNT_NAME, { exact: true }) })
  ).toBeVisible({ timeout: 60000 });
});

Authentification pour les pages personnalisées

Les pages personnalisées s’exécutent sur le domaine Dynamics 365. Utilisez l’état de stockage de l’application pilotée par le modèle :

test.use({
  storageState: path.join(
    path.dirname(getStorageStatePath(process.env.MS_AUTH_EMAIL!)),
    `state-mda-${process.env.MS_AUTH_EMAIL}.json`
  ),
});

Étapes suivantes

Voir aussi