ロケーター

ツールキットには、次の 4 つのレイヤーのロケーターサポートが用意されています。

  • Playwright の標準 HTML および Fluent UI 要素用の組み込みのセマンティック ロケーター
  • 各アプリサーフェスの静的セレクターオブジェクト
  • カスタム ページ オブジェクトを作成するための BaseLocators クラス
  • LocatorUtils CSS セレクター文字列を構築するためのヘルパー

4 つのレイヤーをすべて理解することで、状況ごとに適切なツールを選択し、DOM の変更に抵抗するセレクターを記述できます。

ロケーターの優先度

以下の順序で、ロケーターを希望度の高い順から低い順に選択してください:

優先順位 タイプ Example なぜでしょうか
1 ロールと名前 getByRole('button', { name: 'Save' }) 支援技術がページを見る方法を反映し、CSSのリファクタリングに対応できる
2 ARIA ラベル getByLabel('Account Name') DOM 構造体ではなく、ユーザーが読み取るラベルに関連付けられている
3 プレースホルダー getByPlaceholder('Search') 入力に対して安定しています。スクリーン リーダーに表示される
4 テスト 識別子 getByTestId('create-blank-canvas-app') テスト用の明示的な専用フック
5 データ属性 locator('[data-control-name="Gallery1"]') Power Platform 固有で、キャンバス アプリ環境において安定しています
6 CSS クラス/ID locator('.ms-Button') セマンティック オプションが存在しない場合にのみ使用します。壊れ やすい

Playwright の組み込みセマンティックロケーター

Playwright はこれらのロケーターを 、pagelocator で直接公開します。 最初に、標準の HTML 要素と Fluent UI 要素をターゲットにするときに使用します。

getByRole

ARIA ロールとオプションのアクセス可能な名前で要素を選択します。

// Click a button by its label
await page.getByRole('button', { name: 'Save' }).click();

// Click a menu item
await page.getByRole('menuitem', { name: 'New record' }).click();

// Find a dialog
const dialog = page.getByRole('dialog');
await dialog.getByRole('button', { name: 'Delete' }).click();

// Model-driven grid row
const row = page.getByRole('row', { name: 'ORD-001' });

Power Platform に関連するサポートされているロール:

button  link  textbox  heading  menuitem  grid  row  cell
dialog  navigation  searchbox  checkbox  combobox  tab  tabpanel
gridcell  columnheader  rowheader  option  listbox

getByLabel

関連付けられた <label> または aria-labelで入力を選択します。

// Canvas app input
const canvasFrame = page.frameLocator('iframe[name="fullscreen-app-host"]');
await canvasFrame.getByLabel('Account Name').fill('Contoso');
await canvasFrame.getByLabel('Main Phone').fill('555-1234');

// Model-driven form input (scoped to the page, not iframe)
await page.getByLabel('Order Number').fill('ORD-001');

getByText

表示されているテキスト コンテンツで要素を検索します。

// Find an item in a list
await page.getByText('Northwind Traders').click();

// Exact match to avoid partial hits
await page.getByText('ORD-001', { exact: true }).click();

// Regex match
await page.getByText(/Order \d+/).first().click();

getByPlaceholder

placeholder属性で入力を選択します。

await page.getByPlaceholder('Search').fill('ORD-001');
await page.getByPlaceholder('Enter a name or email address').fill('test@contoso.com');

getByTestId(テストIDで取得する)

data-testid属性で要素を選択します。 ツールキットは、いくつかの場所で data-test-id を使用します。 どちらの属性も、 getByTestId または CSS で動作します。

// Using Playwright getByTestId (maps to data-testid)
await page.getByTestId('create-blank-canvas-app').click();

// Using CSS for data-test-id (Power Apps portal variant)
await page.locator('[data-test-id="Dialog.Accept"]').click();

BaseLocators - カスタム ページ オブジェクトをビルドする

BaseLocators は、すべての Playwright セマンティック ロケーターをラップする抽象クラスです。 ページ固有のロケーター クラスを構築するように拡張し、セレクターを 1 か所に保持します。

import { BaseLocators } from 'power-platform-playwright-toolkit';
import { Page } from '@playwright/test';

export class OrderFormLocators extends BaseLocators {
  constructor(page: Page) {
    super(page);
  }

  // Semantic locators via protected helpers
  get saveButton() {
    return this.getByRole('button', { name: 'Save' });
  }

  get orderNumberInput() {
    return this.getByLabel('Order Number');
  }

  get statusDropdown() {
    return this.getByRole('combobox', { name: 'Order Status' });
  }

  // CSS fallback when semantic options aren't available
  get notesTextarea() {
    // Document why CSS is used instead of getByRole/getByLabel
    return this.locator('textarea[data-id="nwind_notes.fieldControl-text-box-textarea"]');
  }

  // Chaining locators
  get confirmDeleteButton() {
    return this.chain(
      this.getByRole('dialog'),
      (dialog) => dialog.getByRole('button', { name: 'Delete' })
    );
  }
}

BaseLocators で使用可能な保護されたメソッド

次の表は、 BaseLocators で使用できる保護されたメソッドとそのプレイライトの対応するメソッドの一覧です。

メソッド マップ先
getByRole(role, options?) page.getByRole()
getByText(text, options?) page.getByText()
getByLabel(label, options?) page.getByLabel()
getByPlaceholder(placeholder) page.getByPlaceholder()
getByTestId(testId) page.getByTestId()
getByAriaLabel(label) page.getByLabel() (aria-label パス)
locator(selector) page.locator() CSS フォールバック
chain(parent, child) 親の中で子のロケーターを特定する

LocatorUtils - CSS セレクター文字列をビルドする

LocatorUtils は、CSS セレクター文字列を生成する静的ヘルパー クラスです。 セレクターを動的にビルドし、 page.locator()に渡す必要がある場合に使用します。

import { LocatorUtils } from 'power-platform-playwright-toolkit';

// data-automation-id (Fluent UI components)
const spinner = page.locator(LocatorUtils.automationId('loading-spinner'));
// => [data-automation-id="loading-spinner"]

// data-automation-key (Fluent UI list items)
const menuItem = page.locator(LocatorUtils.automationKey('contextualMenu'));
// => [data-automation-key="contextualMenu"]

// aria-label as CSS
const button = page.locator(LocatorUtils.ariaLabel('Save'));
// => [aria-label="Save"]

// Parameterized selector template
const appLink = page.locator(
  LocatorUtils.formatSelector('[role="rowheader"] a:has-text("{0}")', 'My App')
);
// => [role="rowheader"] a:has-text("My App")

// Class with wildcard (partial class match)
const badge = page.locator(LocatorUtils.className('*-badge'));
// => [class*="-badge"]

// ID shorthand
const root = page.locator(LocatorUtils.id('ApplicationShell'));
// => #ApplicationShell

LocatorUtils リファレンス

次の表に、使用可能な LocatorUtils メソッドと、生成される CSS セレクターを示します。

メソッド 生成されたセレクター 使用対象
automationId(id) [data-automation-id="id"] Fluent UI コンポーネント
automationKey(key) [data-automation-key="key"] Fluent UI リスト/メニュー項目
ariaLabel(label) [aria-label="label"] aria-label を持つ要素(関連付けされていない <label>
dataTestId(id) [data-test-id="id"] Power Apps ポータルのテスト フック
formatSelector(tpl, ...args) 置き換えられた{0}および{1}を含むテンプレート パラメーター化された CSS セレクター
className(name) .name または [class*="name"] クラス ベースの選択 (控えめに使用)
id(id) #id 要素 ID (控えめに使用)

PowerAppsPageSelectors — Power Apps セレクター

PowerAppsPageSelectors は、ポータル 画面別に編成された CSS セレクター文字列の静的オブジェクトです。 実行中のアプリではなく、Power Apps サイトと対話するテストを記述するときに使用します。

import { PowerAppsPageSelectors } from 'power-platform-playwright-toolkit';

// Wait for the Apps page to load
await page.locator(PowerAppsPageSelectors.AppsPage.MainContainer)
  .waitFor({ state: 'visible', timeout: 30000 });

// Click New App
await page.locator(PowerAppsPageSelectors.AppsPage.NewApp).click();

// Open an app by name (uses dynamic selector)
await page.locator(
  LocatorUtils.formatSelector(PowerAppsPageSelectors.AppsPage.AppSelector, 'My Canvas App')
).click();

PowerAppsPageSelectors 構造体

次の表では、 PowerAppsPageSelectorsで使用できるセクションについて説明します。

Section Description
Root / PageHeader / MainNavigation 最上位ページの Chrome
AppsPage アプリ リスト グリッド、新しいアプリ ボタン、フィルター ボタン
SolutionsPage ソリューション一覧、検索、サイトマップ セレクター
AppPreviewPage Canvas Studio のコマンド バー、保存/発行/再生
ModelAppPage アプリケーション シェル、コマンド バー、階層リンク
HomePage ホーム ページの領域とセクション
TeachingBubble Fluent UI ティーチング バブルの閉じ方
ModalFocusTrapZone / DialogAcceptButton Dialogs
CanvasDesignerIframe / CanvasPlayerIframe Studio の iframe リファレンス
MeInitialsButton / SignOutButton 認証 UI
ErrorPage エラーページの要素

PowerAppsPageLocators クラス

PowerAppsPageLocatorsPowerAppsPageSelectors をラップし、すぐに使用できる Playwright Locator オブジェクトを返します。

import { PowerAppsPageLocators } from 'power-platform-playwright-toolkit';

const portalLocators = new PowerAppsPageLocators(page);

// Navigate the portal without writing raw selectors
await portalLocators.newAppButton.click();
await portalLocators.canvasAppButton.click();

// Find an app by name
await portalLocators.getAppByName('Northwind Orders').click();

// Find a solution by name
portalLocators.getSolutionByName('NorthwindSolution');

// Access the canvas designer iframe
await portalLocators.canvasDesignerIframe.waitFor({ state: 'visible' });

ModelDrivenAppLocators - ランタイム セレクター

ModelDrivenAppLocators には、モデル駆動型アプリ ランタイム (サイトマップ、グリッド、フォーム、コマンド バー、ダイアログ) が含まれます。 GridComponentFormComponentはこれらのセレクターを内部で使用します。 コンポーネントが公開しないセレクターが必要な場合にのみ、直接使用します。

import { ModelDrivenAppLocators } from 'power-platform-playwright-toolkit';

// Grid row by index (used internally by GridComponent)
const row = page.locator(ModelDrivenAppLocators.Runtime.Content.Grid.RowByIndex(0));
// => [role="row"][row-index="0"]

// Column header
const header = page.locator(
  ModelDrivenAppLocators.Runtime.Content.Grid.ColumnHeader('Order Number')
);
// => [role="columnheader"][aria-label*="Order Number"]

// Sitemap subarea
const siteMapItem = page.locator(
  ModelDrivenAppLocators.Runtime.SiteMap.SubArea('Orders')
);
// => a[aria-label="Orders"]

// Common command bar buttons
await page.locator(ModelDrivenAppLocators.Runtime.Commands.NewButton).click();
await page.locator(ModelDrivenAppLocators.Runtime.Commands.DeleteButton).click();

// Delete confirmation dialog
const deleteDialog = page.locator(ModelDrivenAppLocators.DeleteDialog.Dialog);
await deleteDialog.locator(ModelDrivenAppLocators.DeleteDialog.DeleteButton).click();

Note

生のModelDrivenAppPage.grid.* セレクターよりもModelDrivenAppPage.form.*メソッドとModelDrivenAppLocatorsメソッドを優先します。 コンポーネント メソッドは、生のセレクターでは処理されない待機、再試行、エッジ ケースを処理します。


CanvasAppLocators - Studio セレクター

CanvasAppLocators は、アプリを編集し、再生しないテスト用のキャンバス アプリ スタジオ インターフェイスをカバーします。 ランタイム キャンバス アプリには、これらの Studio セレクターではなく、iframe[name="fullscreen-app-host"]属性を使用してdata-control-name経由でアクセスします。

import { CanvasAppLocators } from 'power-platform-playwright-toolkit';

// Studio command bar
await page.locator(CanvasAppLocators.Studio.CommandBar.SaveButton).click();
await page.locator(CanvasAppLocators.Studio.CommandBar.PublishButton).click();

// Insert a gallery control
await page.locator(CanvasAppLocators.Studio.LeftNav.InsertTab).click();
await page.locator(CanvasAppLocators.Studio.Insert.GalleryControl).click();

// Access a control on the canvas by name (studio edit mode)
const control = page.locator(CanvasAppLocators.Studio.Canvas.Control('Gallery1'));
// => [data-control-name="Gallery1"]

// Get runtime canvas controls (play mode)
const galleryItem = canvasFrame.locator(
  getCanvasControlByName('Gallery1') + ' [data-control-part="gallery-item"]'
);

CanvasAppLocators のヘルパー関数

これらのスタンドアロン ヘルパー関数は、クラス インスタンスを必要とせずにキャンバス アプリ コントロール用の CSS セレクターを生成します。

import {
  getCanvasControlByName,
  getCanvasScreenByName,
  getCanvasDataTestId,
} from 'power-platform-playwright-toolkit';

// Build a selector for a named control
getCanvasControlByName('Button1')   // => [data-control-name="Button1"]
getCanvasScreenByName('HomeScreen') // => [data-screen-name="HomeScreen"]
getCanvasDataTestId('my-hook')      // => [data-testid="my-hook"]

Iframe スコープ設定

要素を正しく一致させるには、すべてのキャンバス アプリのプレイ モード セレクターのスコープをキャンバス iframe に設定します。

// Get the frame locator
const canvasFrame = page.frameLocator('iframe[name="fullscreen-app-host"]');

// All locator methods work on FrameLocator exactly as on Page
await canvasFrame.getByLabel('Account Name').fill('Contoso');
await canvasFrame.getByRole('button', { name: 'Save' }).click();
await canvasFrame.locator('[data-control-name="Gallery1"]').waitFor({ state: 'visible' });

// Scope a Playwright semantic locator inside the frame
const gallery = canvasFrame.locator('[data-control-name="Gallery1"]');
const item = gallery
  .locator('[data-control-part="gallery-item"]')
  .filter({
    has: canvasFrame
      .locator('[data-control-name="Title1"]')
      .getByText('Contoso', { exact: true }),
  });

Important

キャンバス コントロールに対して page.locator() を直接呼び出すことはありません。 コントロールが iframe 内に配置されているため、セレクターは解決されません。 常に canvasFrame.locator() または canvasFrame.getBy*()から開始します。


連鎖処理とフィルター処理

Playwright ロケーターがクリーンに構成されます。 nth-childを使用せずに選択範囲を絞り込むには、次のパターンを使用します。

// Filter a list to the item that contains specific text
const specificOrder = page
  .locator('[role="row"]')
  .filter({ hasText: 'ORD-001' });

// Filter using a nested locator (more precise than hasText)
const specificGalleryItem = canvasFrame
  .locator('[data-control-part="gallery-item"]')
  .filter({
    has: canvasFrame.getByText('Contoso Ltd', { exact: true }),
  });

// Scope a locator inside another locator
const dialog = page.getByRole('dialog');
const saveInDialog = dialog.getByRole('button', { name: 'Save' });

// Use .and() to combine conditions on the same element
const visibleSaveButton = page
  .getByRole('button', { name: 'Save' })
  .and(page.locator(':visible'));

セレクターの問題をトラブルシューティングする

セレクターの一般的な問題を診断して修正するには、次の表を使用します。

症状 考えられる原因 修正
Locator resolved to N elements (strict モード) セレクターが広すぎる .filter().first()、またはより具体的な属性を追加する
Timeout waiting for locator キャンバス アプリ内 iframe にスコープが設定されていません canvasFrame.locator() の代わりに page.locator() を使用する
nth-child グリッド フィルターの後の区切り AG Grid で 行が再構築される [row-index="${n}"]の使用 (GridComponent に組み込まれています)
getByRole('button') が間違ったボタンと一致する 同じロールを持つ複数のボタン アクセス可能な名前で絞り込む { name: 'exact label' } を追加する
data-control-name でキャンバス コントロールが見つかりませんでした アプリが再生成されました DevTools での再検査;コントロール名が変更された可能性がある
getByLabel 一致が見つかりません 入力にラベルが関連付けられていない 代わりに getByPlaceholder() または getByAriaLabel() を使用する

次のステップ

こちらも参照ください