feat(playwright): add aiTap/aiInput and other AI actions for Playwright integration (#489)

* feat(playwright): add aiTap/aiInput and other AI actions for Playwright integration

* chore: update doc

---------

Co-authored-by: yutao <yutao.tao@bytedance.com>
This commit is contained in:
Zhou Xiao 2025-03-24 19:30:53 +08:00 committed by GitHub
parent 649aeceb43
commit 36d47e4aef
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 627 additions and 128 deletions

View File

@ -2,30 +2,30 @@
import { PackageManagerTabs } from '@theme';
[Playwright.js](https://playwright.com/) is an open-source automation library developed by Microsoft, primarily designed for end-to-end testing and web scraping of web applications.
[Playwright.js](https://playwright.com/) is an open-source automation library developed by Microsoft, primarily used for end-to-end testing and web scraping of web applications.
We assume you already have a project with Playwright.
Here we assume you already have a repository with Playwright integration.
:::info Demo Project
you can check the demo project of Playwright here: [https://github.com/web-infra-dev/midscene-example/blob/main/playwright-demo](https://github.com/web-infra-dev/midscene-example/blob/main/playwright-demo)
:::info Example Project
You can find an example project of Playwright integration here: [https://github.com/web-infra-dev/midscene-example/blob/main/playwright-demo](https://github.com/web-infra-dev/midscene-example/blob/main/playwright-demo)
:::
## Preparation
## Prerequisites
Config the OpenAI API key, or [config model and provider](./model-provider)
Configure OpenAI API Key, or [Custom Model and Provider](./model-provider)
```bash
# replace with your own
# Update with your own Key
export OPENAI_API_KEY="sk-abcdefghijklmnopqrstuvwxyz"
```
## Step 1. install dependency, update configuration
## Step 1: Add Dependencies and Update Configuration
add the dependency
Add dependencies
<PackageManagerTabs command="install @midscene/web --save-dev" />
update playwright.config.ts
Update playwright.config.ts
```diff
export default defineConfig({
@ -35,9 +35,9 @@ export default defineConfig({
});
```
## Step 2. extend the `test` instance
## Step 2: Extend the `test` Instance
Save the following code as `./e2e/fixture.ts`;
Save the following code as `./e2e/fixture.ts`:
```typescript
import { test as base } from '@playwright/test';
@ -47,49 +47,236 @@ import { PlaywrightAiFixture } from '@midscene/web/playwright';
export const test = base.extend<PlayWrightAiFixtureType>(PlaywrightAiFixture());
```
## Step 3. write the test case
## Step 3: Write Test Cases
Save the following code as `./e2e/ebay-search.spec.ts`
### Basic AI Operation APIs
#### `ai` - General AI Command
```typescript
ai<T = any>(
prompt: string,
opts?: {
type?: 'action' | 'query'; // Specify operation type
trackNewTab?: boolean; // Whether to track new tabs
}
): Promise<T>
```
Used to execute general AI commands, handling various interaction scenarios.
#### `aiAction` - Execute AI Action
```typescript
aiAction(taskPrompt: string): Promise<void>
```
Execute specific AI actions, such as clicking, inputting, etc.
#### `aiTap` - Click Operation
```typescript
aiTap(
target: string | {
prompt: string; // Target element description
searchArea?: string; // Search area
deepThink?: boolean; // Whether to think deeply
},
options?: { // Optional configuration
timeout?: number; // Timeout duration
retry?: number; // Retry attempts
force?: boolean; // Whether to force click
}
): Promise<void>
```
Execute click operation, AI will intelligently identify target elements.
#### `aiHover` - Hover Operation
```typescript
aiHover(
target: string | {
prompt: string; // Target element description
searchArea?: string; // Search area
deepThink?: boolean; // Whether to think deeply
},
options?: { // Optional configuration
timeout?: number; // Timeout duration
retry?: number; // Retry attempts
}
): Promise<void>
```
Execute mouse hover operation.
#### `aiInput` - Input Operation
```typescript
aiInput(
text: string, // Text to input
target: string | {
prompt: string; // Target element description
searchArea?: string; // Search area
deepThink?: boolean; // Whether to think deeply
},
options?: { // Optional configuration
timeout?: number; // Timeout duration
retry?: number; // Retry attempts
clear?: boolean; // Whether to clear input first
}
): Promise<void>
```
Execute text input operation.
#### `aiKeyboardPress` - Keyboard Operation
```typescript
aiKeyboardPress(
key: string, // Key name
target?: string | {
prompt: string; // Target element description
searchArea?: string; // Search area
deepThink?: boolean; // Whether to think deeply
},
options?: { // Optional configuration
timeout?: number; // Timeout duration
retry?: number; // Retry attempts
}
): Promise<void>
```
Execute keyboard key press operation.
#### `aiScroll` - Scroll Operation
```typescript
aiScroll(
scroll: {
direction: 'down' | 'up' | 'right' | 'left'; // Scroll direction
scrollType: 'once' | 'untilBottom' | 'untilTop' | 'untilRight' | 'untilLeft'; // Scroll type
distance?: number; // Scroll distance
},
target?: string | {
prompt: string; // Target element description
searchArea?: string; // Search area
deepThink?: boolean; // Whether to think deeply
},
options?: { // Optional configuration
timeout?: number; // Timeout duration
retry?: number; // Retry attempts
}
): Promise<void>
```
Execute page scroll operation.
### Advanced APIs
#### `generateMidsceneAgent` - Generate AI Agent
```typescript
generateMidsceneAgent(
page?: Page, // Optional page instance
opts?: { // Agent configuration options
selector?: string; // Selector
ignoreMarker?: boolean; // Whether to ignore markers
forceSameTabNavigation?: boolean; // Whether to force navigation in the same tab
bridgeMode?: false | 'newTabWithUrl' | 'currentTab'; // Bridge mode
closeNewTabsAfterDisconnect?: boolean; // Whether to close new tabs after disconnection
}
): Promise<PageAgent>
```
Generate an independent AI Agent instance for more complex interaction scenarios.
### Query and Assertion APIs
#### `aiQuery` - AI Query
```typescript
aiQuery<T = any>(
query: string | Record<string, string>, // Query description or structured query
options?: { // Optional configuration
timeout?: number; // Timeout duration
retry?: number; // Retry attempts
}
): Promise<T>
```
Use AI to execute query operations, returning structured data.
#### `aiAssert` - AI Assertion
```typescript
aiAssert(
assertion: string, // Assertion description
options?: { // Optional configuration
timeout?: number; // Timeout duration
retry?: number; // Retry attempts
keepRawResponse?: boolean; // Whether to keep raw response
}
): Promise<void>
```
Use AI to execute assertion checks.
#### `aiWaitFor` - AI Wait
```typescript
aiWaitFor(
assertion: string, // Wait condition description
options?: { // Wait options
checkIntervalMs?: number; // Check interval
timeoutMs?: number; // Timeout duration
}
): Promise<void>
```
Wait for specific conditions to be met.
### Example Code
```typescript title="./e2e/ebay-search.spec.ts"
import { expect } from "@playwright/test";
import { test } from "./fixture";
test.beforeEach(async ({ page }) => {
page.setViewportSize({ width: 1280, height: 800 });
page.setViewportSize({ width: 400, height: 905 });
await page.goto("https://www.ebay.com");
await page.waitForLoadState("networkidle");
});
test("search headphone on ebay", async ({ ai, aiQuery, aiAssert }) => {
// 👀 type keywords, perform a search
await ai('type "Headphones" in search box, hit Enter');
// 👀 find the items
const items = await aiQuery(
"{itemTitle: string, price: Number}[], find item in list and corresponding price"
test("search headphone on ebay", async ({
ai,
aiQuery,
aiAssert,
aiInput,
aiTap,
aiScroll
}) => {
// Use aiInput to enter search keyword
await aiInput('Headphones', 'search box', { clear: true });
// Use aiTap to click search button
await aiTap('search button');
// Wait for search results to load
await aiWaitFor('search results list loaded', { timeoutMs: 5000 });
// Use aiScroll to scroll to bottom
await aiScroll(
{
direction: 'down',
scrollType: 'untilBottom'
},
'search results list'
);
// Use aiQuery to get product information
const items = await aiQuery<Array<{title: string, price: number}>>(
'get product titles and prices from search results'
);
console.log("headphones in stock", items);
expect(items?.length).toBeGreaterThan(0);
// 👀 assert by AI
await aiAssert("There is a category filter on the left");
// Use aiAssert to verify filter functionality
await aiAssert("category filter exists on the left side");
});
```
For the agent's more APIs, please refer to [API](./API).
For more Agent API details, please refer to [API Reference](./API).
## Step 4. run the test case
## Step 4. Run Test Cases
```bash
npx playwright test ./e2e/ebay-search.spec.ts
```
## Step 5. view test report
## Step 5. View Test Report
After the above command executes successfully, the console will output: `Midscene - report file updated: ./current_cwd/midscene_run/report/some_id.html`. You can open this file in a browser to view the report.
After the command executes successfully, it will output: `Midscene - report file updated: ./current_cwd/midscene_run/report/some_id.html`. Open this file in your browser to view the report.
## More
You may also be interested in [Prompting Tips](./prompting-tips)
You might also want to learn about [Prompting Tips](./prompting-tips)

View File

@ -50,7 +50,172 @@ export const test = base.extend<PlayWrightAiFixtureType>(PlaywrightAiFixture());
## 第三步:编写测试用例
编写下方代码,保存为 `./e2e/ebay-search.spec.ts`
### 基础 AI 操作 API
#### `ai` - 通用 AI 交互
```typescript
ai<T = any>(
prompt: string,
opts?: {
type?: 'action' | 'query'; // 指定操作类型
trackNewTab?: boolean; // 是否追踪新标签页
}
): Promise<T>
```
用于执行通用的 AI 指令,可以处理各种交互场景。
#### `aiAction` - 执行 AI 动作
```typescript
aiAction(taskPrompt: string): Promise<void>
```
执行特定的 AI 动作,如点击、输入等。
#### `aiTap` - 点击操作
```typescript
aiTap(
target: string | {
prompt: string; // 目标元素描述
searchArea?: string; // 搜索区域
deepThink?: boolean; // 是否深度思考
},
options?: { // 可选配置
timeout?: number; // 超时时间
retry?: number; // 重试次数
force?: boolean; // 是否强制点击
}
): Promise<void>
```
执行点击操作AI 会智能识别目标元素。
#### `aiHover` - 悬停操作
```typescript
aiHover(
target: string | {
prompt: string; // 目标元素描述
searchArea?: string; // 搜索区域
deepThink?: boolean; // 是否深度思考
},
options?: { // 可选配置
timeout?: number; // 超时时间
retry?: number; // 重试次数
}
): Promise<void>
```
执行鼠标悬停操作。
#### `aiInput` - 输入操作
```typescript
aiInput(
text: string, // 要输入的文本
target: string | {
prompt: string; // 目标元素描述
searchArea?: string; // 搜索区域
deepThink?: boolean; // 是否深度思考
},
options?: { // 可选配置
timeout?: number; // 超时时间
retry?: number; // 重试次数
clear?: boolean; // 是否先清空输入框
}
): Promise<void>
```
执行文本输入操作。
#### `aiKeyboardPress` - 键盘操作
```typescript
aiKeyboardPress(
key: string, // 按键名称
target?: string | {
prompt: string; // 目标元素描述
searchArea?: string; // 搜索区域
deepThink?: boolean; // 是否深度思考
},
options?: { // 可选配置
timeout?: number; // 超时时间
retry?: number; // 重试次数
}
): Promise<void>
```
执行键盘按键操作。
#### `aiScroll` - 滚动操作
```typescript
aiScroll(
scroll: {
direction: 'down' | 'up' | 'right' | 'left'; // 滚动方向
scrollType: 'once' | 'untilBottom' | 'untilTop' | 'untilRight' | 'untilLeft'; // 滚动类型
distance?: number; // 滚动距离
},
target?: string | {
prompt: string; // 目标元素描述
searchArea?: string; // 搜索区域
deepThink?: boolean; // 是否深度思考
},
options?: { // 可选配置
timeout?: number; // 超时时间
retry?: number; // 重试次数
}
): Promise<void>
```
执行页面滚动操作。
### 高级 API
#### `generateMidsceneAgent` - 生成 AI Agent
```typescript
generateMidsceneAgent(
page?: Page, // 可选的页面实例
opts?: { // Agent 配置选项
selector?: string; // 选择器
ignoreMarker?: boolean; // 是否忽略标记
forceSameTabNavigation?: boolean; // 是否强制在同一标签页导航
bridgeMode?: false | 'newTabWithUrl' | 'currentTab'; // 桥接模式
closeNewTabsAfterDisconnect?: boolean; // 断开连接后是否关闭新标签页
}
): Promise<PageAgent>
```
生成一个独立的 AI Agent 实例,可以用于更复杂的交互场景。
### 查询和断言 API
#### `aiQuery` - AI 查询
```typescript
aiQuery<T = any>(
query: string | Record<string, string>, // 查询描述或结构化查询
options?: { // 可选配置
timeout?: number; // 超时时间
retry?: number; // 重试次数
}
): Promise<T>
```
使用 AI 执行查询操作,返回结构化数据。
#### `aiAssert` - AI 断言
```typescript
aiAssert(
assertion: string, // 断言描述
options?: { // 可选配置
timeout?: number; // 超时时间
retry?: number; // 重试次数
keepRawResponse?: boolean; // 是否保留原始响应
}
): Promise<void>
```
使用 AI 执行断言检查。
#### `aiWaitFor` - AI 等待
```typescript
aiWaitFor(
assertion: string, // 等待条件描述
options?: { // 等待选项
checkIntervalMs?: number; // 检查间隔
timeoutMs?: number; // 超时时间
}
): Promise<void>
```
等待特定条件满足。
### 示例代码
```typescript title="./e2e/ebay-search.spec.ts"
import { expect } from "@playwright/test";
@ -62,20 +227,41 @@ test.beforeEach(async ({ page }) => {
await page.waitForLoadState("networkidle");
});
test("search headphone on ebay", async ({ ai, aiQuery, aiAssert }) => {
// 👀 输入关键字,执行搜索
// 注:尽管这是一个英文页面,你也可以用中文指令控制它
await ai('在搜索框输入 "Headphones" ,敲回车');
// 👀 找到列表里耳机相关的信息
const items = await aiQuery(
'{itemTitle: string, price: Number}[], 找到列表里的商品标题和价格'
test("search headphone on ebay", async ({
ai,
aiQuery,
aiAssert,
aiInput,
aiTap,
aiScroll
}) => {
// 使用 aiInput 输入搜索关键词
await aiInput('Headphones', '搜索框', { clear: true });
// 使用 aiTap 点击搜索按钮
await aiTap('搜索按钮');
// 等待搜索结果加载
await aiWaitFor('搜索结果列表已加载', { timeoutMs: 5000 });
// 使用 aiScroll 滚动到页面底部
await aiScroll(
{
direction: 'down',
scrollType: 'untilBottom'
},
'搜索结果列表'
);
// 使用 aiQuery 获取商品信息
const items = await aiQuery<Array<{title: string, price: number}>>(
'获取搜索结果中的商品标题和价格'
);
console.log("headphones in stock", items);
expect(items?.length).toBeGreaterThan(0);
// 👀 用 AI 断言
// 使用 aiAssert 验证筛选功能
await aiAssert("界面左侧有类目筛选功能");
});
```

View File

@ -1,5 +1,5 @@
import { randomUUID } from 'node:crypto';
import type { PageAgent } from '@/common/agent';
import type { PageAgent, PageAgentOpt } from '@/common/agent';
import { PlaywrightAgent } from '@/playwright/index';
import type { AgentWaitForOpt } from '@midscene/core';
import { type TestInfo, type TestType, test } from '@playwright/test';
@ -28,6 +28,7 @@ const groupAndCaseForTest = (testInfo: TestInfo) => {
const midsceneAgentKeyId = '_midsceneAgentId';
export const midsceneDumpAnnotationId = 'MIDSCENE_DUMP_ANNOTATION';
export const PlaywrightAiFixture = (options?: {
forceSameTabNavigation?: boolean;
}) => {
@ -36,6 +37,7 @@ export const PlaywrightAiFixture = (options?: {
const agentForPage = (
page: OriginPlaywrightPage,
testInfo: TestInfo, // { testId: string; taskFile: string; taskTitle: string },
opts?: PageAgentOpt,
) => {
let idForPage = (page as any)[midsceneAgentKeyId];
if (!idForPage) {
@ -50,11 +52,53 @@ export const PlaywrightAiFixture = (options?: {
groupName: taskTitle,
groupDescription: taskFile,
generateReport: false, // we will generate it in the reporter
...opts,
});
}
return pageAgentMap[idForPage];
};
async function generateAiAction(options: {
page: OriginPlaywrightPage;
testInfo: TestInfo;
use: any;
aiActionType:
| 'ai'
| 'aiAction'
| 'aiHover'
| 'aiInput'
| 'aiKeyboardPress'
| 'aiScroll'
| 'aiTap'
| 'aiQuery'
| 'aiAssert'
| 'aiWaitFor';
}) {
const { page, testInfo, use, aiActionType } = options;
const agent = agentForPage(page, testInfo);
await use(async (taskPrompt: string, ...args: any[]) => {
return new Promise((resolve, reject) => {
test.step(`ai-${aiActionType} - ${JSON.stringify(taskPrompt)}`, async () => {
await waitForNetworkIdle(page);
try {
type AgentMethod = (
prompt: string,
...restArgs: any[]
) => Promise<any>;
const result = await (agent[aiActionType] as AgentMethod)(
taskPrompt,
...(args || []),
);
resolve(result);
} catch (error) {
reject(error);
}
});
});
});
updateDumpAnnotation(testInfo, agent.dumpDataString());
}
const updateDumpAnnotation = (test: TestInfo, dump: string) => {
const currentAnnotation = test.annotations.find((item) => {
return item.type === midsceneDumpAnnotationId;
@ -70,130 +114,172 @@ export const PlaywrightAiFixture = (options?: {
};
return {
generateMidsceneAgent: async (
{ page }: { page: OriginPlaywrightPage },
use: any,
testInfo: TestInfo,
) => {
await use(
async (
propsPage?: OriginPlaywrightPage | undefined,
opts?: PageAgentOpt,
) => {
const agent = agentForPage(propsPage || page, testInfo, opts);
return agent;
},
);
},
ai: async (
{ page }: { page: OriginPlaywrightPage },
use: any,
testInfo: TestInfo,
) => {
const agent = agentForPage(page, testInfo);
await use(
async (
taskPrompt: string,
opts?: { type?: 'action' | 'query'; trackNewTab?: boolean },
) => {
return new Promise((resolve, reject) => {
const { type = 'action' } = opts || {};
test.step(`ai - ${taskPrompt}`, async () => {
await waitForNetworkIdle(page);
try {
const result = await agent.ai(taskPrompt, type);
resolve(result);
} catch (error) {
reject(error);
}
});
});
},
);
updateDumpAnnotation(testInfo, agent.dumpDataString());
await generateAiAction({
page,
testInfo,
use,
aiActionType: 'ai',
});
},
aiAction: async (
{ page }: { page: OriginPlaywrightPage },
use: any,
testInfo: TestInfo,
) => {
const agent = agentForPage(page, testInfo);
await use(async (taskPrompt: string) => {
return new Promise((resolve, reject) => {
test.step(`aiAction - ${taskPrompt}`, async () => {
await waitForNetworkIdle(page);
try {
const result = await agent.aiAction(taskPrompt);
resolve(result);
} catch (error) {
reject(error);
}
});
});
await generateAiAction({
page,
testInfo,
use,
aiActionType: 'aiAction',
});
},
aiTap: async (
{ page }: { page: OriginPlaywrightPage },
use: any,
testInfo: TestInfo,
) => {
await generateAiAction({
page,
testInfo,
use,
aiActionType: 'aiTap',
});
},
aiHover: async (
{ page }: { page: OriginPlaywrightPage },
use: any,
testInfo: TestInfo,
) => {
await generateAiAction({
page,
testInfo,
use,
aiActionType: 'aiHover',
});
},
aiInput: async (
{ page }: { page: OriginPlaywrightPage },
use: any,
testInfo: TestInfo,
) => {
await generateAiAction({
page,
testInfo,
use,
aiActionType: 'aiInput',
});
},
aiKeyboardPress: async (
{ page }: { page: OriginPlaywrightPage },
use: any,
testInfo: TestInfo,
) => {
await generateAiAction({
page,
testInfo,
use,
aiActionType: 'aiKeyboardPress',
});
},
aiScroll: async (
{ page }: { page: OriginPlaywrightPage },
use: any,
testInfo: TestInfo,
) => {
await generateAiAction({
page,
testInfo,
use,
aiActionType: 'aiScroll',
});
updateDumpAnnotation(testInfo, agent.dumpDataString());
},
aiQuery: async (
{ page }: { page: OriginPlaywrightPage },
use: any,
testInfo: TestInfo,
) => {
const agent = agentForPage(page, testInfo);
await use(async (demand: any) => {
return new Promise((resolve, reject) => {
test.step(`aiQuery - ${JSON.stringify(demand)}`, async () => {
await waitForNetworkIdle(page);
try {
const result = await agent.aiQuery(demand);
resolve(result);
} catch (error) {
reject(error);
}
});
});
await generateAiAction({
page,
testInfo,
use,
aiActionType: 'aiQuery',
});
updateDumpAnnotation(testInfo, agent.dumpDataString());
},
aiAssert: async (
{ page }: { page: OriginPlaywrightPage },
use: any,
testInfo: TestInfo,
) => {
const agent = agentForPage(page, testInfo);
await use(async (assertion: string, errorMsg?: string) => {
return new Promise((resolve, reject) => {
test.step(`aiAssert - ${assertion}`, async () => {
await waitForNetworkIdle(page);
try {
await agent.aiAssert(assertion, errorMsg);
resolve(null);
} catch (error) {
reject(error);
}
});
});
await generateAiAction({
page,
testInfo,
use,
aiActionType: 'aiAssert',
});
updateDumpAnnotation(testInfo, agent.dumpDataString());
},
aiWaitFor: async (
{ page }: { page: OriginPlaywrightPage },
use: any,
testInfo: TestInfo,
) => {
const agent = agentForPage(page, testInfo);
await use(async (assertion: string, opt?: AgentWaitForOpt) => {
return new Promise((resolve, reject) => {
test.step(`aiWaitFor - ${assertion}`, async () => {
await waitForNetworkIdle(page);
try {
await agent.aiWaitFor(assertion, opt);
resolve(null);
} catch (error) {
reject(error);
}
});
});
await generateAiAction({
page,
testInfo,
use,
aiActionType: 'aiWaitFor',
});
updateDumpAnnotation(testInfo, agent.dumpDataString());
},
};
};
export type PlayWrightAiFixtureType = {
generateMidsceneAgent: (page?: any, opts?: any) => Promise<PageAgent>;
ai: <T = any>(
prompt: string,
opts?: { type?: 'action' | 'query'; trackNewTab?: boolean },
) => Promise<T>;
aiAction: (taskPrompt: string) => ReturnType<PageTaskExecutor['action']>;
aiQuery: <T = any>(demand: any) => Promise<T>;
aiAssert: (assertion: string, errorMsg?: string) => Promise<void>;
aiAction: (taskPrompt: string) => ReturnType<PageAgent['aiAction']>;
aiTap: (
...args: Parameters<PageAgent['aiTap']>
) => ReturnType<PageAgent['aiTap']>;
aiHover: (
...args: Parameters<PageAgent['aiHover']>
) => ReturnType<PageAgent['aiHover']>;
aiInput: (
...args: Parameters<PageAgent['aiInput']>
) => ReturnType<PageAgent['aiInput']>;
aiKeyboardPress: (
...args: Parameters<PageAgent['aiKeyboardPress']>
) => ReturnType<PageAgent['aiKeyboardPress']>;
aiScroll: (
...args: Parameters<PageAgent['aiScroll']>
) => ReturnType<PageAgent['aiScroll']>;
aiQuery: <T = any>(
...args: Parameters<PageAgent['aiQuery']>
) => ReturnType<PageAgent['aiQuery']>;
aiAssert: (
...args: Parameters<PageAgent['aiAssert']>
) => ReturnType<PageAgent['aiAssert']>;
aiWaitFor: (assertion: string, opt?: AgentWaitForOpt) => Promise<void>;
};

View File

@ -0,0 +1,40 @@
import { AiAssert } from '@midscene/core/.';
import { expect } from 'playwright/test';
import { test } from './fixture';
test.beforeEach(async ({ page }) => {
await page.goto('https://www.saucedemo.com/');
await page.setViewportSize({ width: 1920, height: 1080 });
});
const CACHE_TIME_OUT = process.env.MIDSCENE_CACHE;
test('ai shop', async ({
ai,
aiInput,
aiAssert,
aiQuery,
aiTap,
generateMidsceneAgent,
page,
}) => {
if (CACHE_TIME_OUT) {
test.setTimeout(1000 * 1000);
}
// login
const agent = await generateMidsceneAgent(page);
await aiInput('standard_user', 'in user name input');
await aiInput('secret_sauce', 'in password input');
await agent.aiTap('Login Button');
// check the login success
await aiAssert('the page title is "Swag Labs"');
// add to cart
await aiTap('"add to cart" for black t-shirt products');
await aiTap({
prompt: 'click right top cart icon',
deepThink: true,
});
});

View File

@ -1,5 +1,5 @@
import type { PlayWrightAiFixtureType } from '@/index';
import { PlaywrightAiFixture } from '@/index';
import type { PlayWrightAiFixtureType } from '@/playwright/ai-fixture';
import { PlaywrightAiFixture } from '@/playwright/ai-fixture';
import { test as base } from '@playwright/test';
export const test = base.extend<PlayWrightAiFixtureType>(PlaywrightAiFixture());