Freigeben über


Modellgesteuerte Apps testen

Modellgesteuerte Apps rendern Listenansichten unter Verwendung einer Datenrasterkomponente und einer Formularlaufzeitumgebung zur Datensatzbearbeitung. Die GridComponent und FormComponent Klassen des Frameworks abstrahieren die zugrunde liegende DOM-Struktur, sodass Sie keine komplexen Selektoren schreiben müssen. In diesem Leitfaden wird gezeigt, wie Sie zu einer App navigieren, mit dem Raster interagieren, Datensätze öffnen und Formularfeldwerte mithilfe der ModelDrivenAppPage integrierten Komponenten überprüfen.

Starten einer modellgesteuerten App

Verwenden Sie und , um die Navigation von Power Apps zu umgehen und direkt zur App zu wechseln.

import { test } from '@playwright/test';
import { AppProvider, AppType, AppLaunchMode } from 'power-platform-playwright-toolkit';

const MODEL_DRIVEN_APP_URL = process.env.MODEL_DRIVEN_APP_URL!;

test.beforeEach(async ({ page, context }) => {
  const app = new AppProvider(page, context);

  await app.launch({
    app: 'Northwind Orders',
    type: AppType.ModelDriven,
    mode: AppLaunchMode.Play,
    skipMakerPortal: true,
    directUrl: MODEL_DRIVEN_APP_URL,
  });

  modelDrivenApp = app.getModelDrivenAppPage();
});

Arbeiten mit dem Raster

Das GridComponent verarbeitet das AG Grid, das modellgesteuerte Listenansichten unterstützt. Es verwendet [row-index] Attribute für eine zuverlässige Zeilenadressierung, die nach dem Filtern ordnungsgemäß funktioniert.

Warten, bis das Raster geladen wird

Navigieren Sie zur Listenansicht einer Entität, und warten Sie, bis die Rasterzeilen gerendert werden, bevor sie mit ihnen interagieren.

await modelDrivenApp.navigateToGridView('nwind_orders');
await modelDrivenApp.grid.waitForGridLoad();

Raster filtern

Schränken Sie die Rasterergebnisse ein, indem Sie nach allen sichtbaren Spalten suchen oder auf eine bestimmte Spalte abzielen.

// Filter by keyword (searches across visible columns)
await modelDrivenApp.grid.filterByKeyword('ORD-12345');
await modelDrivenApp.grid.waitForGridLoad();

// Filter by a specific column
await modelDrivenApp.grid.filterByColumn('Order', 'ORD-12345');

Lesen von Zellwerten

Rufen Sie den angezeigten Wert einer Zelle nach Zeilenindex und Spaltenname (Schemaname oder Anzeigename) ab.

// By column schema name
const orderNumber = await modelDrivenApp.grid.getCellValue(0, 'nwind_ordernumber');

// By column display name
const status = await modelDrivenApp.grid.getCellValue(0, 'Order Status');

Öffnen Sie einen Datensatz

Öffnen Sie das Formular eines Datensatzes anhand der Zeilennummer oder indem Sie einen Spaltenwert abgleichen.

// Open the first record in the grid
await modelDrivenApp.grid.openRecord({ rowNumber: 0 });

// Open a record by column value
await modelDrivenApp.grid.openRecord({
  columnName: 'Order Number',
  columnValue: 'ORD-12345',
});

Zeilen auswählen

Wählen Sie eine oder mehrere Zeilen im Raster aus, oder überprüfen Sie, ob das Raster Datensätze enthält.

// Select one row
await modelDrivenApp.grid.selectRow(0);

// Select multiple rows
await modelDrivenApp.grid.selectRows([0, 1, 2]);

// Check if grid is empty
const isEmpty = await modelDrivenApp.grid.isGridEmpty();

Mit Formularen arbeiten

Der FormComponent umschließt die Dynamics 365 Formularlaufzeit und die Xrm FormContext-API.

Lesen von Feldwerten

Verwenden Sie getAttribute() um den aktuellen Wert eines Formularfelds anhand des Schemanamens abzurufen.

const orderNumber = await modelDrivenApp.form.getAttribute('nwind_ordernumber');
const status = await modelDrivenApp.form.getAttribute('nwind_orderstatusid');

Feldwerte schreiben

Wird setAttribute() verwendet, um den Wert eines Felds programmgesteuert im Formular festzulegen.

await modelDrivenApp.form.setAttribute('nwind_ordernumber', 'ORD-99999');
await modelDrivenApp.form.setAttribute('nwind_notes', 'Updated via test');

Speichern des Formulars

Speichern Sie den Datensatz, und überprüfen Sie, ob das Formular nicht mehr schmutzig ist und die Überprüfung besteht.

await modelDrivenApp.form.save();

// Verify the form saved successfully
expect(await modelDrivenApp.form.isDirty()).toBe(false);
expect(await modelDrivenApp.form.isValid()).toBe(true);

Wechseln Sie zwischen den Registerkarten im Formular, um auf Felder in verschiedenen Abschnitten zuzugreifen.

await modelDrivenApp.form.navigateToTab('Summary');
await modelDrivenApp.form.navigateToTab('Details');

Steuerfeld Sichtbarkeit und Zustand

Ändern der Sichtbarkeit, des deaktivierten Zustands oder der erforderlichen Ebene eines Felds zur Laufzeit.

await modelDrivenApp.form.setFieldVisibility('nwind_notes', true);
await modelDrivenApp.form.setFieldDisabled('nwind_ordernumber', false);
await modelDrivenApp.form.setFieldRequiredLevel('nwind_customerid', 'required');

Ausführen von benutzerdefiniertem Xrm-Code

Führen Sie Code, der die Dynamics 365 Xrm-API direkt im Formularkontext nutzt, aus:

const result = await modelDrivenApp.form.execute(async (formContext) => {
  const attr = formContext.getAttribute('nwind_ordernumber');
  return attr?.getValue();
});

console.log(`Order number from Xrm: ${result}`);

Vollständiges CRUD-Workflowbeispiel

Dieser Test veranschaulicht einen vollständigen Erstellungs-, Lese-, Aktualisierungs- und Löschworkflow für eine modellgesteuerte App.

import { test, expect } from '@playwright/test';
import { AppProvider, AppType, AppLaunchMode } from 'power-platform-playwright-toolkit';

const ENTITY = 'nwind_orders';
const APP_URL = process.env.MODEL_DRIVEN_APP_URL!;

test('should create, read, update, and delete an order', async ({ page, context }) => {
  const app = new AppProvider(page, context);
  await app.launch({
    app: 'Northwind Orders',
    type: AppType.ModelDriven,
    mode: AppLaunchMode.Play,
    skipMakerPortal: true,
    directUrl: APP_URL,
  });
  const mda = app.getModelDrivenAppPage();

  // Step 1: Create a new order record
  await mda.navigateToFormView(ENTITY);
  await page.locator('input[data-id="nwind_ordernumber.fieldControl-text-box-text"]').fill('ORD-TEST-001');
  await page.locator('button[aria-label*="Save"]').first().click();
  await page.waitForTimeout(3000);

  // Step 2: Read the record in the grid
  await mda.navigateToGridView(ENTITY);
  await mda.grid.waitForGridLoad();
  await mda.grid.filterByKeyword('ORD-TEST-001');
  await mda.grid.waitForGridLoad();

  expect(await mda.grid.getRowCount()).toBeGreaterThan(0);
  const cellValue = await mda.grid.getCellValue(0, 'Order Number');
  expect(cellValue).toContain('ORD-TEST-001');

  // Step 3: Update the order number
  await mda.grid.openRecord({ rowNumber: 0 });
  await page.locator('input[data-id="nwind_ordernumber.fieldControl-text-box-text"]').fill('ORD-TEST-001-UPDATED');
  await page.locator('button[aria-label*="Save"]').first().click();
  await page.waitForTimeout(3000);

  // Step 4: Delete the record
  await mda.navigateToGridView(ENTITY);
  await mda.grid.waitForGridLoad();
  await mda.grid.filterByKeyword('ORD-TEST-001-UPDATED');
  await mda.grid.waitForGridLoad();
  await mda.grid.selectRow(0);
  await page.locator('button[aria-label*="Delete"]').first().click();
  // Confirm the deletion dialog
  const dialog = page.locator('[role="dialog"]');
  await dialog.locator('button:has-text("Delete")').click();
});

Verwenden Sie die Sitemap-Randleiste, um zu einer bestimmten Entitätslistenansicht zu navigieren.

// Navigate to a specific entity list view via sitemap
const sidebarItem = page.locator('[role="presentation"][title="Orders"]').first();
await sidebarItem.waitFor({ state: 'visible', timeout: 15000 });
await sidebarItem.click();

Lösen von Gitterproblemen

In der folgenden Tabelle sind allgemeine Probleme mit der Rasterinteraktion und deren Behebung aufgeführt.

Symptom Wahrscheinliche Ursache Resolution
Zeile nach dem Filtern nicht gefunden Raster wird weiterhin neu gerendert await mda.grid.waitForGridLoad() nach dem Filter hinzufügen
nth-child Selektor schlägt fehl AG Grid-Kopfzeilen-/Zeilenstruktur Verwenden Sie den [row-index] Selektor (integriert in GridComponent)
Kontrollkästchen durch Überlagerung blockiert Überlagerungen des Häkchensymbols auf Eingabefeldern { force: true } beim Anklicken des Kontrollkästchens verwenden

Nächste Schritte

Siehe auch