@midscene/web 0.30.10 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/es/bin.mjs +0 -4
- package/dist/es/bin.mjs.map +1 -1
- package/dist/es/bridge-mode/agent-cli-side.mjs +22 -11
- package/dist/es/bridge-mode/agent-cli-side.mjs.map +1 -1
- package/dist/es/bridge-mode/common.mjs +8 -2
- package/dist/es/bridge-mode/common.mjs.map +1 -1
- package/dist/es/bridge-mode/io-client.mjs +10 -16
- package/dist/es/bridge-mode/io-client.mjs.map +1 -1
- package/dist/es/bridge-mode/io-server.mjs +21 -19
- package/dist/es/bridge-mode/io-server.mjs.map +1 -1
- package/dist/es/bridge-mode/page-browser-side.mjs +10 -11
- package/dist/es/bridge-mode/page-browser-side.mjs.map +1 -1
- package/dist/es/chrome-extension/agent.mjs.map +1 -1
- package/dist/es/chrome-extension/cdpInput.mjs.map +1 -1
- package/dist/es/chrome-extension/dynamic-scripts.mjs.map +1 -1
- package/dist/es/chrome-extension/page.mjs +85 -89
- package/dist/es/chrome-extension/page.mjs.map +1 -1
- package/dist/es/playwright/ai-fixture.mjs +43 -14
- package/dist/es/playwright/ai-fixture.mjs.map +1 -1
- package/dist/es/playwright/index.mjs +18 -2
- package/dist/es/playwright/index.mjs.map +1 -1
- package/dist/es/playwright/page.mjs.map +1 -1
- package/dist/es/playwright/reporter/index.mjs +30 -16
- package/dist/es/playwright/reporter/index.mjs.map +1 -1
- package/dist/es/puppeteer/agent-launcher.mjs +48 -22
- package/dist/es/puppeteer/agent-launcher.mjs.map +1 -1
- package/dist/es/puppeteer/base-page.mjs +67 -26
- package/dist/es/puppeteer/base-page.mjs.map +1 -1
- package/dist/es/puppeteer/index.mjs +18 -2
- package/dist/es/puppeteer/index.mjs.map +1 -1
- package/dist/es/puppeteer/page.mjs.map +1 -1
- package/dist/es/static/static-agent.mjs.map +1 -1
- package/dist/es/static/static-page.mjs +1 -10
- package/dist/es/static/static-page.mjs.map +1 -1
- package/dist/es/utils.mjs +8 -0
- package/dist/es/utils.mjs.map +1 -0
- package/dist/es/web-element.mjs +2 -24
- package/dist/es/web-element.mjs.map +1 -1
- package/dist/es/web-page.mjs +71 -41
- package/dist/es/web-page.mjs.map +1 -1
- package/dist/lib/bin.js +1 -5
- package/dist/lib/bin.js.map +1 -1
- package/dist/lib/bridge-mode/agent-cli-side.js +23 -12
- package/dist/lib/bridge-mode/agent-cli-side.js.map +1 -1
- package/dist/lib/bridge-mode/browser.js +2 -2
- package/dist/lib/bridge-mode/browser.js.map +1 -1
- package/dist/lib/bridge-mode/common.js +17 -5
- package/dist/lib/bridge-mode/common.js.map +1 -1
- package/dist/lib/bridge-mode/index.js +3 -3
- package/dist/lib/bridge-mode/index.js.map +1 -1
- package/dist/lib/bridge-mode/io-client.js +12 -18
- package/dist/lib/bridge-mode/io-client.js.map +1 -1
- package/dist/lib/bridge-mode/io-server.js +25 -23
- package/dist/lib/bridge-mode/io-server.js.map +1 -1
- package/dist/lib/bridge-mode/page-browser-side.js +12 -13
- package/dist/lib/bridge-mode/page-browser-side.js.map +1 -1
- package/dist/lib/chrome-extension/agent.js +2 -2
- package/dist/lib/chrome-extension/agent.js.map +1 -1
- package/dist/lib/chrome-extension/cdpInput.js +2 -2
- package/dist/lib/chrome-extension/cdpInput.js.map +1 -1
- package/dist/lib/chrome-extension/dynamic-scripts.js +2 -2
- package/dist/lib/chrome-extension/dynamic-scripts.js.map +1 -1
- package/dist/lib/chrome-extension/index.js +3 -3
- package/dist/lib/chrome-extension/index.js.map +1 -1
- package/dist/lib/chrome-extension/page.js +87 -91
- package/dist/lib/chrome-extension/page.js.map +1 -1
- package/dist/lib/index.js +6 -6
- package/dist/lib/index.js.map +1 -1
- package/dist/lib/playwright/ai-fixture.js +46 -17
- package/dist/lib/playwright/ai-fixture.js.map +1 -1
- package/dist/lib/playwright/index.js +32 -6
- package/dist/lib/playwright/index.js.map +1 -1
- package/dist/lib/playwright/page.js +2 -2
- package/dist/lib/playwright/page.js.map +1 -1
- package/dist/lib/playwright/reporter/index.js +32 -18
- package/dist/lib/playwright/reporter/index.js.map +1 -1
- package/dist/lib/puppeteer/agent-launcher.js +57 -28
- package/dist/lib/puppeteer/agent-launcher.js.map +1 -1
- package/dist/lib/puppeteer/base-page.js +73 -29
- package/dist/lib/puppeteer/base-page.js.map +1 -1
- package/dist/lib/puppeteer/index.js +31 -5
- package/dist/lib/puppeteer/index.js.map +1 -1
- package/dist/lib/puppeteer/page.js +2 -2
- package/dist/lib/puppeteer/page.js.map +1 -1
- package/dist/lib/static/index.js +4 -4
- package/dist/lib/static/index.js.map +1 -1
- package/dist/lib/static/static-agent.js +2 -2
- package/dist/lib/static/static-agent.js.map +1 -1
- package/dist/lib/static/static-page.js +3 -12
- package/dist/lib/static/static-page.js.map +1 -1
- package/dist/lib/utils.js +40 -0
- package/dist/lib/utils.js.map +1 -0
- package/dist/lib/web-element.js +6 -28
- package/dist/lib/web-element.js.map +1 -1
- package/dist/lib/web-page.js +73 -43
- package/dist/lib/web-page.js.map +1 -1
- package/dist/types/bridge-mode/agent-cli-side.d.ts +23 -2
- package/dist/types/bridge-mode/common.d.ts +9 -0
- package/dist/types/bridge-mode/io-server.d.ts +3 -2
- package/dist/types/bridge-mode/page-browser-side.d.ts +2 -1
- package/dist/types/chrome-extension/page.d.ts +19 -6
- package/dist/types/playwright/ai-fixture.d.ts +16 -2
- package/dist/types/playwright/index.d.ts +1 -0
- package/dist/types/playwright/reporter/index.d.ts +2 -0
- package/dist/types/puppeteer/agent-launcher.d.ts +3 -4
- package/dist/types/puppeteer/base-page.d.ts +18 -5
- package/dist/types/puppeteer/index.d.ts +1 -0
- package/dist/types/static/static-page.d.ts +0 -1
- package/dist/types/utils.d.ts +6 -0
- package/dist/types/web-element.d.ts +10 -0
- package/dist/types/web-page.d.ts +4 -1
- package/package.json +13 -20
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { writeFileSync } from "node:fs";
|
|
1
|
+
import { rmSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { tmpdir } from "node:os";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { PlaywrightAgent } from "./index.mjs";
|
|
@@ -33,6 +33,7 @@ const groupAndCaseForTest = (testInfo)=>{
|
|
|
33
33
|
};
|
|
34
34
|
const midsceneAgentKeyId = '_midsceneAgentId';
|
|
35
35
|
const midsceneDumpAnnotationId = 'MIDSCENE_DUMP_ANNOTATION';
|
|
36
|
+
const pageTempFiles = new Map();
|
|
36
37
|
const PlaywrightAiFixture = (options)=>{
|
|
37
38
|
const { forceSameTabNavigation = true, waitForNetworkIdleTimeout = DEFAULT_WAIT_FOR_NETWORK_IDLE_TIMEOUT, waitForNavigationTimeout = DEFAULT_WAIT_FOR_NAVIGATION_TIMEOUT, cache } = options ?? {};
|
|
38
39
|
const processTestCacheConfig = (testInfo)=>{
|
|
@@ -58,10 +59,11 @@ const PlaywrightAiFixture = (options)=>{
|
|
|
58
59
|
...opts
|
|
59
60
|
});
|
|
60
61
|
pageAgentMap[idForPage].onDumpUpdate = (dump)=>{
|
|
61
|
-
updateDumpAnnotation(testInfo, dump);
|
|
62
|
+
updateDumpAnnotation(testInfo, dump, idForPage);
|
|
62
63
|
};
|
|
63
64
|
page.on('close', ()=>{
|
|
64
65
|
debugPage('page closed');
|
|
66
|
+
pageTempFiles.delete(idForPage);
|
|
65
67
|
pageAgentMap[idForPage].destroy();
|
|
66
68
|
delete pageAgentMap[idForPage];
|
|
67
69
|
});
|
|
@@ -83,7 +85,7 @@ const PlaywrightAiFixture = (options)=>{
|
|
|
83
85
|
console.warn('[midscene:warning] Waiting for network idle has timed out, but Midscene will continue execution. Please check https://midscenejs.com/faq.html#customize-the-network-timeout for more information on customizing the network timeout');
|
|
84
86
|
}
|
|
85
87
|
try {
|
|
86
|
-
const result = await agent[aiActionType](taskPrompt, ...args
|
|
88
|
+
const result = await agent[aiActionType].bind(agent)(taskPrompt, ...args);
|
|
87
89
|
resolve(result);
|
|
88
90
|
} catch (error) {
|
|
89
91
|
reject(error);
|
|
@@ -91,24 +93,35 @@ const PlaywrightAiFixture = (options)=>{
|
|
|
91
93
|
});
|
|
92
94
|
}));
|
|
93
95
|
}
|
|
94
|
-
const updateDumpAnnotation = (test, dump)=>{
|
|
95
|
-
const
|
|
96
|
+
const updateDumpAnnotation = (test, dump, pageId)=>{
|
|
97
|
+
const oldTempFilePath = pageTempFiles.get(pageId);
|
|
98
|
+
if (oldTempFilePath) try {
|
|
99
|
+
rmSync(oldTempFilePath, {
|
|
100
|
+
force: true
|
|
101
|
+
});
|
|
102
|
+
} catch (error) {}
|
|
103
|
+
const tempFileName = `midscene-dump-${test.testId || uuid()}-${pageId}.json`;
|
|
96
104
|
const tempFilePath = join(tmpdir(), tempFileName);
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
+
try {
|
|
106
|
+
writeFileSync(tempFilePath, dump, 'utf-8');
|
|
107
|
+
debugPage(`Dump written to temp file: ${tempFilePath}`);
|
|
108
|
+
pageTempFiles.set(pageId, tempFilePath);
|
|
109
|
+
const currentAnnotation = test.annotations.find((item)=>item.type === midsceneDumpAnnotationId);
|
|
110
|
+
if (currentAnnotation) currentAnnotation.description = tempFilePath;
|
|
111
|
+
else test.annotations.push({
|
|
112
|
+
type: midsceneDumpAnnotationId,
|
|
113
|
+
description: tempFilePath
|
|
114
|
+
});
|
|
115
|
+
} catch (error) {
|
|
116
|
+
debugPage(`Failed to write temp file: ${tempFilePath}. Skipping annotation.`, error);
|
|
117
|
+
}
|
|
105
118
|
};
|
|
106
119
|
return {
|
|
107
120
|
agentForPage: async ({ page }, use, testInfo)=>{
|
|
108
121
|
await use(async (propsPage, opts)=>{
|
|
109
122
|
const cacheConfig = processTestCacheConfig(testInfo);
|
|
110
123
|
let finalCacheConfig = cacheConfig;
|
|
111
|
-
if (
|
|
124
|
+
if (opts?.cache !== void 0) {
|
|
112
125
|
const userCache = opts.cache;
|
|
113
126
|
if (false === userCache) finalCacheConfig = false;
|
|
114
127
|
else if (true === userCache) {
|
|
@@ -142,6 +155,14 @@ const PlaywrightAiFixture = (options)=>{
|
|
|
142
155
|
aiActionType: 'ai'
|
|
143
156
|
});
|
|
144
157
|
},
|
|
158
|
+
aiAct: async ({ page }, use, testInfo)=>{
|
|
159
|
+
await generateAiFunction({
|
|
160
|
+
page,
|
|
161
|
+
testInfo,
|
|
162
|
+
use,
|
|
163
|
+
aiActionType: 'aiAct'
|
|
164
|
+
});
|
|
165
|
+
},
|
|
145
166
|
aiAction: async ({ page }, use, testInfo)=>{
|
|
146
167
|
await generateAiFunction({
|
|
147
168
|
page,
|
|
@@ -294,6 +315,14 @@ const PlaywrightAiFixture = (options)=>{
|
|
|
294
315
|
aiActionType: 'evaluateJavaScript'
|
|
295
316
|
});
|
|
296
317
|
},
|
|
318
|
+
recordToReport: async ({ page }, use, testInfo)=>{
|
|
319
|
+
await generateAiFunction({
|
|
320
|
+
page,
|
|
321
|
+
testInfo,
|
|
322
|
+
use,
|
|
323
|
+
aiActionType: 'recordToReport'
|
|
324
|
+
});
|
|
325
|
+
},
|
|
297
326
|
logScreenshot: async ({ page }, use, testInfo)=>{
|
|
298
327
|
await generateAiFunction({
|
|
299
328
|
page,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"playwright/ai-fixture.mjs","sources":["webpack://@midscene/web/./src/playwright/ai-fixture.ts"],"sourcesContent":["import { writeFileSync } from 'node:fs';\nimport { tmpdir } from 'node:os';\nimport { join } from 'node:path';\nimport { PlaywrightAgent, type PlaywrightWebPage } from '@/playwright/index';\nimport type { WebPageAgentOpt } from '@/web-element';\nimport type { Cache } from '@midscene/core';\nimport type { AgentOpt, Agent as PageAgent } from '@midscene/core/agent';\nimport { processCacheConfig } from '@midscene/core/utils';\nimport {\n DEFAULT_WAIT_FOR_NAVIGATION_TIMEOUT,\n DEFAULT_WAIT_FOR_NETWORK_IDLE_TIMEOUT,\n} from '@midscene/shared/constants';\nimport { getDebug } from '@midscene/shared/logger';\nimport { uuid } from '@midscene/shared/utils';\nimport { replaceIllegalPathCharsAndSpace } from '@midscene/shared/utils';\nimport { type TestInfo, type TestType, test } from '@playwright/test';\nimport type { Page as OriginPlaywrightPage } from 'playwright';\nexport type APITestType = Pick<TestType<any, any>, 'step'>;\n\nconst debugPage = getDebug('web:playwright:ai-fixture');\n\nconst groupAndCaseForTest = (testInfo: TestInfo) => {\n let taskFile: string;\n let taskTitle: string;\n const titlePath = [...testInfo.titlePath];\n\n if (titlePath.length > 1) {\n taskFile = titlePath.shift() || 'unnamed';\n taskTitle = titlePath.join('__');\n } else if (titlePath.length === 1) {\n taskTitle = titlePath[0];\n taskFile = `${taskTitle}`;\n } else {\n taskTitle = 'unnamed';\n taskFile = 'unnamed';\n }\n\n const taskTitleWithRetry = `${taskTitle}${testInfo.retry ? `(retry #${testInfo.retry})` : ''}`;\n\n return {\n file: taskFile,\n id: replaceIllegalPathCharsAndSpace(`${taskFile}(${taskTitle})`),\n title: replaceIllegalPathCharsAndSpace(taskTitleWithRetry),\n };\n};\n\nconst midsceneAgentKeyId = '_midsceneAgentId';\nexport const midsceneDumpAnnotationId = 'MIDSCENE_DUMP_ANNOTATION';\n\ntype PlaywrightCacheConfig = {\n strategy?: 'read-only' | 'read-write' | 'write-only';\n id?: string;\n};\ntype PlaywrightCache = false | true | PlaywrightCacheConfig;\n\nexport const PlaywrightAiFixture = (options?: {\n forceSameTabNavigation?: boolean;\n waitForNetworkIdleTimeout?: number;\n waitForNavigationTimeout?: number;\n cache?: PlaywrightCache;\n}) => {\n const {\n forceSameTabNavigation = true,\n waitForNetworkIdleTimeout = DEFAULT_WAIT_FOR_NETWORK_IDLE_TIMEOUT,\n waitForNavigationTimeout = DEFAULT_WAIT_FOR_NAVIGATION_TIMEOUT,\n cache,\n } = options ?? {};\n\n // Helper function to process cache configuration and auto-generate ID from test info\n const processTestCacheConfig = (testInfo: TestInfo): Cache | undefined => {\n // Generate ID from test info\n const { id } = groupAndCaseForTest(testInfo);\n\n // Use shared processCacheConfig with generated ID as fallback\n return processCacheConfig(cache as Cache, id);\n };\n\n const pageAgentMap: Record<string, PageAgent<PlaywrightWebPage>> = {};\n const createOrReuseAgentForPage = (\n page: OriginPlaywrightPage,\n testInfo: TestInfo, // { testId: string; taskFile: string; taskTitle: string },\n opts?: WebPageAgentOpt,\n ) => {\n let idForPage = (page as any)[midsceneAgentKeyId];\n if (!idForPage) {\n idForPage = uuid();\n (page as any)[midsceneAgentKeyId] = idForPage;\n const { testId } = testInfo;\n const { file, title } = groupAndCaseForTest(testInfo);\n const cacheConfig = processTestCacheConfig(testInfo);\n\n pageAgentMap[idForPage] = new PlaywrightAgent(page, {\n testId: `playwright-${testId}-${idForPage}`,\n forceSameTabNavigation,\n cache: cacheConfig,\n groupName: title,\n groupDescription: file,\n generateReport: false, // we will generate it in the reporter\n ...opts,\n });\n\n pageAgentMap[idForPage].onDumpUpdate = (dump: string) => {\n updateDumpAnnotation(testInfo, dump);\n };\n\n page.on('close', () => {\n debugPage('page closed');\n pageAgentMap[idForPage].destroy();\n delete pageAgentMap[idForPage];\n });\n }\n\n return pageAgentMap[idForPage];\n };\n\n async function generateAiFunction(options: {\n page: OriginPlaywrightPage;\n testInfo: TestInfo;\n use: any;\n aiActionType:\n | 'ai'\n | 'aiAction'\n | 'aiHover'\n | 'aiInput'\n | 'aiKeyboardPress'\n | 'aiScroll'\n | 'aiTap'\n | 'aiRightClick'\n | 'aiDoubleClick'\n | 'aiQuery'\n | 'aiAssert'\n | 'aiWaitFor'\n | 'aiLocate'\n | 'aiNumber'\n | 'aiString'\n | 'aiBoolean'\n | 'aiAsk'\n | 'runYaml'\n | 'setAIActionContext'\n | 'evaluateJavaScript'\n | 'logScreenshot'\n | 'freezePageContext'\n | 'unfreezePageContext';\n }) {\n const { page, testInfo, use, aiActionType } = options;\n const agent = createOrReuseAgentForPage(page, testInfo, {\n waitForNavigationTimeout,\n waitForNetworkIdleTimeout,\n }) as PlaywrightAgent;\n\n await use(async (taskPrompt: string, ...args: any[]) => {\n return new Promise((resolve, reject) => {\n test.step(`ai-${aiActionType} - ${JSON.stringify(taskPrompt)}`, async () => {\n try {\n debugPage(\n `waitForNetworkIdle timeout: ${waitForNetworkIdleTimeout}`,\n );\n await agent.waitForNetworkIdle(waitForNetworkIdleTimeout);\n } catch (error) {\n console.warn(\n '[midscene:warning] Waiting for network idle has timed out, but Midscene will continue execution. Please check https://midscenejs.com/faq.html#customize-the-network-timeout for more information on customizing the network timeout',\n );\n }\n try {\n type AgentMethod = (\n prompt: string,\n ...restArgs: any[]\n ) => Promise<any>;\n const result = await (agent[aiActionType] as AgentMethod)(\n taskPrompt,\n ...(args || []),\n );\n resolve(result);\n } catch (error) {\n reject(error);\n }\n });\n });\n });\n }\n\n const updateDumpAnnotation = (test: TestInfo, dump: string) => {\n // Write dump to temporary file\n const tempFileName = `midscene-dump-${test.testId || uuid()}-${Date.now()}.json`;\n const tempFilePath = join(tmpdir(), tempFileName);\n\n writeFileSync(tempFilePath, dump, 'utf-8');\n debugPage(`Dump written to temp file: ${tempFilePath}`);\n\n // Store only the file path in annotation\n const currentAnnotation = test.annotations.find((item) => {\n return item.type === midsceneDumpAnnotationId;\n });\n if (currentAnnotation) {\n // Store file path instead of dump content\n currentAnnotation.description = tempFilePath;\n } else {\n test.annotations.push({\n type: midsceneDumpAnnotationId,\n description: tempFilePath,\n });\n }\n };\n\n return {\n agentForPage: async (\n { page }: { page: OriginPlaywrightPage },\n use: any,\n testInfo: TestInfo,\n ) => {\n await use(\n async (\n propsPage?: OriginPlaywrightPage | undefined,\n opts?: AgentOpt,\n ) => {\n const cacheConfig = processTestCacheConfig(testInfo);\n\n // Handle cache configuration priority:\n // 1. If user provides cache in opts, use it (but auto-generate ID if missing)\n // 2. Otherwise use fixture's cache config\n let finalCacheConfig = cacheConfig;\n if (opts?.cache !== undefined) {\n const userCache = opts.cache;\n if (userCache === false) {\n finalCacheConfig = false;\n } else if (userCache === true) {\n // Auto-generate ID for user's cache: true\n const { id } = groupAndCaseForTest(testInfo);\n finalCacheConfig = { id };\n } else if (typeof userCache === 'object') {\n if (!userCache.id) {\n // Auto-generate ID for user's cache object without ID\n const { id } = groupAndCaseForTest(testInfo);\n finalCacheConfig = { ...userCache, id };\n } else {\n finalCacheConfig = userCache;\n }\n }\n }\n\n const agent = createOrReuseAgentForPage(propsPage || page, testInfo, {\n waitForNavigationTimeout,\n waitForNetworkIdleTimeout,\n cache: finalCacheConfig,\n ...opts,\n });\n return agent;\n },\n );\n },\n ai: async (\n { page }: { page: OriginPlaywrightPage },\n use: any,\n testInfo: TestInfo,\n ) => {\n await generateAiFunction({\n page,\n testInfo,\n use,\n aiActionType: 'ai',\n });\n },\n aiAction: async (\n { page }: { page: OriginPlaywrightPage },\n use: any,\n testInfo: TestInfo,\n ) => {\n await generateAiFunction({\n page,\n testInfo,\n use,\n aiActionType: 'aiAction',\n });\n },\n aiTap: async (\n { page }: { page: OriginPlaywrightPage },\n use: any,\n testInfo: TestInfo,\n ) => {\n await generateAiFunction({\n page,\n testInfo,\n use,\n aiActionType: 'aiTap',\n });\n },\n aiRightClick: async (\n { page }: { page: OriginPlaywrightPage },\n use: any,\n testInfo: TestInfo,\n ) => {\n await generateAiFunction({\n page,\n testInfo,\n use,\n aiActionType: 'aiRightClick',\n });\n },\n aiDoubleClick: async (\n { page }: { page: OriginPlaywrightPage },\n use: any,\n testInfo: TestInfo,\n ) => {\n await generateAiFunction({\n page,\n testInfo,\n use,\n aiActionType: 'aiDoubleClick',\n });\n },\n aiHover: async (\n { page }: { page: OriginPlaywrightPage },\n use: any,\n testInfo: TestInfo,\n ) => {\n await generateAiFunction({\n page,\n testInfo,\n use,\n aiActionType: 'aiHover',\n });\n },\n aiInput: async (\n { page }: { page: OriginPlaywrightPage },\n use: any,\n testInfo: TestInfo,\n ) => {\n await generateAiFunction({\n page,\n testInfo,\n use,\n aiActionType: 'aiInput',\n });\n },\n aiKeyboardPress: async (\n { page }: { page: OriginPlaywrightPage },\n use: any,\n testInfo: TestInfo,\n ) => {\n await generateAiFunction({\n page,\n testInfo,\n use,\n aiActionType: 'aiKeyboardPress',\n });\n },\n aiScroll: async (\n { page }: { page: OriginPlaywrightPage },\n use: any,\n testInfo: TestInfo,\n ) => {\n await generateAiFunction({\n page,\n testInfo,\n use,\n aiActionType: 'aiScroll',\n });\n },\n aiQuery: async (\n { page }: { page: OriginPlaywrightPage },\n use: any,\n testInfo: TestInfo,\n ) => {\n await generateAiFunction({\n page,\n testInfo,\n use,\n aiActionType: 'aiQuery',\n });\n },\n aiAssert: async (\n { page }: { page: OriginPlaywrightPage },\n use: any,\n testInfo: TestInfo,\n ) => {\n await generateAiFunction({\n page,\n testInfo,\n use,\n aiActionType: 'aiAssert',\n });\n },\n aiWaitFor: async (\n { page }: { page: OriginPlaywrightPage },\n use: any,\n testInfo: TestInfo,\n ) => {\n await generateAiFunction({\n page,\n testInfo,\n use,\n aiActionType: 'aiWaitFor',\n });\n },\n aiLocate: async (\n { page }: { page: OriginPlaywrightPage },\n use: any,\n testInfo: TestInfo,\n ) => {\n await generateAiFunction({\n page,\n testInfo,\n use,\n aiActionType: 'aiLocate',\n });\n },\n aiNumber: async (\n { page }: { page: OriginPlaywrightPage },\n use: any,\n testInfo: TestInfo,\n ) => {\n await generateAiFunction({\n page,\n testInfo,\n use,\n aiActionType: 'aiNumber',\n });\n },\n aiString: async (\n { page }: { page: OriginPlaywrightPage },\n use: any,\n testInfo: TestInfo,\n ) => {\n await generateAiFunction({\n page,\n testInfo,\n use,\n aiActionType: 'aiString',\n });\n },\n aiBoolean: async (\n { page }: { page: OriginPlaywrightPage },\n use: any,\n testInfo: TestInfo,\n ) => {\n await generateAiFunction({\n page,\n testInfo,\n use,\n aiActionType: 'aiBoolean',\n });\n },\n aiAsk: async (\n { page }: { page: OriginPlaywrightPage },\n use: any,\n testInfo: TestInfo,\n ) => {\n await generateAiFunction({\n page,\n testInfo,\n use,\n aiActionType: 'aiAsk',\n });\n },\n runYaml: async (\n { page }: { page: OriginPlaywrightPage },\n use: any,\n testInfo: TestInfo,\n ) => {\n await generateAiFunction({\n page,\n testInfo,\n use,\n aiActionType: 'runYaml',\n });\n },\n setAIActionContext: async (\n { page }: { page: OriginPlaywrightPage },\n use: any,\n testInfo: TestInfo,\n ) => {\n await generateAiFunction({\n page,\n testInfo,\n use,\n aiActionType: 'setAIActionContext',\n });\n },\n evaluateJavaScript: async (\n { page }: { page: OriginPlaywrightPage },\n use: any,\n testInfo: TestInfo,\n ) => {\n await generateAiFunction({\n page,\n testInfo,\n use,\n aiActionType: 'evaluateJavaScript',\n });\n },\n logScreenshot: async (\n { page }: { page: OriginPlaywrightPage },\n use: any,\n testInfo: TestInfo,\n ) => {\n await generateAiFunction({\n page,\n testInfo,\n use,\n aiActionType: 'logScreenshot',\n });\n },\n freezePageContext: async (\n { page }: { page: OriginPlaywrightPage },\n use: any,\n testInfo: TestInfo,\n ) => {\n await generateAiFunction({\n page,\n testInfo,\n use,\n aiActionType: 'freezePageContext',\n });\n },\n unfreezePageContext: async (\n { page }: { page: OriginPlaywrightPage },\n use: any,\n testInfo: TestInfo,\n ) => {\n await generateAiFunction({\n page,\n testInfo,\n use,\n aiActionType: 'unfreezePageContext',\n });\n },\n };\n};\n\nexport type PlayWrightAiFixtureType = {\n agentForPage: (\n page?: any,\n opts?: any,\n ) => Promise<PageAgent<PlaywrightWebPage>>;\n ai: <T = any>(prompt: string) => Promise<T>;\n aiAction: (taskPrompt: string) => ReturnType<PageAgent['aiAction']>;\n aiTap: (\n ...args: Parameters<PageAgent['aiTap']>\n ) => ReturnType<PageAgent['aiTap']>;\n aiRightClick: (\n ...args: Parameters<PageAgent['aiRightClick']>\n ) => ReturnType<PageAgent['aiRightClick']>;\n aiDoubleClick: (\n ...args: Parameters<PageAgent['aiDoubleClick']>\n ) => ReturnType<PageAgent['aiDoubleClick']>;\n aiHover: (\n ...args: Parameters<PageAgent['aiHover']>\n ) => ReturnType<PageAgent['aiHover']>;\n aiInput: (\n ...args: Parameters<PageAgent['aiInput']>\n ) => ReturnType<PageAgent['aiInput']>;\n aiKeyboardPress: (\n ...args: Parameters<PageAgent['aiKeyboardPress']>\n ) => ReturnType<PageAgent['aiKeyboardPress']>;\n aiScroll: (\n ...args: Parameters<PageAgent['aiScroll']>\n ) => ReturnType<PageAgent['aiScroll']>;\n aiQuery: <T = any>(...args: Parameters<PageAgent['aiQuery']>) => Promise<T>;\n aiAssert: (\n ...args: Parameters<PageAgent['aiAssert']>\n ) => ReturnType<PageAgent['aiAssert']>;\n aiWaitFor: (...args: Parameters<PageAgent['aiWaitFor']>) => Promise<void>;\n aiLocate: (\n ...args: Parameters<PageAgent['aiLocate']>\n ) => ReturnType<PageAgent['aiLocate']>;\n aiNumber: (\n ...args: Parameters<PageAgent['aiNumber']>\n ) => ReturnType<PageAgent['aiNumber']>;\n aiString: (\n ...args: Parameters<PageAgent['aiString']>\n ) => ReturnType<PageAgent['aiString']>;\n aiBoolean: (\n ...args: Parameters<PageAgent['aiBoolean']>\n ) => ReturnType<PageAgent['aiBoolean']>;\n aiAsk: (\n ...args: Parameters<PageAgent['aiAsk']>\n ) => ReturnType<PageAgent['aiAsk']>;\n runYaml: (\n ...args: Parameters<PageAgent['runYaml']>\n ) => ReturnType<PageAgent['runYaml']>;\n setAIActionContext: (\n ...args: Parameters<PageAgent['setAIActionContext']>\n ) => ReturnType<PageAgent['setAIActionContext']>;\n evaluateJavaScript: (\n ...args: Parameters<PageAgent['evaluateJavaScript']>\n ) => ReturnType<PageAgent['evaluateJavaScript']>;\n logScreenshot: (\n ...args: Parameters<PageAgent['logScreenshot']>\n ) => ReturnType<PageAgent['logScreenshot']>;\n freezePageContext: (\n ...args: Parameters<PageAgent['freezePageContext']>\n ) => ReturnType<PageAgent['freezePageContext']>;\n unfreezePageContext: (\n ...args: Parameters<PageAgent['unfreezePageContext']>\n ) => ReturnType<PageAgent['unfreezePageContext']>;\n};\n"],"names":["debugPage","getDebug","groupAndCaseForTest","testInfo","taskFile","taskTitle","titlePath","taskTitleWithRetry","replaceIllegalPathCharsAndSpace","midsceneAgentKeyId","midsceneDumpAnnotationId","PlaywrightAiFixture","options","forceSameTabNavigation","waitForNetworkIdleTimeout","DEFAULT_WAIT_FOR_NETWORK_IDLE_TIMEOUT","waitForNavigationTimeout","DEFAULT_WAIT_FOR_NAVIGATION_TIMEOUT","cache","processTestCacheConfig","id","processCacheConfig","pageAgentMap","createOrReuseAgentForPage","page","opts","idForPage","uuid","testId","file","title","cacheConfig","PlaywrightAgent","dump","updateDumpAnnotation","generateAiFunction","use","aiActionType","agent","taskPrompt","args","Promise","resolve","reject","test","JSON","error","console","result","tempFileName","Date","tempFilePath","join","tmpdir","writeFileSync","currentAnnotation","item","propsPage","finalCacheConfig","undefined","userCache"],"mappings":";;;;;;;;;AAmBA,MAAMA,YAAYC,SAAS;AAE3B,MAAMC,sBAAsB,CAACC;IAC3B,IAAIC;IACJ,IAAIC;IACJ,MAAMC,YAAY;WAAIH,SAAS,SAAS;KAAC;IAEzC,IAAIG,UAAU,MAAM,GAAG,GAAG;QACxBF,WAAWE,UAAU,KAAK,MAAM;QAChCD,YAAYC,UAAU,IAAI,CAAC;IAC7B,OAAO,IAAIA,AAAqB,MAArBA,UAAU,MAAM,EAAQ;QACjCD,YAAYC,SAAS,CAAC,EAAE;QACxBF,WAAW,GAAGC,WAAW;IAC3B,OAAO;QACLA,YAAY;QACZD,WAAW;IACb;IAEA,MAAMG,qBAAqB,GAAGF,YAAYF,SAAS,KAAK,GAAG,CAAC,QAAQ,EAAEA,SAAS,KAAK,CAAC,CAAC,CAAC,GAAG,IAAI;IAE9F,OAAO;QACL,MAAMC;QACN,IAAII,gCAAgC,GAAGJ,SAAS,CAAC,EAAEC,UAAU,CAAC,CAAC;QAC/D,OAAOG,gCAAgCD;IACzC;AACF;AAEA,MAAME,qBAAqB;AACpB,MAAMC,2BAA2B;AAQjC,MAAMC,sBAAsB,CAACC;IAMlC,MAAM,EACJC,yBAAyB,IAAI,EAC7BC,4BAA4BC,qCAAqC,EACjEC,2BAA2BC,mCAAmC,EAC9DC,KAAK,EACN,GAAGN,WAAW,CAAC;IAGhB,MAAMO,yBAAyB,CAAChB;QAE9B,MAAM,EAAEiB,EAAE,EAAE,GAAGlB,oBAAoBC;QAGnC,OAAOkB,mBAAmBH,OAAgBE;IAC5C;IAEA,MAAME,eAA6D,CAAC;IACpE,MAAMC,4BAA4B,CAChCC,MACArB,UACAsB;QAEA,IAAIC,YAAaF,IAAY,CAACf,mBAAmB;QACjD,IAAI,CAACiB,WAAW;YACdA,YAAYC;YACXH,IAAY,CAACf,mBAAmB,GAAGiB;YACpC,MAAM,EAAEE,MAAM,EAAE,GAAGzB;YACnB,MAAM,EAAE0B,IAAI,EAAEC,KAAK,EAAE,GAAG5B,oBAAoBC;YAC5C,MAAM4B,cAAcZ,uBAAuBhB;YAE3CmB,YAAY,CAACI,UAAU,GAAG,IAAIM,gBAAgBR,MAAM;gBAClD,QAAQ,CAAC,WAAW,EAAEI,OAAO,CAAC,EAAEF,WAAW;gBAC3Cb;gBACA,OAAOkB;gBACP,WAAWD;gBACX,kBAAkBD;gBAClB,gBAAgB;gBAChB,GAAGJ,IAAI;YACT;YAEAH,YAAY,CAACI,UAAU,CAAC,YAAY,GAAG,CAACO;gBACtCC,qBAAqB/B,UAAU8B;YACjC;YAEAT,KAAK,EAAE,CAAC,SAAS;gBACfxB,UAAU;gBACVsB,YAAY,CAACI,UAAU,CAAC,OAAO;gBAC/B,OAAOJ,YAAY,CAACI,UAAU;YAChC;QACF;QAEA,OAAOJ,YAAY,CAACI,UAAU;IAChC;IAEA,eAAeS,mBAAmBvB,OA4BjC;QACC,MAAM,EAAEY,IAAI,EAAErB,QAAQ,EAAEiC,GAAG,EAAEC,YAAY,EAAE,GAAGzB;QAC9C,MAAM0B,QAAQf,0BAA0BC,MAAMrB,UAAU;YACtDa;YACAF;QACF;QAEA,MAAMsB,IAAI,OAAOG,YAAoB,GAAGC,OAC/B,IAAIC,QAAQ,CAACC,SAASC;gBAC3BC,UAAAA,IAAS,CAAC,CAAC,GAAG,EAAEP,aAAa,GAAG,EAAEQ,KAAK,SAAS,CAACN,aAAa,EAAE;oBAC9D,IAAI;wBACFvC,UACE,CAAC,4BAA4B,EAAEc,2BAA2B;wBAE5D,MAAMwB,MAAM,kBAAkB,CAACxB;oBACjC,EAAE,OAAOgC,OAAO;wBACdC,QAAQ,IAAI,CACV;oBAEJ;oBACA,IAAI;wBAKF,MAAMC,SAAS,MAAOV,KAAK,CAACD,aAAa,CACvCE,eACIC,QAAQ,EAAE;wBAEhBE,QAAQM;oBACV,EAAE,OAAOF,OAAO;wBACdH,OAAOG;oBACT;gBACF;YACF;IAEJ;IAEA,MAAMZ,uBAAuB,CAACU,MAAgBX;QAE5C,MAAMgB,eAAe,CAAC,cAAc,EAAEL,KAAK,MAAM,IAAIjB,OAAO,CAAC,EAAEuB,KAAK,GAAG,GAAG,KAAK,CAAC;QAChF,MAAMC,eAAeC,KAAKC,UAAUJ;QAEpCK,cAAcH,cAAclB,MAAM;QAClCjC,UAAU,CAAC,2BAA2B,EAAEmD,cAAc;QAGtD,MAAMI,oBAAoBX,KAAK,WAAW,CAAC,IAAI,CAAC,CAACY,OACxCA,KAAK,IAAI,KAAK9C;QAEvB,IAAI6C,mBAEFA,kBAAkB,WAAW,GAAGJ;aAEhCP,KAAK,WAAW,CAAC,IAAI,CAAC;YACpB,MAAMlC;YACN,aAAayC;QACf;IAEJ;IAEA,OAAO;QACL,cAAc,OACZ,EAAE3B,IAAI,EAAkC,EACxCY,KACAjC;YAEA,MAAMiC,IACJ,OACEqB,WACAhC;gBAEA,MAAMM,cAAcZ,uBAAuBhB;gBAK3C,IAAIuD,mBAAmB3B;gBACvB,IAAIN,AAAAA,CAAAA,QAAAA,OAAAA,KAAAA,IAAAA,KAAM,KAAK,AAAD,MAAMkC,QAAW;oBAC7B,MAAMC,YAAYnC,KAAK,KAAK;oBAC5B,IAAImC,AAAc,UAAdA,WACFF,mBAAmB;yBACd,IAAIE,AAAc,SAAdA,WAAoB;wBAE7B,MAAM,EAAExC,EAAE,EAAE,GAAGlB,oBAAoBC;wBACnCuD,mBAAmB;4BAAEtC;wBAAG;oBAC1B,OAAO,IAAI,AAAqB,YAArB,OAAOwC,WAChB,IAAKA,UAAU,EAAE,EAKfF,mBAAmBE;yBALF;wBAEjB,MAAM,EAAExC,EAAE,EAAE,GAAGlB,oBAAoBC;wBACnCuD,mBAAmB;4BAAE,GAAGE,SAAS;4BAAExC;wBAAG;oBACxC;gBAIJ;gBAEA,MAAMkB,QAAQf,0BAA0BkC,aAAajC,MAAMrB,UAAU;oBACnEa;oBACAF;oBACA,OAAO4C;oBACP,GAAGjC,IAAI;gBACT;gBACA,OAAOa;YACT;QAEJ;QACA,IAAI,OACF,EAAEd,IAAI,EAAkC,EACxCY,KACAjC;YAEA,MAAMgC,mBAAmB;gBACvBX;gBACArB;gBACAiC;gBACA,cAAc;YAChB;QACF;QACA,UAAU,OACR,EAAEZ,IAAI,EAAkC,EACxCY,KACAjC;YAEA,MAAMgC,mBAAmB;gBACvBX;gBACArB;gBACAiC;gBACA,cAAc;YAChB;QACF;QACA,OAAO,OACL,EAAEZ,IAAI,EAAkC,EACxCY,KACAjC;YAEA,MAAMgC,mBAAmB;gBACvBX;gBACArB;gBACAiC;gBACA,cAAc;YAChB;QACF;QACA,cAAc,OACZ,EAAEZ,IAAI,EAAkC,EACxCY,KACAjC;YAEA,MAAMgC,mBAAmB;gBACvBX;gBACArB;gBACAiC;gBACA,cAAc;YAChB;QACF;QACA,eAAe,OACb,EAAEZ,IAAI,EAAkC,EACxCY,KACAjC;YAEA,MAAMgC,mBAAmB;gBACvBX;gBACArB;gBACAiC;gBACA,cAAc;YAChB;QACF;QACA,SAAS,OACP,EAAEZ,IAAI,EAAkC,EACxCY,KACAjC;YAEA,MAAMgC,mBAAmB;gBACvBX;gBACArB;gBACAiC;gBACA,cAAc;YAChB;QACF;QACA,SAAS,OACP,EAAEZ,IAAI,EAAkC,EACxCY,KACAjC;YAEA,MAAMgC,mBAAmB;gBACvBX;gBACArB;gBACAiC;gBACA,cAAc;YAChB;QACF;QACA,iBAAiB,OACf,EAAEZ,IAAI,EAAkC,EACxCY,KACAjC;YAEA,MAAMgC,mBAAmB;gBACvBX;gBACArB;gBACAiC;gBACA,cAAc;YAChB;QACF;QACA,UAAU,OACR,EAAEZ,IAAI,EAAkC,EACxCY,KACAjC;YAEA,MAAMgC,mBAAmB;gBACvBX;gBACArB;gBACAiC;gBACA,cAAc;YAChB;QACF;QACA,SAAS,OACP,EAAEZ,IAAI,EAAkC,EACxCY,KACAjC;YAEA,MAAMgC,mBAAmB;gBACvBX;gBACArB;gBACAiC;gBACA,cAAc;YAChB;QACF;QACA,UAAU,OACR,EAAEZ,IAAI,EAAkC,EACxCY,KACAjC;YAEA,MAAMgC,mBAAmB;gBACvBX;gBACArB;gBACAiC;gBACA,cAAc;YAChB;QACF;QACA,WAAW,OACT,EAAEZ,IAAI,EAAkC,EACxCY,KACAjC;YAEA,MAAMgC,mBAAmB;gBACvBX;gBACArB;gBACAiC;gBACA,cAAc;YAChB;QACF;QACA,UAAU,OACR,EAAEZ,IAAI,EAAkC,EACxCY,KACAjC;YAEA,MAAMgC,mBAAmB;gBACvBX;gBACArB;gBACAiC;gBACA,cAAc;YAChB;QACF;QACA,UAAU,OACR,EAAEZ,IAAI,EAAkC,EACxCY,KACAjC;YAEA,MAAMgC,mBAAmB;gBACvBX;gBACArB;gBACAiC;gBACA,cAAc;YAChB;QACF;QACA,UAAU,OACR,EAAEZ,IAAI,EAAkC,EACxCY,KACAjC;YAEA,MAAMgC,mBAAmB;gBACvBX;gBACArB;gBACAiC;gBACA,cAAc;YAChB;QACF;QACA,WAAW,OACT,EAAEZ,IAAI,EAAkC,EACxCY,KACAjC;YAEA,MAAMgC,mBAAmB;gBACvBX;gBACArB;gBACAiC;gBACA,cAAc;YAChB;QACF;QACA,OAAO,OACL,EAAEZ,IAAI,EAAkC,EACxCY,KACAjC;YAEA,MAAMgC,mBAAmB;gBACvBX;gBACArB;gBACAiC;gBACA,cAAc;YAChB;QACF;QACA,SAAS,OACP,EAAEZ,IAAI,EAAkC,EACxCY,KACAjC;YAEA,MAAMgC,mBAAmB;gBACvBX;gBACArB;gBACAiC;gBACA,cAAc;YAChB;QACF;QACA,oBAAoB,OAClB,EAAEZ,IAAI,EAAkC,EACxCY,KACAjC;YAEA,MAAMgC,mBAAmB;gBACvBX;gBACArB;gBACAiC;gBACA,cAAc;YAChB;QACF;QACA,oBAAoB,OAClB,EAAEZ,IAAI,EAAkC,EACxCY,KACAjC;YAEA,MAAMgC,mBAAmB;gBACvBX;gBACArB;gBACAiC;gBACA,cAAc;YAChB;QACF;QACA,eAAe,OACb,EAAEZ,IAAI,EAAkC,EACxCY,KACAjC;YAEA,MAAMgC,mBAAmB;gBACvBX;gBACArB;gBACAiC;gBACA,cAAc;YAChB;QACF;QACA,mBAAmB,OACjB,EAAEZ,IAAI,EAAkC,EACxCY,KACAjC;YAEA,MAAMgC,mBAAmB;gBACvBX;gBACArB;gBACAiC;gBACA,cAAc;YAChB;QACF;QACA,qBAAqB,OACnB,EAAEZ,IAAI,EAAkC,EACxCY,KACAjC;YAEA,MAAMgC,mBAAmB;gBACvBX;gBACArB;gBACAiC;gBACA,cAAc;YAChB;QACF;IACF;AACF"}
|
|
1
|
+
{"version":3,"file":"playwright/ai-fixture.mjs","sources":["../../../src/playwright/ai-fixture.ts"],"sourcesContent":["import { rmSync, writeFileSync } from 'node:fs';\nimport { tmpdir } from 'node:os';\nimport { join } from 'node:path';\nimport { PlaywrightAgent, type PlaywrightWebPage } from '@/playwright/index';\nimport type { WebPageAgentOpt } from '@/web-element';\nimport type { Cache } from '@midscene/core';\nimport type { AgentOpt, Agent as PageAgent } from '@midscene/core/agent';\nimport { processCacheConfig } from '@midscene/core/utils';\nimport {\n DEFAULT_WAIT_FOR_NAVIGATION_TIMEOUT,\n DEFAULT_WAIT_FOR_NETWORK_IDLE_TIMEOUT,\n} from '@midscene/shared/constants';\nimport { getDebug } from '@midscene/shared/logger';\nimport { uuid } from '@midscene/shared/utils';\nimport { replaceIllegalPathCharsAndSpace } from '@midscene/shared/utils';\nimport { type TestInfo, type TestType, test } from '@playwright/test';\nimport type { Page as OriginPlaywrightPage } from 'playwright';\nexport type APITestType = Pick<TestType<any, any>, 'step'>;\n\nconst debugPage = getDebug('web:playwright:ai-fixture');\n\nconst groupAndCaseForTest = (testInfo: TestInfo) => {\n let taskFile: string;\n let taskTitle: string;\n const titlePath = [...testInfo.titlePath];\n\n if (titlePath.length > 1) {\n taskFile = titlePath.shift() || 'unnamed';\n taskTitle = titlePath.join('__');\n } else if (titlePath.length === 1) {\n taskTitle = titlePath[0];\n taskFile = `${taskTitle}`;\n } else {\n taskTitle = 'unnamed';\n taskFile = 'unnamed';\n }\n\n const taskTitleWithRetry = `${taskTitle}${testInfo.retry ? `(retry #${testInfo.retry})` : ''}`;\n\n return {\n file: taskFile,\n id: replaceIllegalPathCharsAndSpace(`${taskFile}(${taskTitle})`),\n title: replaceIllegalPathCharsAndSpace(taskTitleWithRetry),\n };\n};\n\nconst midsceneAgentKeyId = '_midsceneAgentId';\nexport const midsceneDumpAnnotationId = 'MIDSCENE_DUMP_ANNOTATION';\n\n// Track temporary dump files per page for cleanup\nconst pageTempFiles = new Map<string, string>();\n\ntype PlaywrightCacheConfig = {\n strategy?: 'read-only' | 'read-write' | 'write-only';\n id?: string;\n};\ntype PlaywrightCache = false | true | PlaywrightCacheConfig;\n\nexport const PlaywrightAiFixture = (options?: {\n forceSameTabNavigation?: boolean;\n waitForNetworkIdleTimeout?: number;\n waitForNavigationTimeout?: number;\n cache?: PlaywrightCache;\n}) => {\n const {\n forceSameTabNavigation = true,\n waitForNetworkIdleTimeout = DEFAULT_WAIT_FOR_NETWORK_IDLE_TIMEOUT,\n waitForNavigationTimeout = DEFAULT_WAIT_FOR_NAVIGATION_TIMEOUT,\n cache,\n } = options ?? {};\n\n // Helper function to process cache configuration and auto-generate ID from test info\n const processTestCacheConfig = (testInfo: TestInfo): Cache | undefined => {\n // Generate ID from test info\n const { id } = groupAndCaseForTest(testInfo);\n\n // Use shared processCacheConfig with generated ID as fallback\n return processCacheConfig(cache as Cache, id);\n };\n\n const pageAgentMap: Record<string, PageAgent<PlaywrightWebPage>> = {};\n const createOrReuseAgentForPage = (\n page: OriginPlaywrightPage,\n testInfo: TestInfo, // { testId: string; taskFile: string; taskTitle: string },\n opts?: WebPageAgentOpt,\n ) => {\n let idForPage = (page as any)[midsceneAgentKeyId];\n if (!idForPage) {\n idForPage = uuid();\n (page as any)[midsceneAgentKeyId] = idForPage;\n const { testId } = testInfo;\n const { file, title } = groupAndCaseForTest(testInfo);\n const cacheConfig = processTestCacheConfig(testInfo);\n\n pageAgentMap[idForPage] = new PlaywrightAgent(page, {\n testId: `playwright-${testId}-${idForPage}`,\n forceSameTabNavigation,\n cache: cacheConfig,\n groupName: title,\n groupDescription: file,\n generateReport: false, // we will generate it in the reporter\n ...opts,\n });\n\n pageAgentMap[idForPage].onDumpUpdate = (dump: string) => {\n updateDumpAnnotation(testInfo, dump, idForPage);\n };\n\n page.on('close', () => {\n debugPage('page closed');\n\n // Note: We don't clean up temp files here because the reporter\n // needs to read them in onTestEnd. The reporter will clean them up\n // after reading. If the test is interrupted (Ctrl+C), the process\n // exit handlers will clean up remaining temp files.\n\n // However, we do clean up the pageTempFiles Map entry to avoid memory leaks\n pageTempFiles.delete(idForPage);\n\n pageAgentMap[idForPage].destroy();\n delete pageAgentMap[idForPage];\n });\n }\n\n return pageAgentMap[idForPage];\n };\n\n async function generateAiFunction(options: {\n page: OriginPlaywrightPage;\n testInfo: TestInfo;\n use: any;\n aiActionType:\n | 'ai'\n | 'aiAct'\n | 'aiAction'\n | 'aiHover'\n | 'aiInput'\n | 'aiKeyboardPress'\n | 'aiScroll'\n | 'aiTap'\n | 'aiRightClick'\n | 'aiDoubleClick'\n | 'aiQuery'\n | 'aiAssert'\n | 'aiWaitFor'\n | 'aiLocate'\n | 'aiNumber'\n | 'aiString'\n | 'aiBoolean'\n | 'aiAsk'\n | 'runYaml'\n | 'setAIActionContext'\n | 'evaluateJavaScript'\n | 'recordToReport'\n | 'logScreenshot'\n | 'freezePageContext'\n | 'unfreezePageContext';\n }) {\n const { page, testInfo, use, aiActionType } = options;\n const agent = createOrReuseAgentForPage(page, testInfo, {\n waitForNavigationTimeout,\n waitForNetworkIdleTimeout,\n }) as PlaywrightAgent;\n\n await use(async (taskPrompt: string, ...args: any[]) => {\n return new Promise((resolve, reject) => {\n test.step(`ai-${aiActionType} - ${JSON.stringify(taskPrompt)}`, async () => {\n try {\n debugPage(\n `waitForNetworkIdle timeout: ${waitForNetworkIdleTimeout}`,\n );\n await agent.waitForNetworkIdle(waitForNetworkIdleTimeout);\n } catch (error) {\n console.warn(\n '[midscene:warning] Waiting for network idle has timed out, but Midscene will continue execution. Please check https://midscenejs.com/faq.html#customize-the-network-timeout for more information on customizing the network timeout',\n );\n }\n try {\n type AgentMethod = (\n prompt: string,\n ...restArgs: any[]\n ) => Promise<any>;\n const result = await (agent[aiActionType] as AgentMethod).bind(\n agent,\n )(taskPrompt, ...args);\n resolve(result);\n } catch (error) {\n reject(error);\n }\n });\n });\n });\n }\n\n const updateDumpAnnotation = (\n test: TestInfo,\n dump: string,\n pageId: string,\n ) => {\n // 1. First, clean up the old temp file if it exists\n const oldTempFilePath = pageTempFiles.get(pageId);\n if (oldTempFilePath) {\n try {\n rmSync(oldTempFilePath, { force: true });\n } catch (error) {\n // Silently ignore if old file is already cleaned up\n }\n }\n\n // 2. Create new temp file with predictable name using pageId\n const tempFileName = `midscene-dump-${test.testId || uuid()}-${pageId}.json`;\n const tempFilePath = join(tmpdir(), tempFileName);\n\n // 3. Write dump to the new temporary file\n try {\n writeFileSync(tempFilePath, dump, 'utf-8');\n debugPage(`Dump written to temp file: ${tempFilePath}`);\n\n // 4. Track the new temp file (only if write succeeded)\n pageTempFiles.set(pageId, tempFilePath);\n\n // Store only the file path in annotation (only if write succeeded)\n const currentAnnotation = test.annotations.find((item) => {\n return item.type === midsceneDumpAnnotationId;\n });\n if (currentAnnotation) {\n // Store file path instead of dump content\n currentAnnotation.description = tempFilePath;\n } else {\n test.annotations.push({\n type: midsceneDumpAnnotationId,\n description: tempFilePath,\n });\n }\n } catch (error) {\n // If write fails (e.g., disk full), don't track the file or add annotation\n // This prevents reporter from trying to read a non-existent file\n debugPage(\n `Failed to write temp file: ${tempFilePath}. Skipping annotation.`,\n error,\n );\n }\n };\n\n return {\n agentForPage: async (\n { page }: { page: OriginPlaywrightPage },\n use: any,\n testInfo: TestInfo,\n ) => {\n await use(\n async (\n propsPage?: OriginPlaywrightPage | undefined,\n opts?: AgentOpt,\n ) => {\n const cacheConfig = processTestCacheConfig(testInfo);\n\n // Handle cache configuration priority:\n // 1. If user provides cache in opts, use it (but auto-generate ID if missing)\n // 2. Otherwise use fixture's cache config\n let finalCacheConfig = cacheConfig;\n if (opts?.cache !== undefined) {\n const userCache = opts.cache;\n if (userCache === false) {\n finalCacheConfig = false;\n } else if (userCache === true) {\n // Auto-generate ID for user's cache: true\n const { id } = groupAndCaseForTest(testInfo);\n finalCacheConfig = { id };\n } else if (typeof userCache === 'object') {\n if (!userCache.id) {\n // Auto-generate ID for user's cache object without ID\n const { id } = groupAndCaseForTest(testInfo);\n finalCacheConfig = { ...userCache, id };\n } else {\n finalCacheConfig = userCache;\n }\n }\n }\n\n const agent = createOrReuseAgentForPage(propsPage || page, testInfo, {\n waitForNavigationTimeout,\n waitForNetworkIdleTimeout,\n cache: finalCacheConfig,\n ...opts,\n });\n return agent;\n },\n );\n },\n ai: async (\n { page }: { page: OriginPlaywrightPage },\n use: any,\n testInfo: TestInfo,\n ) => {\n await generateAiFunction({\n page,\n testInfo,\n use,\n aiActionType: 'ai',\n });\n },\n aiAct: async (\n { page }: { page: OriginPlaywrightPage },\n use: any,\n testInfo: TestInfo,\n ) => {\n await generateAiFunction({\n page,\n testInfo,\n use,\n aiActionType: 'aiAct',\n });\n },\n /**\n * @deprecated Use {@link PlaywrightAiFixture.aiAct} instead.\n */\n aiAction: async (\n { page }: { page: OriginPlaywrightPage },\n use: any,\n testInfo: TestInfo,\n ) => {\n await generateAiFunction({\n page,\n testInfo,\n use,\n aiActionType: 'aiAction',\n });\n },\n aiTap: async (\n { page }: { page: OriginPlaywrightPage },\n use: any,\n testInfo: TestInfo,\n ) => {\n await generateAiFunction({\n page,\n testInfo,\n use,\n aiActionType: 'aiTap',\n });\n },\n aiRightClick: async (\n { page }: { page: OriginPlaywrightPage },\n use: any,\n testInfo: TestInfo,\n ) => {\n await generateAiFunction({\n page,\n testInfo,\n use,\n aiActionType: 'aiRightClick',\n });\n },\n aiDoubleClick: async (\n { page }: { page: OriginPlaywrightPage },\n use: any,\n testInfo: TestInfo,\n ) => {\n await generateAiFunction({\n page,\n testInfo,\n use,\n aiActionType: 'aiDoubleClick',\n });\n },\n aiHover: async (\n { page }: { page: OriginPlaywrightPage },\n use: any,\n testInfo: TestInfo,\n ) => {\n await generateAiFunction({\n page,\n testInfo,\n use,\n aiActionType: 'aiHover',\n });\n },\n aiInput: async (\n { page }: { page: OriginPlaywrightPage },\n use: any,\n testInfo: TestInfo,\n ) => {\n await generateAiFunction({\n page,\n testInfo,\n use,\n aiActionType: 'aiInput',\n });\n },\n aiKeyboardPress: async (\n { page }: { page: OriginPlaywrightPage },\n use: any,\n testInfo: TestInfo,\n ) => {\n await generateAiFunction({\n page,\n testInfo,\n use,\n aiActionType: 'aiKeyboardPress',\n });\n },\n aiScroll: async (\n { page }: { page: OriginPlaywrightPage },\n use: any,\n testInfo: TestInfo,\n ) => {\n await generateAiFunction({\n page,\n testInfo,\n use,\n aiActionType: 'aiScroll',\n });\n },\n aiQuery: async (\n { page }: { page: OriginPlaywrightPage },\n use: any,\n testInfo: TestInfo,\n ) => {\n await generateAiFunction({\n page,\n testInfo,\n use,\n aiActionType: 'aiQuery',\n });\n },\n aiAssert: async (\n { page }: { page: OriginPlaywrightPage },\n use: any,\n testInfo: TestInfo,\n ) => {\n await generateAiFunction({\n page,\n testInfo,\n use,\n aiActionType: 'aiAssert',\n });\n },\n aiWaitFor: async (\n { page }: { page: OriginPlaywrightPage },\n use: any,\n testInfo: TestInfo,\n ) => {\n await generateAiFunction({\n page,\n testInfo,\n use,\n aiActionType: 'aiWaitFor',\n });\n },\n aiLocate: async (\n { page }: { page: OriginPlaywrightPage },\n use: any,\n testInfo: TestInfo,\n ) => {\n await generateAiFunction({\n page,\n testInfo,\n use,\n aiActionType: 'aiLocate',\n });\n },\n aiNumber: async (\n { page }: { page: OriginPlaywrightPage },\n use: any,\n testInfo: TestInfo,\n ) => {\n await generateAiFunction({\n page,\n testInfo,\n use,\n aiActionType: 'aiNumber',\n });\n },\n aiString: async (\n { page }: { page: OriginPlaywrightPage },\n use: any,\n testInfo: TestInfo,\n ) => {\n await generateAiFunction({\n page,\n testInfo,\n use,\n aiActionType: 'aiString',\n });\n },\n aiBoolean: async (\n { page }: { page: OriginPlaywrightPage },\n use: any,\n testInfo: TestInfo,\n ) => {\n await generateAiFunction({\n page,\n testInfo,\n use,\n aiActionType: 'aiBoolean',\n });\n },\n aiAsk: async (\n { page }: { page: OriginPlaywrightPage },\n use: any,\n testInfo: TestInfo,\n ) => {\n await generateAiFunction({\n page,\n testInfo,\n use,\n aiActionType: 'aiAsk',\n });\n },\n runYaml: async (\n { page }: { page: OriginPlaywrightPage },\n use: any,\n testInfo: TestInfo,\n ) => {\n await generateAiFunction({\n page,\n testInfo,\n use,\n aiActionType: 'runYaml',\n });\n },\n setAIActionContext: async (\n { page }: { page: OriginPlaywrightPage },\n use: any,\n testInfo: TestInfo,\n ) => {\n await generateAiFunction({\n page,\n testInfo,\n use,\n aiActionType: 'setAIActionContext',\n });\n },\n evaluateJavaScript: async (\n { page }: { page: OriginPlaywrightPage },\n use: any,\n testInfo: TestInfo,\n ) => {\n await generateAiFunction({\n page,\n testInfo,\n use,\n aiActionType: 'evaluateJavaScript',\n });\n },\n recordToReport: async (\n { page }: { page: OriginPlaywrightPage },\n use: any,\n testInfo: TestInfo,\n ) => {\n await generateAiFunction({\n page,\n testInfo,\n use,\n aiActionType: 'recordToReport',\n });\n },\n logScreenshot: async (\n { page }: { page: OriginPlaywrightPage },\n use: any,\n testInfo: TestInfo,\n ) => {\n await generateAiFunction({\n page,\n testInfo,\n use,\n aiActionType: 'logScreenshot',\n });\n },\n freezePageContext: async (\n { page }: { page: OriginPlaywrightPage },\n use: any,\n testInfo: TestInfo,\n ) => {\n await generateAiFunction({\n page,\n testInfo,\n use,\n aiActionType: 'freezePageContext',\n });\n },\n unfreezePageContext: async (\n { page }: { page: OriginPlaywrightPage },\n use: any,\n testInfo: TestInfo,\n ) => {\n await generateAiFunction({\n page,\n testInfo,\n use,\n aiActionType: 'unfreezePageContext',\n });\n },\n };\n};\n\nexport type PlayWrightAiFixtureType = {\n agentForPage: (\n page?: any,\n opts?: any,\n ) => Promise<PageAgent<PlaywrightWebPage>>;\n ai: <T = any>(...args: Parameters<PageAgent['ai']>) => Promise<T>;\n aiAct: (\n ...args: Parameters<PageAgent['aiAct']>\n ) => ReturnType<PageAgent['aiAct']>;\n /**\n * @deprecated Use {@link PlayWrightAiFixtureType.aiAct} instead.\n */\n aiAction: (\n ...args: Parameters<PageAgent['aiAction']>\n ) => ReturnType<PageAgent['aiAction']>;\n aiTap: (\n ...args: Parameters<PageAgent['aiTap']>\n ) => ReturnType<PageAgent['aiTap']>;\n aiRightClick: (\n ...args: Parameters<PageAgent['aiRightClick']>\n ) => ReturnType<PageAgent['aiRightClick']>;\n aiDoubleClick: (\n ...args: Parameters<PageAgent['aiDoubleClick']>\n ) => ReturnType<PageAgent['aiDoubleClick']>;\n aiHover: (\n ...args: Parameters<PageAgent['aiHover']>\n ) => ReturnType<PageAgent['aiHover']>;\n aiInput: (\n ...args: Parameters<PageAgent['aiInput']>\n ) => ReturnType<PageAgent['aiInput']>;\n aiKeyboardPress: (\n ...args: Parameters<PageAgent['aiKeyboardPress']>\n ) => ReturnType<PageAgent['aiKeyboardPress']>;\n aiScroll: (\n ...args: Parameters<PageAgent['aiScroll']>\n ) => ReturnType<PageAgent['aiScroll']>;\n aiQuery: <T = any>(...args: Parameters<PageAgent['aiQuery']>) => Promise<T>;\n aiAssert: (\n ...args: Parameters<PageAgent['aiAssert']>\n ) => ReturnType<PageAgent['aiAssert']>;\n aiWaitFor: (...args: Parameters<PageAgent['aiWaitFor']>) => Promise<void>;\n aiLocate: (\n ...args: Parameters<PageAgent['aiLocate']>\n ) => ReturnType<PageAgent['aiLocate']>;\n aiNumber: (\n ...args: Parameters<PageAgent['aiNumber']>\n ) => ReturnType<PageAgent['aiNumber']>;\n aiString: (\n ...args: Parameters<PageAgent['aiString']>\n ) => ReturnType<PageAgent['aiString']>;\n aiBoolean: (\n ...args: Parameters<PageAgent['aiBoolean']>\n ) => ReturnType<PageAgent['aiBoolean']>;\n aiAsk: (\n ...args: Parameters<PageAgent['aiAsk']>\n ) => ReturnType<PageAgent['aiAsk']>;\n runYaml: (\n ...args: Parameters<PageAgent['runYaml']>\n ) => ReturnType<PageAgent['runYaml']>;\n setAIActionContext: (\n ...args: Parameters<PageAgent['setAIActionContext']>\n ) => ReturnType<PageAgent['setAIActionContext']>;\n evaluateJavaScript: (\n ...args: Parameters<PageAgent['evaluateJavaScript']>\n ) => ReturnType<PageAgent['evaluateJavaScript']>;\n recordToReport: (\n ...args: Parameters<PageAgent['recordToReport']>\n ) => ReturnType<PageAgent['recordToReport']>;\n logScreenshot: (\n ...args: Parameters<PageAgent['logScreenshot']>\n ) => ReturnType<PageAgent['logScreenshot']>;\n freezePageContext: (\n ...args: Parameters<PageAgent['freezePageContext']>\n ) => ReturnType<PageAgent['freezePageContext']>;\n unfreezePageContext: (\n ...args: Parameters<PageAgent['unfreezePageContext']>\n ) => ReturnType<PageAgent['unfreezePageContext']>;\n};\n"],"names":["debugPage","getDebug","groupAndCaseForTest","testInfo","taskFile","taskTitle","titlePath","taskTitleWithRetry","replaceIllegalPathCharsAndSpace","midsceneAgentKeyId","midsceneDumpAnnotationId","pageTempFiles","Map","PlaywrightAiFixture","options","forceSameTabNavigation","waitForNetworkIdleTimeout","DEFAULT_WAIT_FOR_NETWORK_IDLE_TIMEOUT","waitForNavigationTimeout","DEFAULT_WAIT_FOR_NAVIGATION_TIMEOUT","cache","processTestCacheConfig","id","processCacheConfig","pageAgentMap","createOrReuseAgentForPage","page","opts","idForPage","uuid","testId","file","title","cacheConfig","PlaywrightAgent","dump","updateDumpAnnotation","generateAiFunction","use","aiActionType","agent","taskPrompt","args","Promise","resolve","reject","test","JSON","error","console","result","pageId","oldTempFilePath","rmSync","tempFileName","tempFilePath","join","tmpdir","writeFileSync","currentAnnotation","item","propsPage","finalCacheConfig","undefined","userCache"],"mappings":"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"}
|
|
@@ -3,8 +3,19 @@ import { WebPage } from "./page.mjs";
|
|
|
3
3
|
import { PlaywrightAiFixture } from "./ai-fixture.mjs";
|
|
4
4
|
import { overrideAIConfig } from "@midscene/shared/env";
|
|
5
5
|
import { getDebug } from "@midscene/shared/logger";
|
|
6
|
-
import
|
|
6
|
+
import semver from "semver";
|
|
7
|
+
import { forceChromeSelectRendering as base_page_mjs_forceChromeSelectRendering, forceClosePopup } from "../puppeteer/base-page.mjs";
|
|
8
|
+
import { getWebpackRequire } from "../utils.mjs";
|
|
7
9
|
const debug = getDebug('playwright:agent');
|
|
10
|
+
function getPlaywrightVersion() {
|
|
11
|
+
try {
|
|
12
|
+
const playwrightPkg = getWebpackRequire()('playwright/package.json');
|
|
13
|
+
return playwrightPkg.version || null;
|
|
14
|
+
} catch (error) {
|
|
15
|
+
console.error('[midscene:error] Failed to get Playwright version', error);
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
8
19
|
class PlaywrightAgent extends Agent {
|
|
9
20
|
async waitForNetworkIdle(timeout = 1000) {
|
|
10
21
|
await this.page.underlyingPage.waitForLoadState('networkidle', {
|
|
@@ -14,8 +25,13 @@ class PlaywrightAgent extends Agent {
|
|
|
14
25
|
constructor(page, opts){
|
|
15
26
|
const webPage = new WebPage(page, opts);
|
|
16
27
|
super(webPage, opts);
|
|
17
|
-
const { forceSameTabNavigation = true } = opts ?? {};
|
|
28
|
+
const { forceSameTabNavigation = true, forceChromeSelectRendering } = opts ?? {};
|
|
18
29
|
if (forceSameTabNavigation) forceClosePopup(page, debug);
|
|
30
|
+
if (forceChromeSelectRendering) {
|
|
31
|
+
const playwrightVersion = getPlaywrightVersion();
|
|
32
|
+
if (playwrightVersion && !semver.gte(playwrightVersion, '1.52.0')) console.warn(`[midscene:error] forceChromeSelectRendering requires Playwright >= 1.52.0, but current version is ${playwrightVersion}. This feature may not work correctly.`);
|
|
33
|
+
base_page_mjs_forceChromeSelectRendering(page);
|
|
34
|
+
}
|
|
19
35
|
}
|
|
20
36
|
}
|
|
21
37
|
export { PlaywrightAgent, PlaywrightAiFixture, WebPage as PlaywrightWebPage, overrideAIConfig };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"playwright/index.mjs","sources":["
|
|
1
|
+
{"version":3,"file":"playwright/index.mjs","sources":["../../../src/playwright/index.ts"],"sourcesContent":["import { Agent as PageAgent } from '@midscene/core/agent';\nimport type { Page as PlaywrightPage } from 'playwright';\nimport { WebPage as PlaywrightWebPage } from './page';\n\nexport type { PlayWrightAiFixtureType } from './ai-fixture';\nexport { PlaywrightAiFixture } from './ai-fixture';\nexport { overrideAIConfig } from '@midscene/shared/env';\nexport { WebPage as PlaywrightWebPage } from './page';\nexport type { WebPageAgentOpt } from '@/web-element';\nimport type { WebPageAgentOpt } from '@/web-element';\nimport { getDebug } from '@midscene/shared/logger';\nimport semver from 'semver';\nimport {\n forceChromeSelectRendering as applyChromeSelectRendering,\n forceClosePopup,\n} from '../puppeteer/base-page';\nimport { getWebpackRequire } from '../utils';\n\nconst debug = getDebug('playwright:agent');\n\n/**\n * Get Playwright version from package.json\n */\nfunction getPlaywrightVersion(): string | null {\n try {\n const playwrightPkg = getWebpackRequire()('playwright/package.json');\n return playwrightPkg.version || null;\n } catch (error) {\n console.error('[midscene:error] Failed to get Playwright version', error);\n return null;\n }\n}\n\nexport class PlaywrightAgent extends PageAgent<PlaywrightWebPage> {\n constructor(page: PlaywrightPage, opts?: WebPageAgentOpt) {\n const webPage = new PlaywrightWebPage(page, opts);\n super(webPage, opts);\n\n const { forceSameTabNavigation = true, forceChromeSelectRendering } =\n opts ?? {};\n\n if (forceSameTabNavigation) {\n forceClosePopup(page, debug);\n }\n\n if (forceChromeSelectRendering) {\n // Check Playwright version requirement (>= 1.52)\n const playwrightVersion = getPlaywrightVersion();\n if (playwrightVersion && !semver.gte(playwrightVersion, '1.52.0')) {\n console.warn(\n `[midscene:error] forceChromeSelectRendering requires Playwright >= 1.52.0, but current version is ${playwrightVersion}. This feature may not work correctly.`,\n );\n }\n applyChromeSelectRendering(page);\n }\n }\n\n async waitForNetworkIdle(timeout = 1000) {\n await this.page.underlyingPage.waitForLoadState('networkidle', { timeout });\n }\n}\n"],"names":["debug","getDebug","getPlaywrightVersion","playwrightPkg","getWebpackRequire","error","console","PlaywrightAgent","PageAgent","timeout","page","opts","webPage","PlaywrightWebPage","forceSameTabNavigation","forceChromeSelectRendering","forceClosePopup","playwrightVersion","semver","applyChromeSelectRendering"],"mappings":";;;;;;;;AAkBA,MAAMA,QAAQC,SAAS;AAKvB,SAASC;IACP,IAAI;QACF,MAAMC,gBAAgBC,oBAAoB;QAC1C,OAAOD,cAAc,OAAO,IAAI;IAClC,EAAE,OAAOE,OAAO;QACdC,QAAQ,KAAK,CAAC,qDAAqDD;QACnE,OAAO;IACT;AACF;AAEO,MAAME,wBAAwBC;IAwBnC,MAAM,mBAAmBC,UAAU,IAAI,EAAE;QACvC,MAAM,IAAI,CAAC,IAAI,CAAC,cAAc,CAAC,gBAAgB,CAAC,eAAe;YAAEA;QAAQ;IAC3E;IAzBA,YAAYC,IAAoB,EAAEC,IAAsB,CAAE;QACxD,MAAMC,UAAU,IAAIC,QAAkBH,MAAMC;QAC5C,KAAK,CAACC,SAASD;QAEf,MAAM,EAAEG,yBAAyB,IAAI,EAAEC,0BAA0B,EAAE,GACjEJ,QAAQ,CAAC;QAEX,IAAIG,wBACFE,gBAAgBN,MAAMV;QAGxB,IAAIe,4BAA4B;YAE9B,MAAME,oBAAoBf;YAC1B,IAAIe,qBAAqB,CAACC,OAAO,GAAG,CAACD,mBAAmB,WACtDX,QAAQ,IAAI,CACV,CAAC,kGAAkG,EAAEW,kBAAkB,sCAAsC,CAAC;YAGlKE,yCAA2BT;QAC7B;IACF;AAKF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"playwright/page.mjs","sources":["
|
|
1
|
+
{"version":3,"file":"playwright/page.mjs","sources":["../../../src/playwright/page.ts"],"sourcesContent":["import type { Page as PlaywrightPageType } from 'playwright';\nimport { Page as BasePage } from '../puppeteer/base-page';\nimport type { WebPageOpt } from '../web-element';\n\nexport class WebPage extends BasePage<'playwright', PlaywrightPageType> {\n constructor(page: PlaywrightPageType, opts?: WebPageOpt) {\n super(page, 'playwright', opts);\n }\n}\n"],"names":["WebPage","BasePage","page","opts"],"mappings":";AAIO,MAAMA,gBAAgBC;IAC3B,YAAYC,IAAwB,EAAEC,IAAiB,CAAE;QACvD,KAAK,CAACD,MAAM,cAAcC;IAC5B;AACF"}
|
|
@@ -38,9 +38,8 @@ class MidsceneReporter {
|
|
|
38
38
|
throw new Error(`Unknown mode: ${this.mode}`);
|
|
39
39
|
}
|
|
40
40
|
updateReport(testData) {
|
|
41
|
-
var _testData_attributes;
|
|
42
41
|
if (!testData || !this.mode) return;
|
|
43
|
-
const fileName = this.getReportFilename(
|
|
42
|
+
const fileName = this.getReportFilename(testData.attributes?.playwright_test_title);
|
|
44
43
|
const reportPath = writeDumpReport(fileName, testData, 'merged' === this.mode);
|
|
45
44
|
reportPath && printReportMsg(reportPath);
|
|
46
45
|
}
|
|
@@ -48,39 +47,54 @@ class MidsceneReporter {
|
|
|
48
47
|
onTestBegin(_test, _result) {}
|
|
49
48
|
onTestEnd(test, result) {
|
|
50
49
|
const dumpAnnotation = test.annotations.find((annotation)=>'MIDSCENE_DUMP_ANNOTATION' === annotation.type);
|
|
51
|
-
if (!
|
|
50
|
+
if (!dumpAnnotation?.description) return;
|
|
52
51
|
const tempFilePath = dumpAnnotation.description;
|
|
52
|
+
this.tempFiles.add(tempFilePath);
|
|
53
53
|
let dumpString;
|
|
54
54
|
try {
|
|
55
55
|
dumpString = readFileSync(tempFilePath, 'utf-8');
|
|
56
56
|
} catch (error) {
|
|
57
57
|
console.error(`Failed to read Midscene dump file: ${tempFilePath}`, error);
|
|
58
|
-
return;
|
|
59
58
|
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
59
|
+
if (dumpString) {
|
|
60
|
+
const retry = result.retry ? `(retry #${result.retry})` : '';
|
|
61
|
+
const testId = `${test.id}${retry}`;
|
|
62
|
+
const testData = {
|
|
63
|
+
dumpString,
|
|
64
|
+
attributes: {
|
|
65
|
+
playwright_test_id: testId,
|
|
66
|
+
playwright_test_title: `${test.title}${retry}`,
|
|
67
|
+
playwright_test_status: result.status,
|
|
68
|
+
playwright_test_duration: result.duration
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
this.updateReport(testData);
|
|
72
|
+
}
|
|
72
73
|
try {
|
|
73
74
|
rmSync(tempFilePath, {
|
|
74
75
|
force: true
|
|
75
76
|
});
|
|
77
|
+
this.tempFiles.delete(tempFilePath);
|
|
76
78
|
} catch (error) {
|
|
77
79
|
console.warn(`Failed to delete Midscene temp file: ${tempFilePath}`, error);
|
|
78
80
|
}
|
|
79
81
|
}
|
|
82
|
+
onEnd() {
|
|
83
|
+
if (this.tempFiles.size > 0) {
|
|
84
|
+
console.log(`Midscene: Cleaning up ${this.tempFiles.size} remaining temp file(s)...`);
|
|
85
|
+
for (const filePath of this.tempFiles)try {
|
|
86
|
+
rmSync(filePath, {
|
|
87
|
+
force: true
|
|
88
|
+
});
|
|
89
|
+
} catch (error) {}
|
|
90
|
+
this.tempFiles.clear();
|
|
91
|
+
}
|
|
92
|
+
}
|
|
80
93
|
constructor(options = {}){
|
|
81
94
|
_define_property(this, "mergedFilename", void 0);
|
|
82
95
|
_define_property(this, "testTitleToFilename", new Map());
|
|
83
96
|
_define_property(this, "mode", void 0);
|
|
97
|
+
_define_property(this, "tempFiles", new Set());
|
|
84
98
|
this.mode = MidsceneReporter.getMode(options.type ?? 'merged');
|
|
85
99
|
}
|
|
86
100
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"playwright/reporter/index.mjs","sources":["
|
|
1
|
+
{"version":3,"file":"playwright/reporter/index.mjs","sources":["../../../../src/playwright/reporter/index.ts"],"sourcesContent":["import { readFileSync, rmSync } from 'node:fs';\nimport type { ReportDumpWithAttributes } from '@midscene/core';\nimport { getReportFileName, printReportMsg } from '@midscene/core/agent';\nimport { writeDumpReport } from '@midscene/core/utils';\nimport { replaceIllegalPathCharsAndSpace } from '@midscene/shared/utils';\nimport type {\n FullConfig,\n Reporter,\n Suite,\n TestCase,\n TestResult,\n} from '@playwright/test/reporter';\n\ninterface MidsceneReporterOptions {\n type?: 'merged' | 'separate';\n}\n\nclass MidsceneReporter implements Reporter {\n private mergedFilename?: string;\n private testTitleToFilename = new Map<string, string>();\n mode?: 'merged' | 'separate';\n\n // Track all temp files created during this test run for cleanup\n private tempFiles = new Set<string>();\n\n constructor(options: MidsceneReporterOptions = {}) {\n // Set mode from constructor options (official Playwright way)\n this.mode = MidsceneReporter.getMode(options.type ?? 'merged');\n }\n\n private static getMode(reporterType: string): 'merged' | 'separate' {\n if (!reporterType) {\n return 'merged';\n }\n if (reporterType !== 'merged' && reporterType !== 'separate') {\n throw new Error(\n `Unknown reporter type in playwright config: ${reporterType}, only support 'merged' or 'separate'`,\n );\n }\n return reporterType;\n }\n\n private getSeparatedFilename(testTitle: string): string {\n if (!this.testTitleToFilename.has(testTitle)) {\n const baseTag = `playwright-${replaceIllegalPathCharsAndSpace(testTitle)}`;\n const generatedFilename = getReportFileName(baseTag);\n this.testTitleToFilename.set(testTitle, generatedFilename);\n }\n return this.testTitleToFilename.get(testTitle)!;\n }\n\n private getReportFilename(testTitle?: string): string {\n if (this.mode === 'merged') {\n if (!this.mergedFilename) {\n this.mergedFilename = getReportFileName('playwright-merged');\n }\n return this.mergedFilename;\n } else if (this.mode === 'separate') {\n if (!testTitle) throw new Error('testTitle is required in separate mode');\n return this.getSeparatedFilename(testTitle);\n }\n throw new Error(`Unknown mode: ${this.mode}`);\n }\n\n private updateReport(testData: ReportDumpWithAttributes) {\n if (!testData || !this.mode) return;\n const fileName = this.getReportFilename(\n testData.attributes?.playwright_test_title,\n );\n const reportPath = writeDumpReport(\n fileName,\n testData,\n this.mode === 'merged',\n );\n reportPath && printReportMsg(reportPath);\n }\n\n async onBegin(config: FullConfig, suite: Suite) {}\n\n onTestBegin(_test: TestCase, _result: TestResult) {\n // logger(`Starting test ${test.title}`);\n }\n\n onTestEnd(test: TestCase, result: TestResult) {\n const dumpAnnotation = test.annotations.find((annotation) => {\n return annotation.type === 'MIDSCENE_DUMP_ANNOTATION';\n });\n if (!dumpAnnotation?.description) return;\n\n const tempFilePath = dumpAnnotation.description;\n\n // Track this temp file for potential cleanup in onEnd\n this.tempFiles.add(tempFilePath);\n\n let dumpString: string | undefined;\n\n try {\n dumpString = readFileSync(tempFilePath, 'utf-8');\n } catch (error) {\n console.error(\n `Failed to read Midscene dump file: ${tempFilePath}`,\n error,\n );\n // Don't return here - we still need to clean up the temp file\n }\n\n // Only update report if we successfully read the dump\n if (dumpString) {\n const retry = result.retry ? `(retry #${result.retry})` : '';\n const testId = `${test.id}${retry}`;\n const testData: ReportDumpWithAttributes = {\n dumpString,\n attributes: {\n playwright_test_id: testId,\n playwright_test_title: `${test.title}${retry}`,\n playwright_test_status: result.status,\n playwright_test_duration: result.duration,\n },\n };\n\n this.updateReport(testData);\n }\n\n // Always try to clean up temp file\n try {\n rmSync(tempFilePath, { force: true });\n // If successfully deleted, remove from tracking\n this.tempFiles.delete(tempFilePath);\n } catch (error) {\n console.warn(\n `Failed to delete Midscene temp file: ${tempFilePath}`,\n error,\n );\n // Keep in tempFiles for cleanup in onEnd\n }\n }\n\n onEnd() {\n // Clean up any remaining temp files that weren't deleted in onTestEnd\n if (this.tempFiles.size > 0) {\n console.log(\n `Midscene: Cleaning up ${this.tempFiles.size} remaining temp file(s)...`,\n );\n\n for (const filePath of this.tempFiles) {\n try {\n rmSync(filePath, { force: true });\n } catch (error) {\n // Silently ignore - file may have been deleted already\n }\n }\n\n this.tempFiles.clear();\n }\n }\n}\n\nexport default MidsceneReporter;\n"],"names":["MidsceneReporter","reporterType","Error","testTitle","baseTag","replaceIllegalPathCharsAndSpace","generatedFilename","getReportFileName","testData","fileName","reportPath","writeDumpReport","printReportMsg","config","suite","_test","_result","test","result","dumpAnnotation","annotation","tempFilePath","dumpString","readFileSync","error","console","retry","testId","rmSync","filePath","options","Map","Set"],"mappings":"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"}
|
|
@@ -9,30 +9,47 @@ const defaultViewportWidth = 1440;
|
|
|
9
9
|
const defaultViewportHeight = 768;
|
|
10
10
|
const defaultViewportScale = 'darwin' === process.platform ? 2 : 1;
|
|
11
11
|
const defaultWaitForNetworkIdleTimeout = DEFAULT_WAIT_FOR_NETWORK_IDLE_TIMEOUT;
|
|
12
|
+
function resolveAiActionContext(target, preference) {
|
|
13
|
+
const data = preference?.aiActContext ?? preference?.aiActionContext ?? target.aiActionContext;
|
|
14
|
+
return data;
|
|
15
|
+
}
|
|
16
|
+
const DANGEROUS_ARGS = [
|
|
17
|
+
'--no-sandbox',
|
|
18
|
+
'--disable-setuid-sandbox',
|
|
19
|
+
'--disable-web-security',
|
|
20
|
+
'--ignore-certificate-errors',
|
|
21
|
+
'--disable-features=IsolateOrigins',
|
|
22
|
+
'--disable-site-isolation-trials',
|
|
23
|
+
'--allow-running-insecure-content'
|
|
24
|
+
];
|
|
25
|
+
function validateChromeArgs(args, baseArgs) {
|
|
26
|
+
const newArgs = args.filter((arg)=>!baseArgs.some((baseArg)=>{
|
|
27
|
+
const argFlag = arg.split('=')[0];
|
|
28
|
+
const baseFlag = baseArg.split('=')[0];
|
|
29
|
+
return argFlag === baseFlag;
|
|
30
|
+
}));
|
|
31
|
+
const dangerousArgs = newArgs.filter((arg)=>DANGEROUS_ARGS.some((dangerous)=>arg.startsWith(dangerous)));
|
|
32
|
+
if (dangerousArgs.length > 0) console.warn(`Warning: Dangerous Chrome arguments detected: ${dangerousArgs.join(', ')}.\nThese arguments may reduce browser security. Use only in controlled testing environments.`);
|
|
33
|
+
}
|
|
12
34
|
const launcherDebug = getDebug('puppeteer:launcher');
|
|
13
35
|
async function launchPuppeteerPage(target, preference, browser) {
|
|
14
|
-
var _target_waitForNetworkIdle;
|
|
15
36
|
assert(target.url, 'url is required');
|
|
16
37
|
const freeFn = [];
|
|
17
38
|
const ua = target.userAgent || defaultUA;
|
|
18
39
|
let width = defaultViewportWidth;
|
|
19
|
-
let preferMaximizedWindow = true;
|
|
20
40
|
if (target.viewportWidth) {
|
|
21
|
-
preferMaximizedWindow = false;
|
|
22
41
|
assert('number' == typeof target.viewportWidth, 'viewportWidth must be a number');
|
|
23
42
|
width = Number.parseInt(target.viewportWidth, 10);
|
|
24
43
|
assert(width > 0, `viewportWidth must be greater than 0, but got ${width}`);
|
|
25
44
|
}
|
|
26
45
|
let height = defaultViewportHeight;
|
|
27
46
|
if (target.viewportHeight) {
|
|
28
|
-
preferMaximizedWindow = false;
|
|
29
47
|
assert('number' == typeof target.viewportHeight, 'viewportHeight must be a number');
|
|
30
48
|
height = Number.parseInt(target.viewportHeight, 10);
|
|
31
49
|
assert(height > 0, `viewportHeight must be greater than 0, but got ${height}`);
|
|
32
50
|
}
|
|
33
51
|
let dpr = defaultViewportScale;
|
|
34
52
|
if (target.viewportScale) {
|
|
35
|
-
preferMaximizedWindow = false;
|
|
36
53
|
assert('number' == typeof target.viewportScale, 'viewportScale must be a number');
|
|
37
54
|
dpr = Number.parseInt(target.viewportScale, 10);
|
|
38
55
|
assert(dpr > 0, `viewportScale must be greater than 0, but got ${dpr}`);
|
|
@@ -42,11 +59,12 @@ async function launchPuppeteerPage(target, preference, browser) {
|
|
|
42
59
|
height,
|
|
43
60
|
deviceScaleFactor: dpr
|
|
44
61
|
};
|
|
45
|
-
const headed =
|
|
46
|
-
|
|
62
|
+
const headed = preference?.headed || preference?.keepWindow;
|
|
63
|
+
const windowSizeArg = `--window-size=${width},${height + (headed ? 100 : 0)}`;
|
|
64
|
+
const defaultViewportConfig = headed ? null : viewportConfig;
|
|
47
65
|
if (headed && '1' === process.env.CI) console.warn('you are probably running headed mode in CI, this will usually fail.');
|
|
48
66
|
const isWindows = 'win32' === process.platform;
|
|
49
|
-
const
|
|
67
|
+
const baseArgs = [
|
|
50
68
|
...isWindows ? [] : [
|
|
51
69
|
'--no-sandbox',
|
|
52
70
|
'--disable-setuid-sandbox'
|
|
@@ -55,24 +73,33 @@ async function launchPuppeteerPage(target, preference, browser) {
|
|
|
55
73
|
'--disable-features=PasswordLeakDetection',
|
|
56
74
|
'--disable-save-password-bubble',
|
|
57
75
|
`--user-agent="${ua}"`,
|
|
58
|
-
|
|
76
|
+
windowSizeArg
|
|
59
77
|
];
|
|
78
|
+
let args = baseArgs;
|
|
79
|
+
if (target.chromeArgs && target.chromeArgs.length > 0) {
|
|
80
|
+
validateChromeArgs(target.chromeArgs, baseArgs);
|
|
81
|
+
args = [
|
|
82
|
+
...baseArgs,
|
|
83
|
+
...target.chromeArgs
|
|
84
|
+
];
|
|
85
|
+
launcherDebug('Merging custom Chrome arguments', target.chromeArgs, 'Final args', args);
|
|
86
|
+
}
|
|
60
87
|
launcherDebug('launching browser with viewport, headed', headed, 'viewport', viewportConfig, 'args', args, 'preference', preference);
|
|
61
88
|
let browserInstance = browser;
|
|
62
89
|
if (!browserInstance) {
|
|
63
90
|
browserInstance = await puppeteer.launch({
|
|
64
|
-
headless: !
|
|
65
|
-
defaultViewport:
|
|
91
|
+
headless: !preference?.headed,
|
|
92
|
+
defaultViewport: defaultViewportConfig,
|
|
66
93
|
args,
|
|
67
94
|
acceptInsecureCerts: target.acceptInsecureCerts
|
|
68
95
|
});
|
|
69
96
|
freeFn.push({
|
|
70
97
|
name: 'puppeteer_browser',
|
|
71
98
|
fn: ()=>{
|
|
72
|
-
if (!
|
|
73
|
-
|
|
99
|
+
if (!preference?.keepWindow) if (isWindows) setTimeout(()=>{
|
|
100
|
+
browserInstance?.close();
|
|
74
101
|
}, 800);
|
|
75
|
-
else
|
|
102
|
+
else browserInstance?.close();
|
|
76
103
|
}
|
|
77
104
|
});
|
|
78
105
|
}
|
|
@@ -83,7 +110,7 @@ async function launchPuppeteerPage(target, preference, browser) {
|
|
|
83
110
|
}
|
|
84
111
|
if (ua) await page.setUserAgent(ua);
|
|
85
112
|
if (viewportConfig) await page.setViewport(viewportConfig);
|
|
86
|
-
const waitForNetworkIdleTimeout = 'number' == typeof
|
|
113
|
+
const waitForNetworkIdleTimeout = 'number' == typeof target.waitForNetworkIdle?.timeout ? target.waitForNetworkIdle.timeout : defaultWaitForNetworkIdleTimeout;
|
|
87
114
|
try {
|
|
88
115
|
launcherDebug('goto', target.url);
|
|
89
116
|
await page.goto(target.url);
|
|
@@ -94,8 +121,7 @@ async function launchPuppeteerPage(target, preference, browser) {
|
|
|
94
121
|
});
|
|
95
122
|
}
|
|
96
123
|
} catch (e) {
|
|
97
|
-
|
|
98
|
-
if ('boolean' == typeof (null == (_target_waitForNetworkIdle1 = target.waitForNetworkIdle) ? void 0 : _target_waitForNetworkIdle1.continueOnNetworkIdleError) && !(null == (_target_waitForNetworkIdle2 = target.waitForNetworkIdle) ? void 0 : _target_waitForNetworkIdle2.continueOnNetworkIdleError)) {
|
|
124
|
+
if ('boolean' == typeof target.waitForNetworkIdle?.continueOnNetworkIdleError && !target.waitForNetworkIdle?.continueOnNetworkIdleError) {
|
|
99
125
|
const newError = new Error(`failed to wait for network idle: ${e}`, {
|
|
100
126
|
cause: e
|
|
101
127
|
});
|
|
@@ -111,11 +137,11 @@ async function launchPuppeteerPage(target, preference, browser) {
|
|
|
111
137
|
}
|
|
112
138
|
async function puppeteerAgentForTarget(target, preference, browser) {
|
|
113
139
|
const { page, freeFn } = await launchPuppeteerPage(target, preference, browser);
|
|
140
|
+
const aiActContext = resolveAiActionContext(target, preference);
|
|
141
|
+
const { aiActionContext, ...preferenceToUse } = preference ?? {};
|
|
114
142
|
const agent = new PuppeteerAgent(page, {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
cache: null == preference ? void 0 : preference.cache,
|
|
118
|
-
aiActionContext: target.aiActionContext,
|
|
143
|
+
...preferenceToUse,
|
|
144
|
+
aiActContext,
|
|
119
145
|
forceSameTabNavigation: void 0 !== target.forceSameTabNavigation ? target.forceSameTabNavigation : true
|
|
120
146
|
});
|
|
121
147
|
freeFn.push({
|
|
@@ -127,6 +153,6 @@ async function puppeteerAgentForTarget(target, preference, browser) {
|
|
|
127
153
|
freeFn
|
|
128
154
|
};
|
|
129
155
|
}
|
|
130
|
-
export { defaultUA, defaultViewportHeight, defaultViewportScale, defaultViewportWidth, defaultWaitForNetworkIdleTimeout, launchPuppeteerPage, puppeteerAgentForTarget };
|
|
156
|
+
export { defaultUA, defaultViewportHeight, defaultViewportScale, defaultViewportWidth, defaultWaitForNetworkIdleTimeout, launchPuppeteerPage, puppeteerAgentForTarget, resolveAiActionContext };
|
|
131
157
|
|
|
132
158
|
//# sourceMappingURL=agent-launcher.mjs.map
|