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
2 changes: 2 additions & 0 deletions apps/site/docs/en/integrate-with-playwright.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,8 @@ export const test = base.extend<PlayWrightAiFixtureType>(
);
```

`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.

### 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
2 changes: 2 additions & 0 deletions apps/site/docs/en/web-api-reference.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,8 @@ test('search flow', async ({ agentForPage, page }) => {
});
```

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.

### See also

- [Integrate with Playwright](./integrate-with-playwright) for setup, fixtures, and advanced configuration.
Expand Down
2 changes: 2 additions & 0 deletions apps/site/docs/zh/integrate-with-playwright.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,8 @@ export const test = base.extend<PlayWrightAiFixtureType>(
);
```

`PlaywrightAiFixture()` 也支持传入 `PlaywrightAgent` 的全部配置,因此你可以在创建 fixture 时统一配置共享的 Agent 行为。`testId`、`reportFileName`、`groupName`、`groupDescription` 这类由 fixture 管理的元信息仍会自动生成。

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

完整的交互、查询和辅助 API 请参考 [Agent API 参考](./api#interaction-methods)。如果需要调用更底层的能力,可以使用 `agentForPage` 获取 `PageAgent` 实例,再直接调用对应的方法:
Expand Down
2 changes: 2 additions & 0 deletions apps/site/docs/zh/web-api-reference.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,8 @@ test('search flow', async ({ agentForPage, page }) => {
});
```

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

### 另请参阅

- [集成到 Playwright](./integrate-with-playwright) 获取安装、Fixture 用法和更多配置。
Expand Down
27 changes: 18 additions & 9 deletions packages/web-integration/src/playwright/ai-fixture.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { PlaywrightAgent, type PlaywrightWebPage } from '@/playwright/index';
import type { WebPageAgentOpt } from '@/web-element';
import type { Cache } from '@midscene/core';
import type { AgentOpt, Agent as PageAgent } from '@midscene/core/agent';
import type { Agent as PageAgent } from '@midscene/core/agent';
import { processCacheConfig } from '@midscene/core/utils';
import {
DEFAULT_WAIT_FOR_NAVIGATION_TIMEOUT,
Expand Down Expand Up @@ -56,17 +56,25 @@ type PlaywrightCacheConfig = {
};
type PlaywrightCache = false | true | PlaywrightCacheConfig;

export const PlaywrightAiFixture = (options?: {
forceSameTabNavigation?: boolean;
waitForNetworkIdleTimeout?: number;
waitForNavigationTimeout?: number;
export type PlaywrightAiFixtureOptions = Omit<
WebPageAgentOpt,
| 'testId'
| 'cacheId'
| 'groupName'
| 'groupDescription'
| 'reportFileName'
| '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,
...sharedAgentOptions
} = options ?? {};

// Helper function to process cache configuration and auto-generate ID from test info
Expand Down Expand Up @@ -139,6 +147,7 @@ export const PlaywrightAiFixture = (options?: {
groupName: title,
groupDescription: file,
generateReport: true,
...sharedAgentOptions,
...opts,
});
pageAgentMap[idForPage] = agent;
Expand Down Expand Up @@ -261,7 +270,7 @@ export const PlaywrightAiFixture = (options?: {
await use(
async (
propsPage?: OriginPlaywrightPage | undefined,
opts?: AgentOpt,
opts?: WebPageAgentOpt,
) => {
const cacheConfig = processTestCacheConfig(testInfo);

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

export type PlayWrightAiFixtureType = {
agentForPage: (
page?: any,
opts?: any,
page?: OriginPlaywrightPage,
opts?: WebPageAgentOpt,
) => Promise<PageAgent<PlaywrightWebPage>>;
ai: <T = any>(...args: Parameters<PageAgent['ai']>) => Promise<T>;
aiAct: (
Expand Down
5 changes: 4 additions & 1 deletion packages/web-integration/src/playwright/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ import { Agent as PageAgent } from '@midscene/core/agent';
import type { Page as PlaywrightPage } from 'playwright';
import { WebPage as PlaywrightWebPage } from './page';

export type { PlayWrightAiFixtureType } from './ai-fixture';
export type {
PlayWrightAiFixtureType,
PlaywrightAiFixtureOptions,
} from './ai-fixture';
export { PlaywrightAiFixture } from './ai-fixture';
export { overrideAIConfig } from '@midscene/shared/env';
export { WebPage as PlaywrightWebPage } from './page';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';

const mockState = vi.hoisted(() => ({
ctorOpts: [] as any[],
instances: [] as any[],
}));

vi.mock('@/playwright/index', () => {
class MockPlaywrightAgent {
reportFile?: string;

constructor(_page: any, opts: any) {
mockState.ctorOpts.push(opts);
mockState.instances.push(this);
this.reportFile = opts?.generateReport ? 'mock-report.html' : undefined;
}

async destroy() {}
}

return {
PlaywrightAgent: MockPlaywrightAgent,
};
});

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

describe('PlaywrightAiFixture option forwarding', () => {
beforeEach(() => {
mockState.ctorOpts.length = 0;
mockState.instances.length = 0;
});

const createPage = () =>
({
on: vi.fn(),
}) as any;

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

it('should forward fixture-level AgentOpt and WebPageOpt to the first agent creation', async () => {
const fixture = PlaywrightAiFixture({
autoPrintReportMsg: false,
outputFormat: 'html-and-external-assets',
waitAfterAction: 120,
enableTouchEventsInActionSpace: true,
forceChromeSelectRendering: true,
});

await fixture.ai({ page: createPage() }, async () => {}, createTestInfo());

expect(mockState.ctorOpts).toHaveLength(1);
expect(mockState.ctorOpts[0]).toMatchObject({
autoPrintReportMsg: false,
outputFormat: 'html-and-external-assets',
waitAfterAction: 120,
enableTouchEventsInActionSpace: true,
forceChromeSelectRendering: true,
generateReport: true,
});
});

it('should allow the first agentForPage call to override fixture defaults', async () => {
const fixture = PlaywrightAiFixture({
autoPrintReportMsg: true,
waitAfterAction: 300,
enableTouchEventsInActionSpace: true,
});

let getAgentForPage: any;
await fixture.agentForPage(
{ page: createPage() },
async (agentForPage: any) => {
getAgentForPage = agentForPage;
},
createTestInfo(),
);

await getAgentForPage(createPage(), {
autoPrintReportMsg: false,
waitAfterAction: 50,
enableTouchEventsInActionSpace: false,
});

expect(mockState.ctorOpts).toHaveLength(1);
expect(mockState.ctorOpts[0]).toMatchObject({
autoPrintReportMsg: false,
waitAfterAction: 50,
enableTouchEventsInActionSpace: false,
});
});

it('should reuse the existing agent instead of recreating it with later overrides', async () => {
const page = createPage();
const fixture = PlaywrightAiFixture({
autoPrintReportMsg: true,
});

let getAgentForPage: any;
await fixture.agentForPage(
{ page },
async (agentForPage: any) => {
getAgentForPage = agentForPage;
},
createTestInfo(),
);

const firstAgent = await getAgentForPage(page, {
autoPrintReportMsg: false,
});
const secondAgent = await getAgentForPage(page, {
autoPrintReportMsg: true,
});

expect(firstAgent).toBe(secondAgent);
expect(mockState.ctorOpts).toHaveLength(1);
expect(mockState.ctorOpts[0]).toMatchObject({
autoPrintReportMsg: false,
});
});
});
Loading