stably-sdk-rules について
stably-sdk-rulesは、Stably Playwright SDKでテストを作成するためのガイドで、ブラウザー自動化の開発者向けです。通常のPlaywrightとAIによる操作・アサーションの使い分けに加え、メール認証とテスト分離を説明します。
主な機能
- 01Playwrightのロケーターとアサーションの指針
- 02動的なビジュアルアサーション
- 03ページからの構造化データ抽出
- 04AIロケーターと操作の利用
- 05テスト用受信箱の分離
- 06Google認証フローへの対応
活用シーン
- 01不安定なキャンバス操作のテスト
- 02動的なページ内容の検証
- 03ページやメールからのデータ抽出
- 04OTPや認証メールのフローのテスト
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ stablyai/ agent-skills --skill stably-sdk-rulesインストール後、Claude Code で/stably-sdk-rulesを入力して読み込まれたか確認します。
同梱ファイルREADME.md
詳細
Stably SDK のルール
クイックルール
- 決定論的な操作やアサーションには、まず生の Playwright を使用します(高速かつ低コストです)。
- Playwright が不安定になった場合は、コストより信頼性を優先します。
- キャンバス、座標ベースのドラッグ/クリック、不安定な複数ステップのフローには
agent.act()を使用します。 - 動的な視覚アサーションには
expect(...).aiAssert()を使用し、プロンプトを具体的にします。 - 視覚情報からデータを抽出する場合は、
page.extract()/locator.extract()を使用します。 - 標準のロケーターではセマンティックなセレクターを見つけにくい場合は、
page.getLocatorsByAI()を使用します。 - OTP/マジックリンク/確認メールのフローには
Inboxを使用します。 - すべてのプロンプトは自己完結させ、暗黙の過去のコンテキストに依存しないでください。
agent.act()のタスクは小さく保ち、ループ/計算/条件分岐はコードで処理します。- ビューポート外のコンテンツが重要な場合にのみ
fullPage: trueを使用します。 - トレースを読みやすくするため、ロケーターには必ず
.describe("...")を追加します。 - メールを分離するには、各テストで一意の
Inbox.build({ suffix })を使用し、クリーンアップします。
セットアップ(単一ブロック)
npm install -D @playwright/test @stablyai/playwright-test @stablyai/email
export STABLY_API_KEY=YOUR_KEY
export STABLY_PROJECT_ID=YOUR_PROJECT_ID
import { test, expect } from "@stablyai/playwright-test";
import { Inbox } from "@stablyai/email";
任意:プログラムから API キーを設定します。
import { setApiKey } from "@stablyai/playwright-test";
setApiKey("YOUR_KEY");
コアとなるルール
- ロケーターのルール:ロケーターを操作する際は、必ず
.describe("...")を使用します。 - アサーションの選択:
- まず Playwright のアサーションを使用します。
- 動的なチェックや視覚要素の多いチェックには
aiAssertを使用します。
- 操作の選択:
- 決定論的なステップには Playwright を使用します。
- 不安定なタスクやセマンティックなタスク(特にキャンバス/座標)には
agent.actを使用します。
- プロンプトの品質:
- 対象、意図、制約を明示します。
- ステップ間でデータを渡す際は、曖昧な参照ではなく変数を使用します。
最小限の使用パターン
aiAssert
await expect(page).aiAssert("Shows revenue trend chart and spotlight card");
await expect(page.locator(".header").describe("Header")).aiAssert("Has nav, avatar, and bell icon");
アサーションで画面外のコンテンツが必要な場合にのみ、fullPage: true を使用します。
extract
const orderId = await page.extract("Extract the order ID from the first row");
スキーマを指定する場合:
import { z } from "zod";
const Schema = z.object({ revenue: z.string(), users: z.number() });
const metrics = await page.extract("Get revenue and active users", { schema: Schema });
getLocatorsByAI
Playwright >= 1.54.1 が必要です。
const { locator, count } = await page.getLocatorsByAI("the login button");
expect(count).toBe(1);
await locator.describe("Login button located by AI").click();
agent.act
await agent.act("Find the first pending order and mark it as shipped", { page });
適切なパターン:コードで値を計算してから、具体的な値をプロンプトに渡します。
Inbox(メールの分離)
インストール:npm install -D @stablyai/email。STABLY_API_KEY と STABLY_PROJECT_ID の環境変数が必要です(または Inbox.build() に渡します)。
const inbox = await Inbox.build({ suffix: `test-${Date.now()}` });
// inbox.address → "[email protected]"
await page.getByLabel("Email").describe("Email input").fill(inbox.address);
const email = await inbox.waitForEmail({ subject: "verification", timeoutMs: 60_000 });
const { data: otp } = await inbox.extractFromEmail({
id: email.id,
prompt: "Extract the 6-digit OTP code",
});
await inbox.deleteAllEmails();
Inbox.build のオプション
| オプション | 型 | 説明 |
|---|---|---|
suffix | string | テストを分離するためのサフィックス(例:"test-123" → "[email protected]") |
apiKey | string | 既定では STABLY_API_KEY 環境変数を使用します |
projectId | string | 既定では STABLY_PROJECT_ID 環境変数を使用します |
並列実行時に分離できるよう、各テストで一意の suffix を必ず使用してください。受信ボックスは、作成前に受信したメールを自動的に除外します。
waitForEmail
const email = await inbox.waitForEmail({
from: "[email protected]", // filter by sender
subject: "verification", // contains match by default
subjectMatch: "exact", // or "contains" (default)
timeoutMs: 60_000, // default: 120000 (2 min)
pollIntervalMs: 5000, // default: 3000 (3 sec)
});
タイムアウト時間内に一致するメールが届かない場合は、EmailTimeoutError がスローされます。
extractFromEmail
{ data, reason } を返します。失敗すると EmailExtractionError がスローされます。
// String extraction
const { data: otp } = await inbox.extractFromEmail({
id: email.id,
prompt: "Extract the 6-digit OTP code",
});
// Structured extraction with Zod schema
import { z } from "zod";
const { data } = await inbox.extractFromEmail({
id: email.id,
prompt: "Extract the verification URL and expiration time",
schema: z.object({ url: z.string().url(), expiresIn: z.string() }),
});
Inbox のプロパティ
| プロパティ | 型 | 説明 |
|---|---|---|
address | string | メールアドレス全体(指定した場合はサフィックス付き) |
suffix | string | undefined | Inbox.build() に渡されたサフィックス |
createdAt | Date | 受信ボックスの作成時刻。この時刻より前のメールは自動的に除外されます |
listEmails
const { emails, nextCursor } = await inbox.listEmails(options?);
| オプション | 型 | 既定値 | 説明 |
|---|---|---|---|
from | string | — | 送信者のアドレスでフィルターします |
subject | string | — | 件名でフィルターします |
subjectMatch | 'contains' | 'exact' | 'contains' | 件名の照合モード |
limit | number | 20 | 結果の最大数(最大:100) |
cursor | string | — | 前回の nextCursor によるページネーションカーソル |
since | Date | — | 既定の作成時刻フィルターを上書きします |
includeOlder | boolean | false | 受信ボックスの作成前に受信したメールを含めます |
その他のメソッド
const email = await inbox.getEmail(id); // get by ID
await inbox.deleteEmail(email.id); // delete single
await inbox.deleteAllEmails(); // delete all (this inbox only)
メールオブジェクトのプロパティ
| プロパティ | 型 | 説明 |
|---|---|---|
id | string | 一意の識別子 |
mailbox | string | コンテナ(例:"INBOX") |
from | { address: string, name?: string } | 送信者 |
to | { address: string, name?: string }[] | 宛先 |
subject | string | 件名 |
receivedAt | Date | 受信日時 |
text | string? | プレーンテキスト本文 |
html | string[]? | HTML 本文パーツ |
Playwright フィクスチャパターン
import { test as base } from "@stablyai/playwright-test";
import { Inbox } from "@stablyai/email";
const test = base.extend<{ inbox: Inbox }>({
inbox: async ({}, use, testInfo) => {
const inbox = await Inbox.build({ suffix: `test-${testInfo.testId}` });
await use(inbox);
await inbox.deleteAllEmails();
},
});
test("signup flow", async ({ page, inbox }) => {
await page.fill("#email", inbox.address);
await page.click("#signup");
const email = await inbox.waitForEmail({ subject: "Welcome" });
// ...
});
組織のメールアドレスを確認する
メールアドレスは Stably ダッシュボードで確認できます。
- Settings > Email Inbox:コピー用ボタン付きで、アドレス全体が表示されます
- コード内:
Inbox.build()を呼び出した後、inbox.addressを使うとアドレス全体を取得できます
アドレスの形式は {org-name}@mail.stably.ai です。ユーザーが許可リストへの登録を必要とする場合は、メールプロバイダーの許可リストに mail.stably.ai を追加してください。
ユーザーには、Settings > Email Inbox に移動して個別のアドレスを確認するよう案内してください。
認証フロー(Google)
ポップアップのカスタムスクリプトではなく、ヘルパーを使用してください。
import { authWithGoogle } from "@stablyai/playwright-test/auth";
await authWithGoogle({
context,
email: process.env.GOOGLE_AUTH_EMAIL!,
password: process.env.GOOGLE_AUTH_PASSWORD!,
otpSecret: process.env.GOOGLE_AUTH_OTP_SECRET!,
});
必須の環境変数:
GOOGLE_AUTH_EMAILGOOGLE_AUTH_PASSWORDGOOGLE_AUTH_OTP_SECRET
専用のテスト用 Google アカウントのみを使用してください。
トラブルシューティング(概要)
aiAssertの処理が遅い/不安定:ロケーターの対象範囲を絞り、プロンプトを具体化し、不要なfullPage: trueを避けてください。agent.actが失敗する:タスクを小さく分割し、明示的な制約を渡し、必要な場合に限ってmaxCyclesを増やしてください。- メールのタイムアウト:件名/送信者のフィルターを確認し、一意の受信トレイサフィックスを使用してください。
完全なリファレンス
- Stably のドキュメント:https://docs.stably.ai
- SDK セットアップスキル:
skills/stably-sdk-setup/SKILL.md - パッケージのドキュメント:https://www.npmjs.com/package/@stablyai/playwright-test
- メールパッケージのドキュメント:https://www.npmjs.com/package/@stablyai/email
- ローカル概要:
skills/stably-sdk-rules/README.md