Freigeben über


Testen von benutzerdefinierten Seiten in modellgesteuerten Apps

Benutzerdefinierte Seiten sind Canvas-Apps, die in eine modellgesteuerte App eingebettet sind. Sie werden in einem iFrame in der modellgesteuerten App-Shell gerendert. Zum Testen müssen Sie zu der modellgesteuerten App navigieren, die benutzerdefinierte Seite aus der Sitemap auswählen und dann alle Interaktionen mit Steuerelementen auf das innere iFrame beziehen.

Funktionsweise von benutzerdefinierten Seitentests in modellgesteuerten Apps

Wenn eine benutzerdefinierte Seite geladen wird, verbleibt die modellgesteuerte App-Shell in der Dynamics 365 Domäne. Die benutzerdefinierte Seiten-Canvas-Runtime wird innerhalb folgender Umgebung geladen.

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

Dies ist derselbe iFrame, der von eigenständigen Canvas-Apps verwendet wird. Sobald Sie den Frame-Locator haben, gelten alle Canvas-App-Testmuster.

  1. Starten Sie die modellgesteuerte App mit AppProvider.
  2. Wählen Sie das benutzerdefinierte Seitenelement in der Sitemap aus.
  3. Warten Sie, bis die Canvas-Laufzeitumgebung initialisiert wird.
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);
});

Interagieren mit benutzerdefinierten Seitensteuerelementen

Nachdem Sie zur benutzerdefinierten Seite navigiert haben, richten Sie die Bereichslokatoren auf das Canvas-Iframe aus:

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 });

Klicken auf Schaltflächen in benutzerdefinierten Seitensteuerelementen

Verwenden Sie das data-control-name Attribut, um bestimmte Schaltflächensteuerelemente innerhalb des Canvas-iframes anzusprechen, und suchen Sie dann das innere [role="button"] Element, um eine Klickaktion auszulösen.

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

Ausfüllen von Formularfeldern auf einer benutzerdefinierten Seite

Suchen Sie Eingabefelder anhand ihres aria-label Attributs im Canvas-iframe, und verwenden Sie die fill Methode, um Werte einzugeben.

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

Um ein bestimmtes Element in einer Galerie zu finden, filtern Sie die Liste der Galerieelemente, indem Sie den Textinhalt eines untergeordneten Steuerelements wie z. B. Title1 abgleichen.

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 });

Aktualisieren der benutzerdefinierten Seite nach dem Speichern

Wenn Sie einen neuen Datensatz auf einer benutzerdefinierten Seite speichern, die von Dataverse unterstützt wird, wird der Katalog nicht automatisch aktualisiert, es sei denn, Sie lösen ein vollständiges Neuladen aus. Der empfohlene Ansatz besteht darin, zum App-Stamm und zurück zu wechseln:

// 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 });

Vollständiges Testbeispiel: Erstellen und Überprüfen eines Datensatzes

Im folgenden Beispiel werden navigations-, Formulareingabe-, Speicher- und Katalogüberprüfungen in einem einzigen End-to-End-Test kombiniert, der einen Kontodatensatz erstellt und bestätigt, dass er im benutzerdefinierten Seitenkatalog angezeigt wird.

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 });
});

Authentifizierung für benutzerdefinierte Seiten

Benutzerdefinierte Seiten werden in der domäne Dynamics 365 ausgeführt. Verwenden Sie den modellgesteuerten App-Speicherstatus:

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

Nächste Schritte

Siehe auch