次の方法で共有


モデル駆動型アプリをテストする

モデル駆動型アプリは、データ グリッド コンポーネントとレコード編集用のフォーム ランタイムを使用してリスト ビューをレンダリングします。 フレームワークの GridComponent クラスと FormComponent クラスは、基になる DOM 構造を抽象化するため、複雑なセレクターを記述する必要はありません。 このガイドでは、 ModelDrivenAppPage とその組み込みコンポーネントを使用して、アプリに移動する方法、グリッドを操作する方法、レコードを開く方法、フォーム フィールドの値を確認する方法について説明します。

モデル駆動型アプリを起動する

skipMakerPortal: truedirectUrl を使用して、Power Apps ナビゲーションをバイパスし、アプリに直接移動します。

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

グリッドを操作する

GridComponentは、モデル駆動型リスト ビューに対応する AG グリッドを処理します。 フィルター処理後に正しく動作する信頼性の高い行のターゲット設定には、 [row-index] 属性を使用します。

グリッドが読み込まれるのを待つ

エンティティのリスト ビューに移動し、グリッド行がレンダリングされるまで待ってから操作します。

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

グリッドをフィルター処理する

表示されているすべての列を検索するか、特定の列を対象にして、グリッドの結果を絞り込みます。

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

セル値の読み取り

行インデックスと列名 (スキーマ名または表示名) でセルの表示値を取得します。

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

レコードを開く

レコードのフォームを行番号または列の値と一致させて開きます。

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

行の選択

グリッド内の 1 つ以上の行を選択するか、グリッドにレコードが含まれているかどうかを確認します。

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

フォームに関する作業

FormComponentは、Dynamics 365 フォーム ランタイムと Xrm FormContext API をラップします。

フィールド値の読み取り

getAttribute()を使用して、フォーム フィールドの現在の値をスキーマ名で取得します。

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

フィールド値を書き込む

setAttribute()を使用して、フォームにフィールドの値をプログラムで設定します。

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

フォームを保存する

レコードを保存し、フォームがダーティでないことを確認し、検証に合格します。

await modelDrivenApp.form.save();

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

フォームのタブを切り替えて、さまざまなセクションのフィールドにアクセスします。

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

フィールドの可視性と状態を制御する

実行時にフィールドの表示、無効な状態、または必要なレベルを変更します。

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

カスタム Xrm コードを実行する

Dynamics 365 Xrm API を使用するコードをフォーム コンテキストで直接実行します。

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

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

完全な CRUD ワークフローの例

このテストでは、モデル駆動型アプリに対する完全な作成、読み取り、更新、および削除ワークフローを示します。

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

サイトマップサイドバーを使用して、特定のエンティティリストビューに移動します。

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

グリッドの問題のトラブルシューティング

次の表に、グリッドの相互作用に関する一般的な問題とその解決方法を示します。

症状: 想定される原因 Resolution
フィルター処理後に行が見つかりません グリッドの再レンダリング フィルターの後に await mda.grid.waitForGridLoad() を追加する
nth-child セレクターが失敗する AG Grid のヘッダー/行の構造 [row-index] セレクターを使用する (GridComponent に組み込まれています)
オーバーレイによってチェックボックスのクリックがブロックされる 入力に重ね合わせられたチェックマークアイコン チェックボックスのクリックで { force: true } を使用する

次のステップ

こちらも参照ください