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.
Le modèle pom (Page Object Model) est un modèle de conception qui encapsule les sélecteurs et les actions d’une page ou d’un composant spécifique dans une classe dédiée. Teste les méthodes d’appel sur l’objet de page au lieu d’utiliser des localisateurs bruts. Cette approche rend les tests plus lisibles et plus faciles à gérer lorsque l’interface utilisateur change.
Pourquoi utiliser POM pour les tests Power Platform
Les applications Power Platform présentent plusieurs caractéristiques qui rendent POM particulièrement utile :
-
Les applications de canevas ont de nombreux
data-control-nameattributs : les centraliser dans une classe signifie que le renommage d’un contrôle ne nécessite qu’une seule modification. - Les noms de schémas de champ de formulaire pilotés par modèle peuvent changer si les tables sont modifiées , les isolant dans un POM limite l’impact des modifications.
- Les actions courantes (accédez à la galerie, cliquez sur Ajouter, enregistrer un enregistrement) sont répétées dans de nombreux tests : POM évite la duplication.
Objets de page intégrés de la boîte à outils
Le power-platform-playwright-toolkit fournit des objets de page prêts à l’emploi :
| Classe | Type d’application | Méthodes clés |
|---|---|---|
CanvasAppPage |
Canvas |
waitForLoad(), getFrame() |
ModelDrivenAppPage |
Pilotée par modèle |
navigateToGridView(), navigateToFormView() |
GridComponent |
grille d’application pilotée par modèle |
filterByKeyword(), getCellValue(), openRecord(), selectRow() |
FormComponent |
formulaire d’application piloté par modèle |
getAttribute(), setAttribute(), save(), isDirty() |
CommandingComponent |
barre de commandes de l’application pilotée par modèle |
clickButton(), clickMoreCommands() |
Accédez-y via AppProvider:
const app = new AppProvider(page, context);
await app.launch({ ... });
const mda = app.getModelDrivenAppPage();
// mda.grid, mda.form, mda.commanding are ready to use
Créer un POM personnalisé pour votre application canevas
Étendez le kit de ressources en créant votre propre objet de page pour votre application spécifique :
// pages/accounts/AccountsCanvasPage.ts
import { Page, FrameLocator, expect } from '@playwright/test';
export class AccountsCanvasPage {
private readonly frame: FrameLocator;
constructor(private readonly page: Page) {
this.frame = page.frameLocator('iframe[name="fullscreen-app-host"]');
}
// --- Locators ---
get gallery() {
return this.frame.locator('[data-control-name="Gallery1"]');
}
get addButton() {
return this.frame.locator('[data-control-name="IconButton_Add1"] [role="button"]');
}
get saveButton() {
return this.frame.locator('[data-control-name="IconButton_Accept1"] [role="button"]');
}
get accountNameInput() {
return this.frame.locator('input[aria-label="Account Name"]');
}
get phoneInput() {
return this.frame.locator('input[aria-label="Main Phone"]');
}
// --- Actions ---
async waitForLoad(): Promise<void> {
await this.gallery
.locator('[data-control-part="gallery-item"]')
.first()
.waitFor({ state: 'visible', timeout: 60000 });
}
async clickAdd(): Promise<void> {
await this.addButton.waitFor({ state: 'visible' });
await this.addButton.click();
}
async fillAccountForm(accountName: string, phone: string): Promise<void> {
await this.accountNameInput.fill(accountName);
await this.phoneInput.fill(phone);
}
async save(): Promise<void> {
await this.saveButton.click();
}
async findAccount(name: string) {
return this.gallery
.locator('[data-control-part="gallery-item"]')
.filter({
has: this.frame
.locator('[data-control-name="Title1"]')
.getByText(name, { exact: true }),
});
}
async expectAccountVisible(name: string): Promise<void> {
const item = await this.findAccount(name);
await expect(item).toBeVisible({ timeout: 30000 });
}
async getItemCount(): Promise<number> {
return this.gallery.locator('[data-control-part="gallery-item"]').count();
}
}
Utilisez le POM dans les tests
L’exemple suivant montre comment les tests consomment l’objet de page pour maintenir le code de test axé sur le AccountsCanvasPage comportement.
// tests/accounts/accounts.test.ts
import { test, expect } from '@playwright/test';
import { AppProvider, AppType, AppLaunchMode, buildCanvasAppUrlFromEnv } from 'power-platform-playwright-toolkit';
import { AccountsCanvasPage } from '../../pages/accounts/AccountsCanvasPage';
test.describe('Accounts canvas app', () => {
let accountsPage: AccountsCanvasPage;
test.beforeEach(async ({ page, context }) => {
const app = new AppProvider(page, context);
await app.launch({
app: 'Accounts App',
type: AppType.Canvas,
mode: AppLaunchMode.Play,
skipMakerPortal: true,
directUrl: buildCanvasAppUrlFromEnv(),
});
accountsPage = new AccountsCanvasPage(page);
await accountsPage.waitForLoad();
});
test('should display accounts', async () => {
const count = await accountsPage.getItemCount();
expect(count).toBeGreaterThan(0);
});
test('should create a new account', async () => {
const name = `Test Account ${Date.now()}`;
await accountsPage.clickAdd();
await accountsPage.fillAccountForm(name, '555-9000');
await accountsPage.save();
await accountsPage.expectAccountVisible(name);
});
});
Créer un POM personnalisé pour les entités basées sur des modèles
Envelopper le kit d'outils ModelDrivenAppPage pour une entité spécifique :
// pages/orders/OrdersPage.ts
import { Page, expect } from '@playwright/test';
import { ModelDrivenAppPage } from 'power-platform-playwright-toolkit';
const ENTITY = 'nwind_orders';
const ORDER_NUMBER_FIELD = 'nwind_ordernumber';
const STATUS_FIELD = 'nwind_orderstatusid';
export class OrdersPage {
constructor(
private readonly page: Page,
private readonly mda: ModelDrivenAppPage,
) {}
async navigateToList(): Promise<void> {
await this.mda.navigateToGridView(ENTITY);
await this.mda.grid.waitForGridLoad();
}
async filterByOrderNumber(orderNumber: string): Promise<void> {
await this.mda.grid.filterByKeyword(orderNumber);
await this.mda.grid.waitForGridLoad();
}
async openFirstOrder(): Promise<void> {
await this.mda.grid.openRecord({ rowNumber: 0 });
}
async getOrderNumber(): Promise<string | null> {
return this.mda.form.getAttribute(ORDER_NUMBER_FIELD);
}
async setOrderNumber(value: string): Promise<void> {
await this.mda.form.setAttribute(ORDER_NUMBER_FIELD, value);
}
async saveOrder(): Promise<void> {
await this.mda.form.save();
expect(await this.mda.form.isDirty()).toBe(false);
}
async deleteFirstOrder(): Promise<void> {
await this.mda.grid.selectRow(0);
await this.page.locator('button[aria-label*="Delete"]').first().click();
const dialog = this.page.locator('[role="dialog"]');
await dialog.locator('button:has-text("Delete")').click();
}
}
Structure de dossiers
Organisez les objets de page en même temps que les tests dans une structure de répertoires mise en miroir :
packages/e2e-tests/
├── pages/
│ ├── accounts/
│ │ └── AccountsCanvasPage.ts
│ ├── orders/
│ │ └── OrdersPage.ts
│ └── northwind/
│ ├── NorthwindCanvasAppPage.ts
│ └── CustomPage.page.ts
├── tests/
│ ├── accounts/
│ │ └── accounts.test.ts
│ └── northwind/
│ ├── canvas/
│ └── mda/
└── playwright.config.ts
Recommandations en matière de conception POM
Suivez ces instructions pour que vos objets de page soient cohérents et faciles à gérer.
- Une classe par page logique ou section principale de l’interface utilisateur : ne placez pas l’ensemble de l’application dans une classe
- Exposer des localisateurs en tant que getters, et non en tant que chaînes : l’objet localisateur offre une meilleure sécurité de type et une attente automatique
-
Placer
waitFordans les méthodes d’action : les appelants ne doivent pas avoir besoin de savoir quand attendre - Conservez les assertions dans les tests, et non dans les Page Objects. Les POMs doivent effectuer des actions et retourner des données ; les tests doivent vérifier les résultats attendus.
-
Utiliser des noms de méthodes descriptives -
clickAdd()est mieux queclick(),findAccount(name)est mieux quegetItem(text)