Remarque
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de vous connecter ou de modifier des répertoires.
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de modifier des répertoires.
Les applications basées sur des modèles affichent des vues de liste à l’aide d’un composant de grille de données et d’une exécution de formulaire pour modifier les enregistrements. Les classes GridComponent et FormComponent du framework abstraient la structure DOM sous-jacente, donc vous n'avez pas besoin d'écrire des sélecteurs complexes. Ce guide montre comment accéder à une application, interagir avec la grille, ouvrir des enregistrements et vérifier les valeurs des champs de formulaire à l’aide du ModelDrivenAppPage et de ses composants intégrés.
Lancer une application basée sur un modèle
Utilisez skipMakerPortal: true et directUrl pour contourner Power Apps navigation et accéder directement à l’application :
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();
});
Travailler avec la grille
Le GridComponent gère le AG Grid qui alimente les vues de liste pilotées par modèle. Il emploie des attributs [row-index] pour un ciblage fiable des lignes qui fonctionne correctement après le filtrage.
Attendez que la grille se charge
Accédez à l’affichage de liste d’une entité et attendez que les lignes de grille s’affichent avant d’interagir avec elles.
await modelDrivenApp.navigateToGridView('nwind_orders');
await modelDrivenApp.grid.waitForGridLoad();
Filtrer la grille
Réduisez les résultats de la grille en recherchant toutes les colonnes visibles ou en ciblant une colonne spécifique.
// 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');
Lire les valeurs des cellules
Récupérez la valeur affichée d’une cellule par index de ligne et nom de colonne (nom de schéma ou nom complet).
// 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');
Ouvrir un enregistrement
Ouvrez le formulaire d’un enregistrement par numéro de ligne ou en faisant correspondre une valeur de colonne.
// 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',
});
Sélectionner des lignes
Sélectionnez une ou plusieurs lignes dans la grille ou vérifiez si la grille contient des enregistrements.
// 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();
Utiliser des formulaires
Le FormComponent encapsule le runtime de formulaire Dynamics 365 et l’API Xrm FormContext.
Lire les valeurs de champ
Utilisez getAttribute() pour récupérer la valeur actuelle d’un champ de formulaire par son nom de schéma.
const orderNumber = await modelDrivenApp.form.getAttribute('nwind_ordernumber');
const status = await modelDrivenApp.form.getAttribute('nwind_orderstatusid');
Écrire des valeurs de champ
Permet setAttribute() de définir par programmation la valeur d’un champ sur le formulaire.
await modelDrivenApp.form.setAttribute('nwind_ordernumber', 'ORD-99999');
await modelDrivenApp.form.setAttribute('nwind_notes', 'Updated via test');
Enregistrer le formulaire
Enregistrez l’enregistrement et vérifiez que le formulaire n’est plus sale et passe la validation.
await modelDrivenApp.form.save();
// Verify the form saved successfully
expect(await modelDrivenApp.form.isDirty()).toBe(false);
expect(await modelDrivenApp.form.isValid()).toBe(true);
Parcourir les onglets de formulaire
Basculez entre les onglets du formulaire pour accéder aux champs dans différentes sections.
await modelDrivenApp.form.navigateToTab('Summary');
await modelDrivenApp.form.navigateToTab('Details');
Contrôler la visibilité et l’état des champs
Modifiez la visibilité, l’état désactivé ou le niveau requis d’un champ au moment de l’exécution.
await modelDrivenApp.form.setFieldVisibility('nwind_notes', true);
await modelDrivenApp.form.setFieldDisabled('nwind_ordernumber', false);
await modelDrivenApp.form.setFieldRequiredLevel('nwind_customerid', 'required');
Exécuter du code Xrm personnalisé
Exécutez tout code qui utilise l’API Dynamics 365 Xrm directement dans le contexte de formulaire :
const result = await modelDrivenApp.form.execute(async (formContext) => {
const attr = formContext.getAttribute('nwind_ordernumber');
return attr?.getValue();
});
console.log(`Order number from Xrm: ${result}`);
Exemple de flux de travail CRUD complet
Ce test illustre un flux de travail de création, de lecture, de mise à jour et de suppression complet sur une application basée sur un modèle.
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();
});
Naviguer à l’aide du plan
Utilisez la barre latérale du plan de plan pour accéder à une vue de liste d’entités spécifique.
// 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();
Dépannage des problèmes de grille
Le tableau suivant répertorie les problèmes courants d’interaction de grille et la façon de les résoudre.
| Symptôme | Cause probable | Résolution |
|---|---|---|
| Ligne non trouvée après application du filtrage | La grille est toujours en cours de rendu | Ajouter await mda.grid.waitForGridLoad() après le filtre |
nth-child échec du sélecteur |
Structure d’en-tête/ligne AG Grid | Utiliser le sélecteur [row-index] intégré dans GridComponent |
| Le clic sur la case à cocher est bloqué par une superposition | L’icône CheckMark recouvre le champ de saisie | Utilisez { force: true } à l'activation de la case à cocher |