Skip to content

Commit cabea9f

Browse files
authored
feat(web-integration): support PlaywrightAgent options in PlaywrightAiFixture (#2328)
1 parent 8f6a3e5 commit cabea9f

File tree

7 files changed

+157
-10
lines changed

7 files changed

+157
-10
lines changed

apps/site/docs/en/integrate-with-playwright.mdx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,8 @@ export const test = base.extend<PlayWrightAiFixtureType>(
174174
);
175175
```
176176

177+
`PlaywrightAiFixture()` accepts all `PlaywrightAgent` options, so you can configure shared agent behavior directly at fixture creation time. Fixture-managed metadata like `testId`, `reportFileName`, `groupName`, and `groupDescription` are still generated automatically.
178+
177179
### Step 3: Write test cases
178180

179181
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:

apps/site/docs/en/web-api-reference.mdx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,8 @@ test('search flow', async ({ agentForPage, page }) => {
194194
});
195195
```
196196

197+
The fixture accepts all `PlaywrightAgent` options, so you can configure shared agent behavior once at fixture creation time. Per-test metadata such as `testId`, `reportFileName`, `groupName`, and `groupDescription` remain fixture-managed.
198+
197199
### See also
198200

199201
- [Integrate with Playwright](./integrate-with-playwright) for setup, fixtures, and advanced configuration.

apps/site/docs/zh/integrate-with-playwright.mdx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,8 @@ export const test = base.extend<PlayWrightAiFixtureType>(
174174
);
175175
```
176176

177+
`PlaywrightAiFixture()` 也支持传入 `PlaywrightAgent` 的全部配置,因此你可以在创建 fixture 时统一配置共享的 Agent 行为。`testId``reportFileName``groupName``groupDescription` 这类由 fixture 管理的元信息仍会自动生成。
178+
177179
### 第三步:编写测试用例
178180

179181
完整的交互、查询和辅助 API 请参考 [Agent API 参考](./api#interaction-methods)。如果需要调用更底层的能力,可以使用 `agentForPage` 获取 `PageAgent` 实例,再直接调用对应的方法:

apps/site/docs/zh/web-api-reference.mdx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,8 @@ test('search flow', async ({ agentForPage, page }) => {
194194
});
195195
```
196196

197+
这个 fixture 也支持传入 `PlaywrightAgent` 的全部配置,因此你可以在创建 fixture 时统一配置共享的 Agent 行为。`testId``reportFileName``groupName``groupDescription` 这类按测试自动生成的元信息仍由 fixture 管理。
198+
197199
### 另请参阅
198200

199201
- [集成到 Playwright](./integrate-with-playwright) 获取安装、Fixture 用法和更多配置。

packages/web-integration/src/playwright/ai-fixture.ts

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { PlaywrightAgent, type PlaywrightWebPage } from '@/playwright/index';
22
import type { WebPageAgentOpt } from '@/web-element';
33
import type { Cache } from '@midscene/core';
4-
import type { AgentOpt, Agent as PageAgent } from '@midscene/core/agent';
4+
import type { Agent as PageAgent } from '@midscene/core/agent';
55
import { processCacheConfig } from '@midscene/core/utils';
66
import {
77
DEFAULT_WAIT_FOR_NAVIGATION_TIMEOUT,
@@ -56,17 +56,25 @@ type PlaywrightCacheConfig = {
5656
};
5757
type PlaywrightCache = false | true | PlaywrightCacheConfig;
5858

59-
export const PlaywrightAiFixture = (options?: {
60-
forceSameTabNavigation?: boolean;
61-
waitForNetworkIdleTimeout?: number;
62-
waitForNavigationTimeout?: number;
59+
export type PlaywrightAiFixtureOptions = Omit<
60+
WebPageAgentOpt,
61+
| 'testId'
62+
| 'cacheId'
63+
| 'groupName'
64+
| 'groupDescription'
65+
| 'reportFileName'
66+
| 'cache'
67+
> & {
6368
cache?: PlaywrightCache;
64-
}) => {
69+
};
70+
71+
export const PlaywrightAiFixture = (options?: PlaywrightAiFixtureOptions) => {
6572
const {
6673
forceSameTabNavigation = true,
6774
waitForNetworkIdleTimeout = DEFAULT_WAIT_FOR_NETWORK_IDLE_TIMEOUT,
6875
waitForNavigationTimeout = DEFAULT_WAIT_FOR_NAVIGATION_TIMEOUT,
6976
cache,
77+
...sharedAgentOptions
7078
} = options ?? {};
7179

7280
// Helper function to process cache configuration and auto-generate ID from test info
@@ -139,6 +147,7 @@ export const PlaywrightAiFixture = (options?: {
139147
groupName: title,
140148
groupDescription: file,
141149
generateReport: true,
150+
...sharedAgentOptions,
142151
...opts,
143152
});
144153
pageAgentMap[idForPage] = agent;
@@ -261,7 +270,7 @@ export const PlaywrightAiFixture = (options?: {
261270
await use(
262271
async (
263272
propsPage?: OriginPlaywrightPage | undefined,
264-
opts?: AgentOpt,
273+
opts?: WebPageAgentOpt,
265274
) => {
266275
const cacheConfig = processTestCacheConfig(testInfo);
267276

@@ -606,8 +615,8 @@ export const PlaywrightAiFixture = (options?: {
606615

607616
export type PlayWrightAiFixtureType = {
608617
agentForPage: (
609-
page?: any,
610-
opts?: any,
618+
page?: OriginPlaywrightPage,
619+
opts?: WebPageAgentOpt,
611620
) => Promise<PageAgent<PlaywrightWebPage>>;
612621
ai: <T = any>(...args: Parameters<PageAgent['ai']>) => Promise<T>;
613622
aiAct: (

packages/web-integration/src/playwright/index.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@ import { Agent as PageAgent } from '@midscene/core/agent';
22
import type { Page as PlaywrightPage } from 'playwright';
33
import { WebPage as PlaywrightWebPage } from './page';
44

5-
export type { PlayWrightAiFixtureType } from './ai-fixture';
5+
export type {
6+
PlayWrightAiFixtureType,
7+
PlaywrightAiFixtureOptions,
8+
} from './ai-fixture';
69
export { PlaywrightAiFixture } from './ai-fixture';
710
export { overrideAIConfig } from '@midscene/shared/env';
811
export { WebPage as PlaywrightWebPage } from './page';
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
import { beforeEach, describe, expect, it, vi } from 'vitest';
2+
3+
const mockState = vi.hoisted(() => ({
4+
ctorOpts: [] as any[],
5+
instances: [] as any[],
6+
}));
7+
8+
vi.mock('@/playwright/index', () => {
9+
class MockPlaywrightAgent {
10+
reportFile?: string;
11+
12+
constructor(_page: any, opts: any) {
13+
mockState.ctorOpts.push(opts);
14+
mockState.instances.push(this);
15+
this.reportFile = opts?.generateReport ? 'mock-report.html' : undefined;
16+
}
17+
18+
async destroy() {}
19+
}
20+
21+
return {
22+
PlaywrightAgent: MockPlaywrightAgent,
23+
};
24+
});
25+
26+
import { PlaywrightAiFixture } from '@/playwright/ai-fixture';
27+
28+
describe('PlaywrightAiFixture option forwarding', () => {
29+
beforeEach(() => {
30+
mockState.ctorOpts.length = 0;
31+
mockState.instances.length = 0;
32+
});
33+
34+
const createPage = () =>
35+
({
36+
on: vi.fn(),
37+
}) as any;
38+
39+
const createTestInfo = () =>
40+
({
41+
testId: 'test-id',
42+
titlePath: ['fixture.spec.ts', 'forwards options'],
43+
annotations: [],
44+
retry: 0,
45+
}) as any;
46+
47+
it('should forward fixture-level AgentOpt and WebPageOpt to the first agent creation', async () => {
48+
const fixture = PlaywrightAiFixture({
49+
autoPrintReportMsg: false,
50+
outputFormat: 'html-and-external-assets',
51+
waitAfterAction: 120,
52+
enableTouchEventsInActionSpace: true,
53+
forceChromeSelectRendering: true,
54+
});
55+
56+
await fixture.ai({ page: createPage() }, async () => {}, createTestInfo());
57+
58+
expect(mockState.ctorOpts).toHaveLength(1);
59+
expect(mockState.ctorOpts[0]).toMatchObject({
60+
autoPrintReportMsg: false,
61+
outputFormat: 'html-and-external-assets',
62+
waitAfterAction: 120,
63+
enableTouchEventsInActionSpace: true,
64+
forceChromeSelectRendering: true,
65+
generateReport: true,
66+
});
67+
});
68+
69+
it('should allow the first agentForPage call to override fixture defaults', async () => {
70+
const fixture = PlaywrightAiFixture({
71+
autoPrintReportMsg: true,
72+
waitAfterAction: 300,
73+
enableTouchEventsInActionSpace: true,
74+
});
75+
76+
let getAgentForPage: any;
77+
await fixture.agentForPage(
78+
{ page: createPage() },
79+
async (agentForPage: any) => {
80+
getAgentForPage = agentForPage;
81+
},
82+
createTestInfo(),
83+
);
84+
85+
await getAgentForPage(createPage(), {
86+
autoPrintReportMsg: false,
87+
waitAfterAction: 50,
88+
enableTouchEventsInActionSpace: false,
89+
});
90+
91+
expect(mockState.ctorOpts).toHaveLength(1);
92+
expect(mockState.ctorOpts[0]).toMatchObject({
93+
autoPrintReportMsg: false,
94+
waitAfterAction: 50,
95+
enableTouchEventsInActionSpace: false,
96+
});
97+
});
98+
99+
it('should reuse the existing agent instead of recreating it with later overrides', async () => {
100+
const page = createPage();
101+
const fixture = PlaywrightAiFixture({
102+
autoPrintReportMsg: true,
103+
});
104+
105+
let getAgentForPage: any;
106+
await fixture.agentForPage(
107+
{ page },
108+
async (agentForPage: any) => {
109+
getAgentForPage = agentForPage;
110+
},
111+
createTestInfo(),
112+
);
113+
114+
const firstAgent = await getAgentForPage(page, {
115+
autoPrintReportMsg: false,
116+
});
117+
const secondAgent = await getAgentForPage(page, {
118+
autoPrintReportMsg: true,
119+
});
120+
121+
expect(firstAgent).toBe(secondAgent);
122+
expect(mockState.ctorOpts).toHaveLength(1);
123+
expect(mockState.ctorOpts[0]).toMatchObject({
124+
autoPrintReportMsg: false,
125+
});
126+
});
127+
});

0 commit comments

Comments
 (0)