Loading...
Loading...
Compare original and translation side by side
agent.act()expect(...).aiAssert()page.extract()locator.extract()page.getLocatorsByAI()Inboxagent.act()fullPage: true.describe("...")Inbox.build({ suffix })agent.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");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.act.describe("...")aiAssertagent.actaiAssertaiAssertawait 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: trueawait 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: trueextractextractconst 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 });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 });getLocatorsByAIgetLocatorsByAI>= 1.54.1const { locator, count } = await page.getLocatorsByAI("the login button");
expect(count).toBe(1);
await locator.describe("Login button located by AI").click();>= 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.actagent.actawait agent.act("Find the first pending order and mark it as shipped", { page });await agent.act("Find the first pending order and mark it as shipped", { page });InboxInboxnpm 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();npm 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 |
suffix| 选项 | 类型 | 描述 |
|---|---|---|
| string | 用于测试隔离的后缀(例如: |
| string | 默认使用环境变量 |
| string | 默认使用环境变量 |
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)
});EmailTimeoutErrorconst email = await inbox.waitForEmail({
from: "noreply@example.com", // 按发件人过滤
subject: "verification", // 默认包含匹配
subjectMatch: "exact", // 可选"contains"(默认值)
timeoutMs: 60_000, // 默认值:120000(2分钟)
pollIntervalMs: 5000, // 默认值:3000(3秒)
});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() }),
});{ data, reason }EmailExtractionError// 字符串提取
const { data: otp } = await inbox.extractFromEmail({
id: email.id,
prompt: "Extract the 6-digit OTP code",
});
// 结合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 |
| 属性 | 类型 | 描述 |
|---|---|---|
| string | 完整邮箱地址(如果提供了后缀则包含后缀) |
| string | undefined | 传入 |
| Date | 收件箱创建时间;在此时间之前的邮件会被自动过滤 |
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 { emails, nextCursor } = await inbox.listEmails(options?);| 选项 | 类型 | 默认值 | 描述 |
|---|---|---|---|
| string | — | 按发件人地址过滤 |
| string | — | 按主题过滤 |
| | | 主题匹配模式 |
| number | 20 | 最大结果数(上限:100) |
| string | — | 来自上一次调用 |
| Date | — | 覆盖默认的创建时间过滤条件 |
| 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)const email = await inbox.getEmail(id); // 通过ID获取邮件
await inbox.deleteEmail(email.id); // 删除单封邮件
await inbox.deleteAllEmails(); // 删除当前收件箱的所有邮件| 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 |
| 属性 | 类型 | 描述 |
|---|---|---|
| string | 唯一标识符 |
| string | 邮件容器(例如: |
| | 发件人信息 |
| | 收件人列表 |
| string | 邮件主题 |
| Date | 邮件接收时间戳 |
| string? | 纯文本邮件内容 |
| string[]? | HTML格式的邮件内容片段 |
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" });
// ...
});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.aiInbox.build()inbox.address{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_SECRETimport { 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.actmaxCyclesaiAssertfullPage: trueagent.actmaxCyclesskills/stably-sdk-setup/SKILL.mdskills/stably-sdk-rules/README.mdskills/stably-sdk-setup/SKILL.mdskills/stably-sdk-rules/README.md