ツールキットには、次の 4 つのレイヤーのロケーターサポートが用意されています。
- Playwright の標準 HTML および Fluent UI 要素用の組み込みのセマンティック ロケーター
- 各アプリサーフェスの静的セレクターオブジェクト
- カスタム ページ オブジェクトを作成するための
BaseLocatorsクラス -
LocatorUtilsCSS セレクター文字列を構築するためのヘルパー
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 はこれらのロケーターを 、page と locator で直接公開します。 最初に、標準の 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 クラス
PowerAppsPageLocators は PowerAppsPageSelectors をラップし、すぐに使用できる 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 には、モデル駆動型アプリ ランタイム (サイトマップ、グリッド、フォーム、コマンド バー、ダイアログ) が含まれます。
GridComponentとFormComponentはこれらのセレクターを内部で使用します。 コンポーネントが公開しないセレクターが必要な場合にのみ、直接使用します。
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() を使用する |