Hinweis
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, sich anzumelden oder das Verzeichnis zu wechseln.
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, das Verzeichnis zu wechseln.
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.
Navigieren zu einer benutzerdefinierten Seite
- Starten Sie die modellgesteuerte App mit
AppProvider. - Wählen Sie das benutzerdefinierte Seitenelement in der Sitemap aus.
- 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');
Filtern einer Galerie nach Titel auf einer benutzerdefinierten Seite
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`
),
});