Loading...
Loading...
AI rules for writing tests with Stably Playwright SDK. Use this skill when writing or modifying Playwright tests with Stably AI features. Covers when to use Playwright vs Stably methods, plus minimal patterns for aiAssert, extract, getLocatorsByAI, agent.act, Inbox, and Google auth.
npx skill4agent add stablyai/agent-skills stably-sdk-rulesagent.act()expect(...).aiAssert()page.extract()locator.extract()page.getLocatorsByAI()Inboxagent.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_IDimport { test, expect } from "@stablyai/playwright-test";
import { Inbox } from "@stablyai/email";import { setApiKey } from "@stablyai/playwright-test";
setApiKey("YOUR_KEY");.describe("...")aiAssertagent.actaiAssertawait 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: trueextractconst 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>= 1.54.1const { locator, count } = await page.getLocatorsByAI("the login button");
expect(count).toBe(1);
await locator.describe("Login button located by AI").click();agent.actawait agent.act("Find the first pending order and mark it as shipped", { page });Inboxnpm install -D @stablyai/emailSTABLY_API_KEYSTABLY_PROJECT_IDInbox.build()const inbox = await Inbox.build({ suffix: `test-${Date.now()}` });
// inbox.address → "my-org+test-1706621234567@mail.stably.ai"
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();| Option | Type | Description |
|---|---|---|
| string | Suffix for test isolation (e.g., |
| string | Defaults to |
| string | Defaults to |
suffixconst email = await inbox.waitForEmail({
from: "noreply@example.com", // 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{ 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() }),
});| Property | Type | Description |
|---|---|---|
| string | Full email address (with suffix if provided) |
| string | undefined | The suffix passed to |
| Date | Inbox creation time; emails before this are auto-filtered |
const { emails, nextCursor } = await inbox.listEmails(options?);| Option | Type | Default | Description |
|---|---|---|---|
| string | — | Filter by sender address |
| string | — | Filter by subject |
| | | Subject matching mode |
| number | 20 | Max results (max: 100) |
| string | — | Pagination cursor from previous |
| Date | — | Override the default creation-time filter |
| boolean | false | Include emails received before inbox creation |
const email = await inbox.getEmail(id); // get by ID
await inbox.deleteEmail(email.id); // delete single
await inbox.deleteAllEmails(); // delete all (this inbox only)| Property | Type | Description |
|---|---|---|
| string | Unique identifier |
| string | Container (e.g., |
| | Sender |
| | Recipients |
| string | Subject line |
| Date | Arrival timestamp |
| string? | Plain text body |
| string[]? | HTML body parts |
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" });
// ...
});inbox.addressInbox.build(){org-name}@mail.stably.aimail.stably.aiimport { 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_SECRETaiAssertfullPage: trueagent.actmaxCyclesskills/stably-sdk-setup/SKILL.mdskills/stably-sdk-rules/README.md