Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions apps/site/docs/en/integrate-with-playwright.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -170,10 +170,14 @@ import { PlaywrightAiFixture } from '@midscene/web/playwright';
export const test = base.extend<PlayWrightAiFixtureType>(
PlaywrightAiFixture({
waitForNetworkIdleTimeout: 2000, // optional, the timeout for waiting for network idle between each action, default is 2000ms
replanningCycleLimit: 30, // optional, override the default aiAct replanning cycle limit
}),
);
```

`PlaywrightAiFixture()` also accepts Agent initialization options (for example `replanningCycleLimit`, `waitAfterAction`, and `modelConfig`) in addition to fixture-specific options. See [Agent API](./api) for the full option list.


### Step 3: Write test cases

Review the full catalog of action, query, and utility methods in the [Agent API reference](./api#interaction-methods). When you need lower-level control, you can use `agentForPage` to obtain the underlying `PageAgent` instance and call any API directly:
Expand Down
4 changes: 4 additions & 0 deletions apps/site/docs/zh/integrate-with-playwright.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -170,10 +170,14 @@ import { PlaywrightAiFixture } from '@midscene/web/playwright';
export const test = base.extend<PlayWrightAiFixtureType>(
PlaywrightAiFixture({
waitForNetworkIdleTimeout: 2000, // 可选, 交互过程中等待网络空闲的超时时间, 默认值为 2000ms, 设置为 0 则禁用超时
replanningCycleLimit: 30, // 可选,覆盖 aiAct 默认的重规划次数上限
}),
);
```

`PlaywrightAiFixture()` 除了 fixture 自身配置外,也支持传入 Agent 初始化参数(例如 `replanningCycleLimit`、`waitAfterAction`、`modelConfig`)。完整参数列表请参考 [Agent API 文档](./api)。


### 第三步:编写测试用例

完整的交互、查询和辅助 API 请参考 [Agent API 参考](./api#interaction-methods)。如果需要调用更底层的能力,可以使用 `agentForPage` 获取 `PageAgent` 实例,再直接调用对应的方法:
Expand Down
12 changes: 7 additions & 5 deletions packages/web-integration/src/playwright/ai-fixture.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,17 +56,17 @@ type PlaywrightCacheConfig = {
};
type PlaywrightCache = false | true | PlaywrightCacheConfig;

export const PlaywrightAiFixture = (options?: {
forceSameTabNavigation?: boolean;
waitForNetworkIdleTimeout?: number;
waitForNavigationTimeout?: number;
type PlaywrightAiFixtureOptions = Omit<WebPageAgentOpt, 'cache'> & {
cache?: PlaywrightCache;
}) => {
};

export const PlaywrightAiFixture = (options?: PlaywrightAiFixtureOptions) => {
const {
forceSameTabNavigation = true,
waitForNetworkIdleTimeout = DEFAULT_WAIT_FOR_NETWORK_IDLE_TIMEOUT,
waitForNavigationTimeout = DEFAULT_WAIT_FOR_NAVIGATION_TIMEOUT,
cache,
...fixtureAgentOptions
} = options ?? {};

// Helper function to process cache configuration and auto-generate ID from test info
Expand Down Expand Up @@ -192,6 +192,7 @@ export const PlaywrightAiFixture = (options?: {
}) {
const { page, testInfo, use, aiActionType } = options;
const agent = createOrReuseAgentForPage(page, testInfo, {
...fixtureAgentOptions,
waitForNavigationTimeout,
waitForNetworkIdleTimeout,
}) as PlaywrightAgent;
Expand Down Expand Up @@ -289,6 +290,7 @@ export const PlaywrightAiFixture = (options?: {
}

const agent = createOrReuseAgentForPage(propsPage || page, testInfo, {
...fixtureAgentOptions,
waitForNavigationTimeout,
waitForNetworkIdleTimeout,
cache: finalCacheConfig,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { describe, expect, it, vi } from 'vitest';

const agentInstances: Array<{ opts: any }> = [];

vi.mock('@/playwright/index', () => {
class MockPlaywrightAgent {
opts: any;

reportFile?: string;

constructor(_page: any, opts: any) {
this.opts = opts;
agentInstances.push(this);
}

waitForNetworkIdle = vi.fn();

destroy = vi.fn(async () => undefined);
}

return {
PlaywrightAgent: MockPlaywrightAgent,
};
});

import { PlaywrightAiFixture } from '@/playwright/ai-fixture';

const createMockPage = () => ({
on: vi.fn(),
});

const createTestInfo = () =>
({
testId: 'playwright-fixture-test-id',
titlePath: ['fixture.spec.ts', 'forwards options'],
retry: 0,
annotations: [],
}) as any;

describe('PlaywrightAiFixture option forwarding', () => {
it('forwards agent options configured at fixture level', async () => {
agentInstances.length = 0;
const fixture = PlaywrightAiFixture({
replanningCycleLimit: 9,
waitAfterAction: 120,
aiActContext: 'fixture-level-context',
useDeviceTimestamp: true,
});

const page = createMockPage();
await fixture.agentForPage(
{ page } as any,
async (getAgent: any) => {
await getAgent();
},
createTestInfo(),
);

expect(agentInstances).toHaveLength(1);
expect(agentInstances[0].opts.replanningCycleLimit).toBe(9);
expect(agentInstances[0].opts.waitAfterAction).toBe(120);
expect(agentInstances[0].opts.aiActContext).toBe('fixture-level-context');
expect(agentInstances[0].opts.useDeviceTimestamp).toBe(true);
});

it('allows per-call options to override fixture-level options', async () => {
agentInstances.length = 0;
const fixture = PlaywrightAiFixture({
replanningCycleLimit: 9,
waitAfterAction: 120,
});

const page = createMockPage();
await fixture.agentForPage(
{ page } as any,
async (getAgent: any) => {
await getAgent(undefined, {
replanningCycleLimit: 3,
waitAfterAction: 60,
});
},
createTestInfo(),
);

expect(agentInstances).toHaveLength(1);
expect(agentInstances[0].opts.replanningCycleLimit).toBe(3);
expect(agentInstances[0].opts.waitAfterAction).toBe(60);
});
});
Loading